在.NET中使用SetForegroundWindow出现问题

10

我正在使用PInvoke在.NET中使用SetForegroundWindow API。

当我在Visual Studio调试时使用该API时,它完美地工作。但是,在应用程序通常运行时,它并不总是起作用。

我在调用SetForegroundWindow之前放置了一些日志,并确认API被调用,但有时不起作用。我也看到了几篇关于这个问题的帖子,但我想知道为什么会失败。

帖子的链接如下:


2
为了方便搜索,我将其移动到任务中,并解决了我的问题。不作为答案发布,因为它可能只适用于我的应用程序。但仍然可能对某些人有帮助 - Task.Factory.StartNew(() => { SetForegroundWindow(windowHandle); }); - JsAndDotNet
4个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
13
实际上,这是一个纯粹的Win32问题,而不是.net特定的问题。.net框架建立在Win32之上,在这里,Win32的规则反映在你身上。 SetForegroundWindow文档提供了对你面临问题的全面解释。实际上SetForegroundWindow设计所面临的问题是它可以用于窃取焦点。焦点是用户应该控制的东西。更改焦点的应用程序可能会引起麻烦。因此,SetForegroundWindow尝试防止焦点窃取者。 文档说: 系统限制哪些进程可以设置前景窗口。只有满足以下条件之一的情况下,进程才能设置前景窗口: - 进程是前台进程。 - 该进程是由前台进程启动的。 - 该进程接收到最后的输入事件。 - 没有前台进程。 - 该进程正在被调试。 - 前台进程不是Modern应用程序或开始屏幕。 - 前台没有被锁定(参见LockSetForegroundWindow)。 - 前台锁超时已经过期(参见SystemParametersInfo中的SPI_GETFOREGROUNDLOCKTIMEOUT)。 - 没有处于激活状态的菜单。 应用程序不能在用户使用其他窗口时强制将一个窗口置于前景。相反,Windows会闪烁任务栏按钮以通知用户。 你几乎肯定违反了这些标准。请注意,正在调试的进程始终被授予设置前景窗口的权限。这就是为什么在调试时看不到问题的原因。但是,在调试器外部,如果你的进程不是前台进程,则对SetForegroundWindow的调用将失败。

这都是故意设计的。你应该尝试设计一种不需要在进程不是前台进程时尝试调用SetForegroundWindow的方案。


GetLastError返回0。但是通过日志,我发现SetForegroundWindow失败了。那该怎么办?我尝试了SwitchToThisWindow API,但没有成功。我发布的链接执行AttachThreadInput,但我在那里发现了一个问题。问题是系统可能会变慢,这在链接上报告了。 - Aster Veigas
2
我不确定我能更清楚地解释了。如果您的进程不是前台进程,则不能指望它能够影响前台窗口。解决方案要么是成为前台进程,要么是放弃尝试修改前台进程。不要调用AttachThreadInput。这会带来痛苦。 - David Heffernan

5
诀窍在于“欺骗”Windows(不是试图使用多余的措辞),并将输入附加到所需焦点的新线程上,我从pinvoke网站中获取了大部分内容,但添加了一个测试以恢复最小化的窗口:
private const uint WS_MINIMIZE = 0x20000000;

private const uint SW_SHOW     = 0x05;
private const uint SW_MINIMIZE = 0x06;
private const uint SW_RESTORE  = 0x09;

public static void FocusWindow(IntPtr focusOnWindowHandle)
{
    int style = GetWindowLong(focusOnWindowHandle, GWL_STYLE);

    // Minimize and restore to be able to make it active.
    if ((style & WS_MINIMIZE) == WS_MINIMIZE)
    {
        ShowWindow(focusOnWindowHandle, SW_RESTORE);
    }

    uint currentlyFocusedWindowProcessId = GetWindowThreadProcessId(GetForegroundWindow(), IntPtr.Zero);
    uint appThread = GetCurrentThreadId();

    if (currentlyFocusedWindowProcessId != appThread)
    {
        AttachThreadInput(currentlyFocusedWindowProcessId, appThread, true);
        BringWindowToTop(focusOnWindowHandle);
        ShowWindow(focusOnWindowHandle, SW_SHOW);
        AttachThreadInput(currentlyFocusedWindowProcessId, appThread, false);
    }

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

3
使用RegisterHotKey注册热键,选择热键时要小心,因为您不能干扰现有(或未来的)应用程序。 当需要窃取焦点时(但请记住这是不好的),使用SendInput模拟热键。 然后,您将收到WM_HOTKEY消息,在处理该消息期间,您将被允许使用SetForegroundWindow(我的意思是,它将成功)。 您需要在调用SendInput和处理已发布的WM_HOTKEY之间存储/记住要激活的窗口的HWND。 参考:按下注册的热键会使您获得前台激活爱

我喜欢这个答案。它是我尝试过的所有解决方案中最强大的一个。 - John Meschke
@JohnMeschke 谢谢。我刚刚修复了《The Old New Thing》的失效链接。 - manuell

-2

David是正确的,他引导我走向了正确的方向。我遵循了代码项目文章中所说的: “如果用户按下ALT键或执行某些操作导致系统自身更改前景窗口,则系统会自动启用对SetForegroundWindow的调用”

如何使用SetForegroundWindow()将窗口置于顶部


你确定在调用 SetForegroundWindow 时,你的应用程序是前台进程吗?你认为是什么让你觉得其他应用程序正在调用 LockSetForegroundWindow - David Heffernan
在我的情况下,前景中的另一个应用程序是IE,在记事本中也可以看到这种情况。我已经保留了日志,并且正在检查setforegroundwindow的返回值,在某些情况下它会返回false,并且getlasterror会返回0。这绝对是一个焦点问题。 - Aster Veigas
2
我仍然认为你没有完全理解。如果IE是前台进程,你不能指望SetForegroundWindow会有任何效果。IE肯定不需要调用LockSetForegroundWindow,如果它这样做了,我会感到惊讶。它所需要做的就是成为前台进程,其他进程就无法更改前台进程。 - David Heffernan
1
当IE处于顶部并且根本没有输入IE时,Setforegroundwindow工作正常,只有当IE接收到输入时它才无法始终正常工作。但是Alt键模拟可以解决这个问题。 - Aster Veigas
已接受的解决方案显然不起作用。我知道现在为时已晚,但是对于可能遇到相同问题并来到此页面的任何人,可以在下面的链接中找到一个实际可行的解决方案(文章顶部有免责声明)。对我来说它完美地运行(再次请注意文章顶部的免责声明)。https://shlomio.wordpress.com/2012/09/04/solved-setforegroundwindow-win32-api-not-always-works/ - aleksandaril
它对我来说确实有效,现在仍然有效。你可能面临另一个问题。 - Aster Veigas

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