【问题标题】:Starting process and storing handle to window启动进程并将句柄存储到窗口
【发布时间】:2014-04-15 00:44:25
【问题描述】:

有人介意帮我解决我一直困扰的问题吗?我正在使用 C# 并尝试启动几个进程,然后将这些窗口移动到单独的监视器。

到目前为止,这是主要的想法:

Process p1 = Process.Start(@"1.pptx");
Process p2 = Process.Start(@"2.pptx");

SetWindowPos(p1.MainWindowHandle, -1,
                        0,
                        0,
                       100,
                        100,
                        SWP_SHOWWINDOW);
SetWindowPos(p2.MainWindowHandle, -1,
                        200,
                        200,
                       100,
                        100,
                        SWP_SHOWWINDOW);

但是在尝试了一堆不同的东西之后,我一直无法让它工作。谁能给我一些指点?

作为一个让我感到困惑的旁注,如果我打印那些以处理 ID(p1,p2),然后运行此代码:

Process[] processlist = Process.GetProcesses();

foreach (Process process in processlist)
{
       if (!String.IsNullOrEmpty(process.MainWindowTitle))
       {
            Console.WriteLine("Process: {0} ID: {1} Window title: {2}", process.ProcessName, process.Id, process.MainWindowTitle);
       }
}

那些进程 ID 不存在。我知道我一定缺少一些简单的东西......?

更新:上述问题的原因是由于某种原因 MainWindowTitle 没有值,所以它没有打印 pid。

【问题讨论】:

    标签: c# winapi process window-handles


    【解决方案1】:

    当您使用Process.Start 打开一个文档时,这是由shell 处理的。 shell 查看文件关联注册表并采取打开文档所需的任何步骤。

    这可能涉及创建一个新流程,但同样可能不涉及。 Office 应用程序通常会重用已打开的进程来打开新文档。这就是这里发生的事情。

    当这种情况发生时,当没有新进程启动时,shell 为新进程句柄返回 0。这反映在 .net Process 对象上。它解释了为什么你没有主窗口句柄。

    所以从根本上说,你的基本方法是有缺陷的。使用Process.Start 不会产生这些文档的窗口句柄。您必须找到另一种方法来定位这些窗口。例如EnumWindows 或 CBT 钩子。或者也许 COM 自动化是正确的解决方案。

    顺便说一句,您在调用SetWindowPos 时似乎没有检查错误。这会帮助你更快地解决这个问题。调用 Win32 函数时始终检查返回值。

    【讨论】:

    • 嗨,大卫,感谢您的回答。如果你试图找到这个窗口把手,你能建议你做什么吗?一种想法是在调用Process.Start(...) 之前收集所有进程,然后遍历所有进程以寻找以前不存在的进程。另一个想法是使用 EnumWindows 在窗口标题中查找文件名。这适用于 powerpoint 文件,但可能不适用于任何类型的文件。有什么建议么?非常感谢。
    • 枚举窗口。然后 GetWindowThreadProcessId 检查哪个进程拥有该窗口。
    • @DavidHeffernan 感谢您在上面的回答;这很有启发性。但我不明白为什么您上面的评论建议使用 GetWindowThreadProcessId 来检查哪个进程拥有该窗口。有关系吗?进程(Process.Start)在它们执行启动操作时不是已经消失了吗?谢谢。
    • @Kevin No. Windows 存在于进程及其线程的上下文中。
    • @DavidHeffernan 对不起,我不清楚。我的意思是问,有了窗口的 processId,在这种情况下我会用它做什么? (在其他新闻中,我在 Infragistics 博客上阅读了您关于 COM 编组的旧帖子。您的文章非常棒。我希望我了解更多有关编组和释放 COM 资源的信息;很难在一个地方找到这些信息,而且似乎有很多规则。)
    【解决方案2】:

    对于那些仍在寻找答案的人,我就是这样做的。

    首先,在开始任何新进程之前,使用 EnumWindows 获取每个打开的窗口的句柄。然后开始您的过程,然后再次检查所有窗口,确保它们可见并具有窗口文本。如果您只有 1 个新进程,那很可能就是您的新窗口。如果没有,我在失败前已经尝试了 3 次。到目前为止,代码运行良好。

    这里是帮助函数/Win32 API 调用的代码。

            public delegate bool EnumedWindow(IntPtr handleWindow, ArrayList handles);
    
            [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool EnumWindows(EnumedWindow lpEnumFunc, ArrayList lParam);
    
            public static ArrayList GetWindows()
            {
                ArrayList windowHandles = new ArrayList();
                EnumedWindow callBackPtr = GetWindowHandle;
                EnumWindows(callBackPtr, windowHandles);
    
                return windowHandles;
            }
    
            private static bool GetWindowHandle(IntPtr windowHandle, ArrayList windowHandles)
            {
                windowHandles.Add(windowHandle);
                return true;
            }
    
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool IsWindowVisible(IntPtr hWnd);
    
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
    
        const int SWP_SHOWWINDOW = 0x0040;
        [DllImport("user32.dll", EntryPoint = "SetWindowPos", SetLastError = true)]
        public static extern Boolean SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x,     int Y, int cx, int cy, int wFlags);
    

    然后我的主要逻辑是这样的(根据需要进行调整):

    List<IntPtr> alreadyOpenWindows = new List<IntPtr>();
    foreach (IntPtr ip in GetWindows())
    {
         alreadyOpenWindows.Add(ip);
    }
    
    Process.Start("your command here");
    System.Threading.Thread.Sleep(1000);
    
    foreach (IntPtr ip in GetWindows())
    {
        // To consider it a new window, it must be visible, it must not have been open previously, and it must have window text length > 0
        if (IsWindowVisible(ip) && alreadyOpenWindows.Contains(ip) == false)
        {
            StringBuilder windowText = new StringBuilder();
            windowText.Length = 256;
            GetWindowText(ip, windowText, windowText.Length);
    
            if (windowText.Length > 0)
            {
                numNewWindows++;
                handle = ip;
                // break if your confident there will only be one new window opened
             }
        }
    }
    
    
    // Check numNewWindows if you'd like
    
    if (handle != IntPtr.Zero)
    {
        SetWindowPos(handle, -1,
             this.GetScreen().WorkingArea.X,
             this.GetScreen().WorkingArea.Y,
             this.GetScreen().WorkingArea.Width,
             this.GetScreen().WorkingArea.Height,
             SWP_SHOWWINDOW);
    }
    

    【讨论】:

    • 什么是 GetScreen() 方法?
    • 这是很久以前的事了,但我猜像Screen.PrimaryScreen。示例:dotnetperls.com/position-windows
    • 感谢您提供有用的链接。我试试看。
    猜你喜欢
    • 2010-11-28
    • 1970-01-01
    • 2011-06-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多