C#最小化的窗口无法通过调用System.Diagnostics.Process.GetProcesses()返回

9

我正在尝试查找最小化的窗口并将其显示。

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

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

enter image description here

请注意,有两个窗口A和B。我使用了一个名为Microsoft Inspect的工具来确定这两个程序窗口是普通窗口。它们没有父子关系。但是,当我启动SideSync时,只会出现Window A。然后,我必须点击“Phone Screen”,然后Window B出现(除了Window A)。这可能是解决此问题的线索?我们将看到。

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

enter image description here

这两个窗口都有窗口标题。使用下面的代码,我可以检索窗口的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这样的工具进行了验证? - tj-cappelletti
截图清楚地显示了在该进程名称下打开了两个顶级窗口。它们互不为子窗口,实际上是兄弟窗口。 - Alejandro
@Alejandro - 我在服务器代码部分的底部添加了一个GetWindowProcesses的代码片段。正如你所看到的,它只是使用了System.Diagnostic.Process。 - sapbucket
好的,抱歉,但这个新提出的问题更加准确。 - sapbucket
@管理员:我该如何增加悬赏?我想通过加大奖金来鼓励其他回答。界面似乎认为我只能+100并且必须授予它。我没有看到任何修改的链接或按钮。 - sapbucket
显示剩余4条评论
1个回答

12

我花了一些时间,试图重新创建你的问题。根据我的分析(因为我一开始没有正确运行 SideSync 而花费了一些时间;-)),只有一个名为 SideSync.exe 的进程在运行,它托管多个窗口。

我猜你方法中的“漏洞”是你尝试通过 MainWindowTitle 获取进程。但如果你使用以下代码片段,你会发现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

如您所见,输出还包括标题,例如 Notifier ToolTip ,当 Notification Window 出现或将鼠标移动到 SideSync 应用中的图标上时会出现。 正如在输出中进一步看到的那样,活动窗口的 MainWindowHandle 当然也会发生变化。

因此,在我看来,您只需要使用Process.GetProcessesByName("SideSync")获取 SideSync 进程,仅此而已。

希望这可以帮助您;-)

更新:

根据OP的评论,他需要一种打开 SideSync 进程的一个特定窗口的方法,而不受上次打开哪个窗口的影响。 为了实现这一点,第一步是查找属于 SideSync 进程的窗口的相应窗口句柄。

我基于答案cREcker的代码编写了以下代码,他的答案基于资源获取所有打开窗口的列表

以下类的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);
}

1
非常抱歉回复晚了。悬赏即将到期:如果这个解决方案有效,即使过期也会奖励+100。我现在要尝试你的建议。 - sapbucket
1
这种方法对我不起作用。如果我使用 Process.GetProcessesByName("SideSync"),然后将返回的进程 BringToTop(),它只会带出窗口 A。 - sapbucket
1
如果我最小化窗口A,并手动显示窗口B,然后调用Process.GetProcessesByName("SideSync"),它确实会返回窗口B。但这正是我的要点:当窗口A和B都被最小化时,我想能够弹出窗口B。不应该依赖于“我上次点击了什么”。 - sapbucket
@sapbucket,我明白了,我已经更新了我的答案,希望我理解了你的问题,并且能够帮到你;-) - Markus Safar
@sapbucket,很高兴我能帮到你。请考虑给cREcker的答案点赞,因为我的答案是基于他的答案的;-) - Markus Safar

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