C# 最小化 windows 未通过调用 System.Diagnostics.Process.GetProcesses() 返回

C# minimized windows not being returned by call to System.Diagnostics.Process.GetProcesses()

我正在尝试找到最小化的 window 并显示它。

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

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

观察到有两个windows,A和B。我用一个叫Microsoft Inspect的工具确定这两个程序windows是正常的windows。他们没有 child parent 关系。但是,当我启动 SideSync 时,只出现 Window A。然后我必须单击 "Phone Screen" 然后 Window B 出现(除了 Window A)。这可能是解决此问题的线索?我们拭目以待。

以下是 windows 在 Microsoft Inspect 中的显示:

两个 windows 都有 Window 头衔。使用下面的代码,我可以检索 window 的 Process(这是我的 objective)。

服务器代码:

public static Process GetProcessByWindowTitle(string windowTitleContains)
{
    foreach (var windowProcess in GetWindowProcesses())
        if (windowProcess.MainWindowTitle.Contains(windowTitleContains))
            return windowProcess;

    return null;
}

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

Process return 取决于我用鼠标单击的最后一个 window。

例如,如果我上次单击 Window A;然后 GetProcessByWindowTitle("SideSync") 将 return 变成 Process,但是 GetProcessByWindowTitle("SAMSUNG") 将 return void.

...反之亦然,如果我上次单击 Window B,GetProcessByWindowTitle("SideSync") 将 return 一个 void,但随后 GetProcessByWindowTitle("SAMSUNG") 将 return 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."); }
}

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

我花了一些时间尝试重现您的问题。 根据我的分析(这花了一些时间,因为我一开始没有正确获得 SideSync 运行 ;-))名称 SideSync.exe 下只有一个进程 运行 托管多个 windows.

我猜你的方法中的 "flaw" 是你试图通过 MainWindowTitle 获取进程。但是,如果您使用以下代码片段,您会看到 MainWindowTitle 会根据该进程中当前活动的 window 发生变化。

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 应用程序中的图标上。从输出中可以进一步看出,活动 window 的 MainWindowHandle 当然也发生了变化。

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

希望对您有所帮助 ;-)

更新:

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

我根据 cREcker, who has based his answer on the resource Getting a list of all the open windows 的答案代码编写了以下代码。

下面class的方法GetOpenWindowsByProcessId可以获取属于指定进程的所有windows的句柄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);
}

此外,我们需要一种方法来 "show" 一个 window。

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);
}