-
22-09-2019 - |
题
我有一个运行三个应用程序的 Windows 机器。当应用程序启动时,每个应用程序都会创建一个无边框窗口,该窗口的位置使得它们以特定方式重叠。
目前,当我单击底部窗口上的控件时,它会到达窗口堆栈的顶部。
我需要确保每个窗口在窗口堆栈中保持其顺序,即使窗口接收输入也是如此。
我想我需要编写某种简单的窗口管理器来维护窗口的正确 Z 顺序。
问题是,我需要知道特定窗口是否已更改位置。我发现有一个 WM_WINDOWPOSCHANGING 消息,但我的理解是该消息被发送到位置已更改的窗口。
我需要以某种方式通知我的窗口管理器应用程序 Z 顺序已更改。
有什么方法可以捕获所有 WM_ 消息并确定该消息是否适用于我希望控制的窗口之一?
解决方案
而不是MSalter的做法试图DLL注入到每一个运行的应用程序,可以考虑安装一个WH_CBT的Windows钩子。在你CBTProc,返回0,当你得到一个HCBT_MOVESIZE你所关心的。
三个应用程序窗口句柄读MSDN对于文档上 CBTProc 一>和 SetWindowsHookEx函数。
其他提示
最简单的方法可能是注入一个DLL到每个的三个应用程序的。这可确保您只需要处理窗口消息的子集,你真正关心。
接着,通过调用EnumWindows()
找到所有窗口,并呼吁每个GetWindowThreadProcessId()
以确定它是否属于当前进程(即,一种发现在每个应用程序的主窗口(未完全微不足道的,在理论上可以存在更多)您的DLL中注入)。
现在,你有正确的HWND,你可以连接相关的WndProc和赶上发送给它的任何WM_WINDOWPOSCHANGING。
当您创建两个窗口,你想在上面,给这个窗口,你想成为底部的hWndParent
值CreateWindow
。那么Windows将永远把这些窗口前时,所带来的底部窗口前,让他们永远留在它的前面。
所以,如果您的底部窗口是窗口1.首先创建它,那么当你创建窗口2&3,举窗1的句柄作为hWndParent值。窗口管理器没有休息。
您可以使用 SetWindowPos 按照您想要的 Z 顺序定位窗口。我建议您拦截 WM_FOCUS 消息(当窗口接收焦点时,该消息会发送到您的窗口)
在你的 wndProc 函数中,也许你可以尝试这样的事情:
LRESULT wndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam){
// other stuff..
switch (msg){
case WM_FOCUS:
{
HWND firstWindow; // get the first window here
HWND secondWindow; // this would be the second window
HWND thirdWindow; // this would be the third window
// TODO: initialize the windows correctly, based on your priority
SetWindowPos(firstWindow, secondWindow, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOREDRAW | SWP_NOSIZE); // position the second window below the first window
SetWindowPos(secondWindow, thirdWindow, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOREDRAW | SWP_NOSIZE); // position the third window below the second window
}
return 0;
}
// other messages..
}
我不太确定 SetWindowPos 参数的顺序,因为我现在无法测试代码,但这也许可以让您继续下去?
如果您需要拦截所有 WM_ 消息,我建议应用程序调用一个 Window 类,而不是(我猜)调用 CreateWindowEx
他们自己。例如:
class Window {
public
Window(){
...
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(WNDCLASSEX));
wc.cbSize = sizeof(WNDCLASSEX);
wc.lpfnWndProc = wndProc; // <- Note this one
...
}
static LRESULT WINAPI wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam){
// reference: http://www.gamedev.net/community/forums/topic.asp?topic_id=303854 - Evil Steve [Moderator]
Window* parent;
// Get pointer to window
if(msg == WM_CREATE){
parent = (Window*)((LPCREATESTRUCT)lParam)->lpCreateParams;
SetWindowLongPtr(hwnd,GWL_USERDATA,(LONG_PTR)parent);
}
else{
parent = (Window*)GetWindowLongPtr(hwnd,GWL_USERDATA);
if(!parent) return DefWindowProc(hwnd,msg,wParam,lParam);
}
HWND prev = parent->mWin;
parent->mWin = hwnd;
LRESULT ret = parent->wndProc(msg,wParam,lParam);
parent->mWin = prev;
return ret;
}
virtual LRESULT wndProc(UINT msg, WPARAM wParam, LPARAM lParam){
}
};
在此示例中,您的应用程序将从 Window 继承,基本上提供稍作修改的 wndProc 函数(它将缺少 HWND,因此需要将其存储在某处,除非您从 Userdata 中获取它)。
每次您收到消息时
Window::wndProc(HWND, UINT, WPARAM, LPARAM)
函数会拾取它。在这里您可以检查任何消息,包括(但不限于)WM_WINDOWPOSCHANGING
.另一件要做的事情是:
在里面wndProc(UINT, WPARAM, LPARAM)
, ,而不是调用DefWindowProc(..)
你打电话Window::wndProc(UINT, WPARAM, LPARAM)
. 。然后你可以在那里做你的检查(以免堵塞第一个wndProc
功能) :)
这样做的缺点 如果应用程序是由其他人编写的,他们将需要遵守您的窗口类。正如您所解释的,用户不需要与窗口管理器交互,但是,使用这种方法,唯一的交互就是让窗口管理器为用户创建窗口。
否则我认为你必须使用其他答案中解释的钩子
我想我会与约翰Knoeller的答案达成一致。如果你想在窗口停留在一个特定的Z顺序,然后确定这个顺序是什么,以及用适当的父子关系创建窗口。
::SetWindowLong(hwnd_child, GWL_HWNDPARENT, hwnd_parent);
当你这样做的子窗口会一直留在父母的顶部。
如果你还是坚持抓的消息,你可以尝试在每个窗口醒目WM_ACTIVATE,然后转发该消息到你的窗口管理器,它可以访问所有的窗户和他们正确使用SetWindowPos Z顺序的hwnds。而不是SetWindowPos你可以一次全部为Windows和避免闪烁使用DeferWindowPos来改变Z顺序。