Win32:将窗口置于顶层

35

我有一个Windows程序,其中有两个窗口:

hwnd (main interface)

hwnd2 (toplevel window, no parent, created by hwnd)

当我双击hwnd时,我需要hwnd2弹出并显示一些数据,因此我使用这个函数将hwnd2置于顶层:

BringWindowToTop(hwnd2);

hwnd2已置顶,但有一件事很奇怪。当我再次点击hwnd2时,hwnd(主界面)会自动弹出。 我尝试使用以下函数来解决此问题,但都无效。

SetWindowPos(hwnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
                                                                  //doesn't work

BringWindowToTop(hwnd2);    //This is the function brings hwnd2 to top

SetForegroundWindow(hwnd2); //doesn't work

SetWindowPos(hwnd2, HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE); 
                                                                  //doesn't work

SetWindowPos(hwnd2, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
                                       // hwnd2 "always" on top, not what I want

SetActiveWindow(hwnd2); // doesn't work too (for replying to Magnus Skog, thanks)

SwitchToThisWindow(hwnd2, TRUE);// got the same problem with BringWindowToTop function
SwitchToThisWindow(hwnd2, FALSE);

我该怎么解决这个问题呢?谢谢提前回答。(针对aJ的回复,hwnd2没有父级窗口,因为它需要成为顶级窗口,以便在其他窗口前后显示。)(hwnd2是一个由几个窗口组成的媒体播放器,其中一个窗口用于视频显示,另外两个滑动条控件用于进度条和音量条,一个工具栏控件用于控制面板。)(有一种情况可能会有所帮助,只要在Z顺序中"鼠标位于hwnd上方",包括菜单栏和非客户区等,无论我点击hwnd2上的哪个窗口,hwnd都会自动弹出。)(这个媒体播放器是使用DirectShow编写的。我使用IVideoWindow :: put_Owner将视频窗口设置为视频所有者,Direct Show在内部创建子视频窗口作为视频窗口的子级。除了这个子视频窗口我看不到源代码之外,在hwnd2中我看不到任何可疑的东西。)(我找到了原因,是因为DirectShow。我使用多线程来执行它,然后问题得到了解决。但是...为什么?)(可以通过使用PostMessage(而不是SendMessage)来解决此问题。)

2
为什么 hwnd2 没有父窗口?hwnd 在创建时可以设置为父窗口。 - aJ.
你在hwnd2的鼠标按钮处理代码中做了什么?那里有什么可疑之处吗? - Andreas Magnusson
1
那么你是如何使用PostMessage解决它的呢? - Tomáš Zato
以前有效的方法(包括答案中提到的方法)现在对我来说不再有效,显然是因为微软进一步加强了用户界面特权隔离(UIPI)(参考维基百科:en.wikipedia.org/wiki/User_Interface_Privilege_Isolation)。 - undefined
7个回答

30

试一下这个,据说它来自微软

    HWND hCurWnd = ::GetForegroundWindow();
    DWORD dwMyID = ::GetCurrentThreadId();
    DWORD dwCurID = ::GetWindowThreadProcessId(hCurWnd, NULL);
    ::AttachThreadInput(dwCurID, dwMyID, TRUE);
    ::SetWindowPos(m_hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
    ::SetWindowPos(m_hWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOMOVE);
    ::SetForegroundWindow(m_hWnd);
    ::SetFocus(m_hWnd);
    ::SetActiveWindow(m_hWnd);
    ::AttachThreadInput(dwCurID, dwMyID, FALSE);
为了将窗口置于顶层,您需要获取窗口句柄、线程句柄以及处于前景的窗口线程句柄。
然后,我们将我们的线程附加到前台窗口线程并通过AttachThreadInput获取输入。接下来,我们将设置窗口的Z顺序到最顶部,然后将其恢复为普通状态,并调用SetForegroundWindow、SetFocus和SetActiveWindow以确保我们的窗口被置于顶层并且活动且拥有焦点。
然后从旧的前台窗口线程解除输入队列的绑定,使我们的线程成为唯一捕获输入事件的线程。
那么为什么要调用AttachThreadInput呢?因为:
“SetFocus将键盘焦点设置到指定的窗口。该窗口必须附加到调用线程的消息队列中。”
AttachThreadInput是用于允许一组线程共享相同输入状态的函数。通过共享输入状态,线程共享他们对活动窗口的概念。这样,一个线程就可以始终激活另一个线程的窗口。此函数还可用于在由不同线程创建的窗口之间共享焦点状态、鼠标捕获状态、键盘状态和窗口Z顺序状态时共享输入状态。
我们使用SetWindowPos将窗口置于最顶层,并在窗口被隐藏时使用SWP_HIDEWINDOW显示窗口。
“SetWindowPos函数更改子窗口、弹出窗口或顶级窗口的大小、位置和Z顺序。这些窗口根据它们在屏幕上的外观进行排序。最上面的窗口接收最高等级,并且是Z顺序中的第一个窗口。”
如果您的问题是您的窗口也被最小化了,您应该在最后添加一行代码。
ShowWindow(m_hWnd, SW_RESTORE);

我已经寻找这个神奇的歌舞表演有一段时间了。它对我来说完美无缺。 - ulatekh
2
我仍然不理解它的作用,但它就是有效的。 - bowman han
希望我能说同样的话。但对我没用。对话框获得焦点,但另一个对话框仍然在它前面。 - Brian Reinhold
当我想将最小化的窗口置于Win7顶部时,它对我无效。它只是不能将最小化的窗口置于顶部。如果窗口没有被最小化,它可以将窗口置于顶部。在我的Win7上,SetFocus API返回错误代码0x57“参数不正确”。我不知道为什么这个代码忽略了所有的Windows API错误。 - bronze man
没有 Windows API 的源代码,很难说。 - bowman han
显示剩余2条评论

14

经过多次尝试和错误,我找到了以下解决方法:

SendMessage(hwnd, WM_SYSCOMMAND, SC_RESTORE, 0); // restore the minimize window
SetForegroundWindow(hwnd); 
SetActiveWindow(hwnd); 
SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOMOVE  | SWP_NOSIZE);
//redraw to prevent the window blank.
RedrawWindow(hwnd, NULL, 0, RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN );

hwnd 是您 Windows 窗口的 HWND。请不要简单地复制和粘贴。每次调用 API 后,您还需要使用 GetLastError 检查 API 错误。

我在我的 Win7 上确认了以下结果:

  • 可以恢复最小化的窗口并且没有错误返回。
  • 如果窗口已经置顶,则窗口标题会闪烁并且没有错误返回。
  • 如果窗口已关闭,则会返回错误 "0x578 无效的窗口句柄"。
  • 它可以将窗口带到所有未置顶的窗口上方,并且没有错误返回。(例如,它将位于最顶层的任务管理器后面)
  • 它不会使窗口置顶。用户可以将其他窗口置于其上方。

1
你不知道这对我有多大帮助。一切都没有错误,完全正常运行。窗口被激活、重新绘制,而且没有移动或改变大小。如果窗口是最大化的,它将保持最大化状态。唯一的问题是,如果窗口是最大化或全屏状态,它无法恢复到之前的状态。 - joseph lake
1
你不知道这对我有多大帮助。一切都没有错误,完全正常运作。窗口被激活、重新绘制,而且没有移动或改变大小。如果窗口是最大化的,它将继续保持最大化状态。唯一的问题是,如果窗口是最大化或全屏状态,它无法恢复到之前的状态。 - undefined
1
编辑这个不适用于如果从同一个进程调用,使用GetConsoleWindow()进行测试,它会返回当前进程的控制台窗口的HWND。 - joseph lake
1
在Windows 10中,对于以前可以正常工作的功能,支持逐渐变得缓慢退化。其中一个方面就是以前用来将窗口置于前台的方法不再可靠。这段代码已经修复了这个问题。 - undefined

14

两者都很好:

::SetForegroundWindow(wnd)
或者
::SetWindowPos(m_hWnd,       // handle to window
            HWND_TOPMOST,  // placement-order handle
            0,     // horizontal position
            0,      // vertical position
            0,  // width
            0, // height
            SWP_SHOWWINDOW|SWP_NOSIZE|SWP_NOMOVE// window-positioning options
            );

但请记住,最后一个设置窗口始终置于顶部。


我可能有点晚了,但在显示窗口之前,您必须调用::SetForegroundWindow(确保更新窗口)。 - Cedric

8

20
MSDN:此函数已被弃用,不适用于一般用途。建议您不要在新程序中使用它,因为在 Windows 的后续版本中可能会更改或不可用。 - Shay Erlichmen

4

你尝试过使用SetActiveWindow()吗?


0
这将恢复最小化的应用并将其置于前台:
ShowWindow(hWnd, SW_SHOW);
SetForegroundWindow(hWnd);

-6

//工作得很好!

Var
 WndHandle:HWND;

begin
 WndHandle :=FindWindowEx(0,0,nil,'Calculator');
 PostMessage(WndHandle,WM_SHOWWINDOW,SW_RESTORE,0);
 SetForegroundWindow(WndHandle);
end; 

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