了解 AttachThreadInput - 分离失去焦点

11

我有点难以完全理解AttachThreadInput.

我知道它在"连接"2个线程的消息队列,这样(我想要的)就允许我强制我的窗口(winforms)置于前台。

我可以用这种方法实现:

private void SetForegroundWindowEx(IntPtr hWnd)
{
    uint SW_SHOW = 5;
    uint appThread = GetCurrentThreadId();     
    uint foregroundThread = GetWindowThreadProcessId(GetForegroundWindow(), IntPtr.Zero);         

    if (foregroundThread != appThread)
    {
        AttachThreadInput(foregroundThread, appThread, true);

        BringWindowToTop(hWnd);
        ShowWindow(hWnd, SW_SHOW);       
        AttachThreadInput(foregroundThread, appThread, false);

    }
    else
    {
        BringWindowToTop(hWnd);
        ShowWindow(hWnd, SW_SHOW);
    }
}

然而,当线程分离后,两个窗口都会立即失去焦点。

如果我等待消息队列被清空(Application.DoEvents()),并激活我的窗口(它现在在前台但没有获得焦点),它将重新获得焦点并保持焦点。

如果我在消息队列被清空之前这样做,它将再次失去焦点。

所以我猜想,某些东西的分离将焦点从我的窗口中取走了,但我不知道那是什么或者如何防止它。

这是我不太理解的第一件事。

我不理解的第二件事是,如果我不将我的窗口设置为前景窗口:

AttachThreadInput(foregroundThread, appThread, true);

AttachThreadInput(foregroundThread, appThread, false);
Application.DoEvents();
this.Activate();

等待消息队列为空,并激活我的窗口(此时它不在前台,而另一个窗口仍然拥有焦点),它实际上被激活了,尽管线程已经不再附加。

也许对 AttachThreadInput 有更好理解的人可以回答我这两个问题。

信息:
在这种情况下,我需要窃取焦点,因为我的应用程序通过 API 被调用。调用我的其他应用程序会等待我的反馈,在大多数情况下会冻结,直到它获得信息。

如果其他应用程序是全屏的,则很多用户没有注意到任务栏中的闪烁,认为其他应用程序崩溃并使用任务管理器杀死它。既然我不能控制其他应用程序,我就无法告诉它将焦点设置为我的窗口。

如果不是绝对必要,就不会调用此方法,在这种情况下,这种敌对行为既是我自己所需要的,也是用户所需要的。


1
你可能会觉得这篇文章这篇文章很有趣。 - Christian.K
这两个链接都在告诉我,我想要附加线程输入是多么糟糕,是吗?还是我错过了什么?我知道这不是常见的做法,应该尽可能避免,但在我的情况下,我确实需要这样做。 - Visions
我的应用程序既不会冻结,其他的也不会。但与本文不同的是,我没有对其进行调试,甚至没有运行VS Studio,因为调试器改变了各种行为(包括这个)。唯一的区别是getCurrentThread和GetForegroundThread的顺序,这不应该有影响(已经测试过)。 - Visions
1
肯定有更好的方法来完成你所需的,而不是这种可怕的黑客方式。包括一些对用户友好得多的方法,这样你的卸载程序就不会成为你应用程序中最常用的功能了。如果你不描述为什么需要这个东西,那么你就无法得到好的建议。 - Hans Passant
如果你想在后台完成一个任务而不会导致UI冻结,你可以将该任务放入一个线程中,并使用委托函数来更新UI。 - Mohammad abumazen
显示剩余2条评论
1个回答

12

这是我用于相同目的的一段C#代码。需要注意,有些情况下确实需要这样做。在我们的情况下,这是为了进行MS Word自动化。每当用户在我们的应用程序中点击工具栏按钮时,我们都应该立即将Word窗口带到用户的注意力中。

public static void ForceWindowIntoForeground(IntPtr window)
{
    uint currentThread = Win32.GetCurrentThreadId();

    IntPtr activeWindow = Win32.GetForegroundWindow();
    uint activeProcess;
    uint activeThread = Win32.GetWindowThreadProcessId(activeWindow, out activeProcess);

    uint windowProcess;
    uint windowThread = Win32.GetWindowThreadProcessId(window, out windowProcess);

    if (currentThread != activeThread)
        Win32.AttachThreadInput(currentThread, activeThread, true);
    if (windowThread != currentThread)
        Win32.AttachThreadInput(windowThread, currentThread, true);

    uint oldTimeout = 0, newTimeout = 0;
    Win32.SystemParametersInfo(Win32.SPI_GETFOREGROUNDLOCKTIMEOUT, 0, ref oldTimeout, 0);
    Win32.SystemParametersInfo(Win32.SPI_SETFOREGROUNDLOCKTIMEOUT, 0, ref newTimeout, 0);
    Win32.LockSetForegroundWindow(LSFW_UNLOCK);
    Win32.AllowSetForegroundWindow(Win32.ASFW_ANY);

    Win32.SetForegroundWindow(window);
    Win32.ShowWindow(window, Win32.SW_RESTORE);

    Win32.SystemParametersInfo(Win32.SPI_SETFOREGROUNDLOCKTIMEOUT, 0, ref oldTimeout, 0);

    if (currentThread != activeThread)
        Win32.AttachThreadInput(currentThread, activeThread, false);
    if (windowThread != currentThread)
        Win32.AttachThreadInput(windowThread, currentThread, false);
}

1
它可以工作,我只是使用了 Win32.ShowWindow(window, Win32.SW_SHOW); - xmedeko
嗨@noseratio,我遇到了类似的问题。我想知道为什么您在上面的片段中使用了FOREGROUNDLOCKTIMEOUT - Ishan Pandya
根据文档SPI_GETFOREGROUNDLOCKTIMEOUT设置用户输入后系统在毫秒级时间内不允许应用程序强制进入前台的时间。因为我想要强制将我的应用程序置于前台,所以我希望将其设置为0。 - noseratio - open to work

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