调用 System.Diagnostics.Process.GetProcesses() 未返回 C# 最小化窗口

Posted

技术标签:

【中文标题】调用 System.Diagnostics.Process.GetProcesses() 未返回 C# 最小化窗口【英文标题】:C# minimized windows not being returned by call to System.Diagnostics.Process.GetProcesses() 【发布时间】:2019-04-02 18:40:39 【问题描述】:

我正在尝试找到一个最小化的窗口并显示它。

该程序可以从三星下载,它的标题是“SideSync”。要完全复制我的问题,您需要安装它并且还需要将三星手机插入您的计算机。

这是完全配置并运行的屏幕截图:

观察有两个窗口,A 和 B。我使用了一个名为 Microsoft Inspect 的工具来确定这两个程序窗口是正常窗口。他们没有孩子父母的关系。但是,当我启动 SideSync 时,只出现 Window A。然后我必须单击“电话屏幕”,然后出现窗口 B(除了窗口 A)。这可能是解决此问题的线索?我们拭目以待。

这是 Microsoft Inspect 中显示的两个窗口:

两个窗口都有窗口标题。使用下面的代码,我可以检索窗口的Process(这是我的目标)。

服务器代码:

public static Process GetProcessByWindowTitle(string windowTitleContains)

    foreach (var windowProcess in GetWindowProcesses())
        if (windowProcess.MainWindowTitle.Contains(windowTitleContains))
            return windowProcess;

    return null;

然而,一些奇怪的行为正在发生。 GetProcessByWindowTitle() 将返回一个而不是两个进程。我假设因为有两个窗口,所以必须有两个进程。

它返回哪个Process 取决于我用鼠标单击的最后一个窗口。

例如,如果我最后一次单击窗口 A;然后GetProcessByWindowTitle("SideSync") 将返回Process,但随后GetProcessByWindowTitle("SAMSUNG") 将返回void

...反之亦然,如果我最后一次单击窗口 B,GetProcessByWindowTitle("SideSync") 将返回 void,但随后 GetProcessByWindowTitle("SAMSUNG") 将返回 Process

客户端代码:

[Ignore("Requires starting SideSync and clicking one of the windows. Only the last clicked will return a Process.")]
[Test]
public void NonMinimizedWindowProcessIsDetected()


    Process p1 = Windows.GetProcessByWindowTitle("SAMSUNG");

    if(p1==null)  Console.WriteLine("SAMSUNG process is null.");
    else  Console.WriteLine("SAMSUNG process detected.");

    Process p2 = Windows.GetProcessByWindowTitle("SideSync");

    if (p2 == null)  Console.WriteLine("SideSync process is null."); 
    else  Console.WriteLine("SideSync process detected."); 

我的目标是显示窗口 B。 我的问题是,这只有在我最后单击它时才有可能,这会产生不必要的依赖。 我希望能够独立于任何点击顺序显示窗口 B。

【问题讨论】:

您说您相信您正在寻找的窗口是另一个窗口的子窗口。您是否使用 Microsoft 的 Inspect 等工具验证了这一点? 截图清楚地显示在该进程名称下打开了2个***窗口。两者都不是对方的孩子,他们实际上是兄弟姐妹。 @Alejandro - 你可能是对的。但是,当最小化时,GetWindowProcesses() 只返回“SideSync”进程名称。另一方面,如果我最大化两个窗口,GetWindowProcesses() 会返回“SideSync”和“Samsung...”进程。这是一个谜,我发布这个问题的原因是为了找出为什么没有列出“三星...”... @sapbucket 子窗口不能最大化或最小化,它们完全存在于它们的父窗口中,也许这两个是普通窗口和对话框,但绝对不是父子窗口。然而,最有可能的是,两者都属于同一进程。至于GetWindowProcesses,不看它的定义很难说。 @Alejandro - 我在服务器代码部分的底部添加了 GetWindowProcesses 的代码 sn-p。如您所见,它只是使用 System.Diagnostic.Process 【参考方案1】:

我花了一些时间尝试重现您的问题。 根据我的分析(这花了一些时间,因为我一开始并没有让 SideSync 正确运行 ;-))只有一个进程以 SideSync.exe 的名称运行,它承载多个窗口。

我猜你的方法中的“缺陷”是你试图通过MainWindowTitle 获取进程。但是如果你使用下面的代码 sn -p 你会看到,MainWindowTitle 会根据该进程中当前活动的窗口而变化。

while (true)

    var processes = Process.GetProcesses();

    foreach (var process in processes)
    
        if (process.ProcessName != "SideSync")
            continue;

        Console.WriteLine($"process.ProcessName, process.MainWindowTitle, process.MainWindowHandle.ToString()");
    

    Thread.Sleep(1000);

在我的情况下,MainWindowTitle 在不同的标题之间发生了变化,例如:

SideSync, SideSync, 1313728
SideSync, SideSync, 1313728
SideSync, SideSync, 1313728
SideSync, SideSync, 1313728
SideSync, SideSync, 1313728
SideSync, Notifier, 1903168
SideSync, Notifier, 1903168
SideSync, Notifier, 1903168
SideSync, Notifier, 1903168
SideSync, Notifier, 1903168
SideSync, Notifier, 1903168
SideSync, Notifier, 1903168
SideSync, Notifier, 1903168
SideSync, Notifier, 1903168
SideSync, Samsung Galaxy S7, 3082852
SideSync, Samsung Galaxy S7, 3082852
SideSync, Samsung Galaxy S7, 3082852
SideSync, Samsung Galaxy S7, 3082852
SideSync, ToolTip, 3148196
SideSync, Samsung Galaxy S7, 3082852
SideSync, Samsung Galaxy S7, 3082852
SideSync, Samsung Galaxy S7, 3082852
SideSync, Samsung Galaxy S7, 3082852
SideSync, ToolTip, 3148196
SideSync, ToolTip, 3148196
SideSync, Samsung Galaxy S7, 3082852
SideSync, Samsung Galaxy S7, 3082852
SideSync, SideSync, 1313728
SideSync, SideSync, 1313728
SideSync, SideSync, 1313728
SideSync, SideSync, 1313728
SideSync, SideSync, 1313728
SideSync, SideSync, 1313728
SideSync, SideSync, 1313728
SideSync, SideSync, 1313728

如您所见,输出还包括 NotifierToolTip 等标题,这些标题会在出现 Notification Window 或您将鼠标移到上方时出现SideSync 应用程序中的图标。从输出中可以进一步看出,活动窗口的MainWindowHandle 当然也会发生变化。

所以在我看来,您只需要使用 Process.GetProcessesByName("SideSync") 获取 SideSync 进程,没有别的。

我希望这会有所帮助;-)

更新:

根据 OP 的评论,他需要一种方法来打开 SideSync 进程的一个特定窗口,该窗口独立于上次打开的进程。为了实现这一点,第一步是找到属于SideSync进程的窗口对应的窗口句柄。

以下代码基于cREcker 的答案代码,他的答案基于资源Getting a list of all the open windows。

下面类的方法GetOpenWindowsByProcessId可以获取属于指定进程id的所有窗口的句柄。

public static class OpenWindowGetter

    public static IDictionary<string, IntPtr> GetOpenWindowsByProcessId(int processId)
    
        IntPtr shellWindow = GetShellWindow();
        Dictionary<string, IntPtr> windows = new Dictionary<string, IntPtr>();

        EnumWindows(delegate (IntPtr hWnd, int lParam)
        
            uint ownerProcessId;
            GetWindowThreadProcessId(hWnd, out ownerProcessId);

            if (ownerProcessId != processId)
                return true;

            if (hWnd == shellWindow)
                return true;

            if (!IsWindowVisible(hWnd))
                return true;

            int length = GetWindowTextLength(hWnd);

            if (length == 0)
                return true;

            StringBuilder builder = new StringBuilder(length);
            GetWindowText(hWnd, builder, length + 1);
            windows[builder.ToString()] = hWnd;

            return true;

        , 0);

        return windows;
    

    private delegate bool EnumWindowsProc(IntPtr hWnd, int lParam);

    [DllImport("USER32.DLL")]
    private static extern bool EnumWindows(EnumWindowsProc enumFunc, int lParam);

    [DllImport("USER32.DLL")]
    private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);

    [DllImport("USER32.DLL")]
    private static extern int GetWindowTextLength(IntPtr hWnd);

    [DllImport("USER32.DLL")]
    private static extern bool IsWindowVisible(IntPtr hWnd);

    [DllImport("USER32.DLL")]
    private static extern IntPtr GetShellWindow();

    [DllImport("user32.dll", SetLastError = true)]
    private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

此外,我们还需要一种“显示”窗口的方法。

private const int SW_SHOWNORMAL = 1;
private const int SW_SHOWMINIMIZED = 2;
private const int SW_SHOWMAXIMIZED = 3;

[DllImport("user32.dll")]
private static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);

有了这些知识,我们现在可以编写如下代码(肯定可以通过数千种不同的方式进行优化):

private static void ShowPhoneScreenWindow()

    const string PROCESSNAME = "sidesync";
    const string WINDOWTITLE = "samsung";

    var process = Process.GetProcessesByName(PROCESSNAME).FirstOrDefault();

    if (process == null)
        throw new InvalidOperationException($"No process with name PROCESSNAME running.");

    var windowHandles = OpenWindowGetter.GetOpenWindowsByProcessId(process.Id);
    IntPtr windowHandle = IntPtr.Zero;

    foreach (var key in windowHandles.Keys)
        if (key.ToLower().StartsWith(WINDOWTITLE))
        
            windowHandle = windowHandles[key];
            break;
        

    if (windowHandle == IntPtr.Zero)
        throw new InvalidOperationException($"No window with title WINDOWTITLE hosted.");

    ShowWindowAsync(windowHandle, SW_SHOWNORMAL);

【讨论】:

对于迟到的回复,我深表歉意。赏金即将到期:如果此解决方案有效,即使在到期后我也会奖励 +100。我现在就试试你的建议。 这种方法对我不起作用。如果我使用 Process.GetProcessesByName("SideSync"),然后使用 BringToTop() 进程返回,它只会打开窗口 A。 如果我最小化窗口 A,并手动显示窗口 B,然后调用 Process.GetProcessesByName("SideSync"),它确实会返回窗口 B。但这正是我的观点:我希望能够当窗口 A 和 B 都被最小化时调出窗口 B。 “我最后点击的内容”不应依赖。 @sapbucket,我明白了,我已经更新了我的答案,希望我能相应地理解你的问题并帮助你;-) @sapbucket,很高兴我能帮上忙。请考虑支持cREcker 的答案以及我的答案是基于他的;-)

以上是关于调用 System.Diagnostics.Process.GetProcesses() 未返回 C# 最小化窗口的主要内容,如果未能解决你的问题,请参考以下文章

java三种调用方式(同步调用/回调/异步调用)

LINUX系统调用

引用调用 vs 复制调用调用

RPC 调用和 HTTP 调用的区别

js方法调用

深入理解Java虚拟机——方法调用(解析调用)