如何打开一个进程,使其不具有焦点?

19

我正在尝试编写一些自动化程序来打开和关闭一系列窗口(非隐藏、非恶意),并且我不希望它们在打开时夺取焦点。问题是,每个窗口打开时都会夺取焦点,这会阻止我在后台运行时进行工作。

以下是我在循环中执行以打开各个窗口的代码:

using (Process proc = new Process())
{
    proc.StartInfo.FileName = filename;
    proc.StartInfo.Arguments = arguments;
    proc.Start();

    Thread.Sleep(1000);

    if (!proc.HasExited)
    {
        proc.Kill();
    }
}
如何使这些窗口不聚焦,以便在自动化运行时可以执行其他操作?
补充说明: 执行上述代码的程序是一个简单的控制台应用程序。我要启动的进程是GUI应用程序。为了测试/设计目的,我当前正在尝试使用不同参数的Internet Explorer(iexplore.exe)的重复实例。 当此程序在后台运行时,我将运行并继续进行其他无关工作。我也不想将焦点返回到父级应用程序。 实质上,当我到达办公桌时,我将运行此.exe文件,并切换到其他窗口以进行其他工作,直到完成忽略原始程序及其子进程为止。

你是否在与应用程序的其余部分相同的线程中运行此代码?它是在WinForms或WPF应用程序内运行吗? - Tomas McGuinness
需要将焦点返回到您的控制台 shell,还是返回到新进程启动之前具有焦点的任何程序? - Servy
哦,还有一个我过去使用过的很不错的无代码解决方案: 当进行 Web 应用程序的自动化测试时,我的一个程序在某些时候需要频繁打开新的浏览器窗口但运行时间很长。为了避免它一直抢占焦点并使工作站无法使用,我从虚拟机中运行它,这样它只会使该虚拟机无法使用,而让主机(和其他虚拟机)在其运行时保持可用。我也可以随时通过查看虚拟机监控进度(双显示器真是太棒了)。 - Servy
@Servy,将焦点放在进程启动时的任何进程上。目的是启动此任务并让我在其后台执行其操作的同时继续进行不相关的工作。 - chaosTechnician
显示剩余3条评论
6个回答

18

虽然可行,但只能通过P/Invoke实现,不幸的是这需要大约70行代码:

[StructLayout(LayoutKind.Sequential)]
struct STARTUPINFO
{
    public Int32 cb;
    public string lpReserved;
    public string lpDesktop;
    public string lpTitle;
    public Int32 dwX;
    public Int32 dwY;
    public Int32 dwXSize;
    public Int32 dwYSize;
    public Int32 dwXCountChars;
    public Int32 dwYCountChars;
    public Int32 dwFillAttribute;
    public Int32 dwFlags;
    public Int16 wShowWindow;
    public Int16 cbReserved2;
    public IntPtr lpReserved2;
    public IntPtr hStdInput;
    public IntPtr hStdOutput;
    public IntPtr hStdError;
}

[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
    public IntPtr hProcess;
    public IntPtr hThread;
    public int dwProcessId;
    public int dwThreadId;
}

[DllImport("kernel32.dll")]
static extern bool CreateProcess(
    string lpApplicationName,
    string lpCommandLine,
    IntPtr lpProcessAttributes,
    IntPtr lpThreadAttributes,
    bool bInheritHandles,
    uint dwCreationFlags,
    IntPtr lpEnvironment,
    string lpCurrentDirectory,
    [In] ref STARTUPINFO lpStartupInfo,
    out PROCESS_INFORMATION lpProcessInformation
);

[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CloseHandle(IntPtr hObject);

const int STARTF_USESHOWWINDOW = 1;
const int SW_SHOWNOACTIVATE = 4; 
const int SW_SHOWMINNOACTIVE = 7; 


public static void StartProcessNoActivate(string cmdLine)
{
    STARTUPINFO si = new STARTUPINFO();
    si.cb = Marshal.SizeOf(si);
    si.dwFlags = STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_SHOWMINNOACTIVE;

    PROCESS_INFORMATION pi = new PROCESS_INFORMATION();

    CreateProcess(null, cmdLine, IntPtr.Zero, IntPtr.Zero, true,
        0, IntPtr.Zero, null, ref si, out pi);

    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
}

si.wShowWindow 设置为 SW_SHOWNOACTIVATE 可以以常规方式显示窗口,但不会窃取焦点,而将其设置为 SW_SHOWMINNOACTIVE 则可以将应用程序最小化,同样不会窃取焦点。

完整的选项列表在此处提供:http://msdn.microsoft.com/en-us/library/windows/desktop/ms633548(v=vs.85).aspx


3
这对我很有用,尽管我不得不在CreateProcess调用中添加CREATE_NEW_CONSOLE。我感激不已,因为焦点窃取让我疯了。 - Ed Bayiates
@EdBayiates,你能展示一下你如何实现CREATE_NEW_CONSOLE标记吗?我尝试将其作为CreateProcess的第6个参数中的值10,但我的执行依然显示在前台。 - Valamas
3
@Valamas-AUS const int CREATE_NEW_CONSOLE = 0x00000010; 和 CreateProcess(appName, cmdLine, IntPtr.Zero, IntPtr.Zero, false, CREATE_NEW_CONSOLE, IntPtr.Zero, basePath, ref si, out pi); - Ed Bayiates
这个有没有可用的封装库? - Jared Beach
请参考此处的代码(https://github.com/jmbeach/betterprocess/blob/master/tests/BetterProcessTests/StartProcessTests.cs),该代码模仿了System.Diagnostics.Process。 - Jared Beach

2
您可以将焦点移至您的应用程序。
    [DllImport("User32")]
    private static extern int SetForegroundWindow(IntPtr hwnd);
    [DllImportAttribute("User32.DLL")]
    private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);


 Process.Start("");
        Thread.Sleep(100);
        var myWindowHandler = Process.GetCurrentProcess().MainWindowHandle;
        ShowWindow(myWindowHandler, 5);
        SetForegroundWindow(myWindowHandler);

SetForegroundWindow

ShowWindow


这会将焦点恢复到“父”应用程序,对吗?我也不希望发生这种情况。在此运行时,我将进行其他工作。(我已经添加了一些内容到我的原始问题中,以使其更清晰。) - chaosTechnician
除非我做错了什么,否则在使用Process.Start之前使用GetForegroundWindow,然后使用ShowWindowSetForegroundWindow来恢复焦点窗口也没有起作用。这很奇怪。 - chaosTechnician
当启动进程的程序不是具有焦点的程序时,这并不能解决让焦点保持在原地的问题。 - Doc Brown
谢谢Sebastian, 这正是我想要的。 :) - Zoltan

1

问题已解决。

我最终采用的解决方案规避了任何属性或重新分配焦点。由于任务是自动化和独立的,我只是使用了Windows任务计划程序来运行应用程序。出于某种原因,只要“父”控制台窗口没有焦点,“子”GUI窗口就会正常打开但不会获得焦点——这使我可以在另一个窗口中继续工作而应用程序仍在运行。


我用来启动进程的代码已经在问题中了。这个答案说明我使用了Windows任务计划程序,但是并没有提供相关代码。你需要什么代码? - chaosTechnician
你手动使用过Windows任务计划程序吗?我以为你用C#创建了一个任务,并以编程方式运行它。据我所知,这是可能的,但文档不是很完善。我请求那段代码。但如果你是手动完成的,那我就误解了。 - Senador

0

SetForegroundWindow 在控制台应用程序中同样有效。

测试代码:

创建一个简单的类:

public class MyClass
{
    [DllImport("user32.dll")]
    static extern bool SetForegroundWindow(IntPtr hWnd);

    public void doProcess(string filename, string arguments){

        using (Process proc = new Process())
        {
            proc.StartInfo.FileName = filename;
            proc.StartInfo.Arguments = arguments;
            proc.Start();

            SetForegroundWindow(proc.MainWindowHandle);
        }
    }
}

然后在控制台应用程序的main方法中:

class Program
{
    static void Main(string[] args)
    {
       MyClass mc = new MyClass();
       mc.doProcess("iexplore.exe", "http://www.stackoverflow.com");

       Console.ReadKey();
    }
}

这会将焦点恢复到“父”应用程序,对吗?我也不希望发生这种情况。在此运行时,我将进行其他工作。(我已经添加了一些内容到我的原始问题中,以使其更清晰。) - chaosTechnician

-2

  1. 这些不是控制台应用程序,它们是 GUI 应用程序。
  2. 要求说明它们不能被隐藏。
- Servy

-3

我个人没有尝试过,但是我相信如果您将 proc.StartInfo.WindowStyle 设置为 WindowStyle.Minimized,那么应该可以达到效果。


它是否实际上最小化了,但在它打开的那一瞬间窃取了焦点? - csauve
它似乎以最小化和焦点启动进程。如果我正在另一个窗口中打字,当新进程仅出现在任务栏上时,我会失去焦点。 - chaosTechnician

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接