Win32:当用户调整窗口大小时我的应用程序冻结
我写了一个win32应用程序。我自己实现了消息循环,如下所示:
bool programcontinue = true;
while(programcontinue)
{
while (PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
IdleProcess();
}
我的应用程序中有一个可调整大小的窗口。通常,IdleProcess() 每秒被调用几次。当用户抓住可调整大小的窗口的一角或边缘时,在用户释放鼠标按钮之前,不会再调用 IdleProcess()。
这里会发生什么?
我尝试用 if 交换内部 while ,但这并没有改变行为。似乎当调整大小开始时,该消息的处理程序在调整大小完成之前不会返回?
有没有办法改变它并在每秒调整大小期间调用 IdleProcess() 几次?
谢谢 马克
编辑:
我用 if 替换内部 while 的意思是:
bool programcontinue = true;
while(programcontinue)
{
if (PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE)) // <<<<
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
IdleProcess();
}
我的窗口 Proc 有点长,但我通过一个小型测试应用程序得到了相同的行为。这与 VS 项目向导创建的 wndproc 相同:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
I write a win32 application. I implemented the message loop myself like this:
bool programcontinue = true;
while(programcontinue)
{
while (PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
IdleProcess();
}
There is a resizable window in my application. Usually, IdleProcess() gets called several times per second. When the user grabs a corner or an edge of the resizable window, IdleProcess() doesn't get called anymore until the user releases the mouse button.
What happens here?
I tried exchanging the inner while with an if, but that doesn't change the behaviour. It seems like when resizing starts, the handler for that message does not return until the resizing is done?
Is there a way to change this and call IdleProcess() during resizing a few times per second?
Thanks
Marc
EDIT:
What I mean by replacing the inner while with if is:
bool programcontinue = true;
while(programcontinue)
{
if (PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE)) // <<<<
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
IdleProcess();
}
My window Proc is a bit lengthy, but I get the same behavior with a small test app. This is identical to the wndproc the VS Project Wizard creates:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
有许多模式操作发生在窗口上。 Win32 模态操作是指通过启动应用程序自己的事件处理循环直到模式完成而将应用程序置于“模式”的函数。常见的应用程序模式包括拖放操作、移动/大小操作、任何时候都会弹出一个对话框,需要输入才能继续应用程序。
所以发生的事情是:您的消息循环没有运行。
您的窗口收到了传递给 DefWindowProc 的 WM_LBUTTONDOWN 消息。 DefWindowProc 确定用户正在尝试以交互方式调整窗口大小或移动窗口,并输入调整大小/移动模态函数。此函数位于消息处理循环中,监视鼠标消息,以便拦截它们以提供交互式大小调整体验,并且仅在大小调整操作完成时退出 - 通常是通过用户释放按住的按钮或按转义键。
您会收到通知 - DefWindowProc 在进入和退出模态事件处理循环时发送 WM_ENTERSIZEMOVE 和 WM_EXITSIZEMOVE 消息。
要继续生成“空闲”消息,通常在调用模态函数之前创建一个计时器(SetTimer) - 或者当收到 DefWindowProc 正在进入模态函数的消息时 - 模态循环将继续调度 WM_TIMER 消息...并调用来自计时器消息处理程序的空闲过程。当模态函数返回时销毁计时器。
There are a number of modal operations that happen on windows. Win32 Modal operations refer to functions that put an application into a "mode" by starting their own event processing loop until the mode finishes. Common application modes include drag and drop operations, move/size operations, anytime a dialog pops up that needs input before the application can continue.
So what is happening is: Your message loop is NOT being run.
Your window recieved a WM_LBUTTONDOWN message that you passed to DefWindowProc. DefWindowProc determined that the user was trying to size or move the window interactively and entered a sizing/moving modal function. This function is in a message processing loop watching for mouse messages so that It can intercept them to provide the interactive sizing experience, and will only exit when the sizing operation completes - typically by the user releasing the held button, or by pressing escape.
You get notified of this - DefWindowProc sends a WM_ENTERSIZEMOVE and WM_EXITSIZEMOVE messages as it enters and exits the modal event processing loop.
To continue to generate "idle" messages, typically create a timer (SetTimer) before calling a modal function - or when getting a message that DefWindowProc is entering a modal function - the modal loop will continue to dispatch WM_TIMER messages... and call the idle proc from the timer message handler. Destroy the timer when the modal function returns.
当 DefWindowProc 使用 wParam 中的 SC_MOVE 或 SC_SIZE 处理 WM_SYSCOMMAND 时,它会进入循环,直到用户通过释放鼠标按钮或按 Enter 或 escape 来停止它。它这样做是因为它允许程序通过处理 WM_PAINT 和 WM_NCPAINT 消息(您仍然应该在窗口过程中接收这些事件)来呈现客户区域(您的小部件或游戏或任何绘制的内容)以及边框和标题区域。
它对于普通的 Windows 应用程序来说工作得很好,这些应用程序通过接收消息来在其窗口过程内完成大部分处理。它只影响在窗口过程之外进行处理的程序,例如游戏(通常是全屏的,无论如何都不受影响)。
但是,有一种解决方法:自己处理 WM_SYSCOMMAND,自己调整大小或移动。这需要付出很大的努力,但可能证明是值得的。或者,当发送 WM_SIZING 时,您可以使用 setjmp/longjmp 来退出窗口过程,或者使用相同的方式使用 Windows Fibers;不过,这些都是黑客解决方案。
上周末我解决了这个问题(使用第一种方法),如果你有兴趣,我已经将代码发布到了 sourceforge 上的公共领域。请务必阅读自述文件,尤其是警告部分。这是: https://sourceforge.net/projects/win32loopl/
When DefWindowProc handles WM_SYSCOMMAND with either SC_MOVE or SC_SIZE in the wParam, it enters a loop until the user stops it by releasing the mouse button, or pressing either enter or escape. It does this because it allows the program to render both the client area (where your widgets or game or whatever is drawn) and the borders and caption area by handling WM_PAINT and WM_NCPAINT messages (you should still receive these events in your Window Procedure).
It works fine for normal Windows apps, which do most of their processing inside of their Window Procedure as a result of receiving messages. It only effects programs which do processing outside of the Window Procedure, such as games (which are usually fullscreen and not affected anyway).
However, there is a way around it: handle WM_SYSCOMMAND yourself, resize or move yourself. This requires a good deal of effort, but may prove to be worth it. Alternatively, you could use setjmp/longjmp to escape from the Window Procedure when WM_SIZING is sent, or Windows Fibers along the same lines; these are hackish solutions though.
I solved it (using the first method) this past weekend, if you're interested I have released the code to the public domain on sourceforge. Just make sure to read the README, especially the caveat section. Here it is: https://sourceforge.net/projects/win32loopl/
您仍然可以收到
WM_PAINT
消息,您只需告诉 WinAPI 您想要它(参见 NeHe OpenGL 教程):它仍然会阻止您的
while
/PeekMessage
-循环! WinAPI 只是直接调用您的WndProc
。You can still receive the
WM_PAINT
message, you just gotta tell the WinAPI that you want it (seen in NeHe OpenGL tutorials):It will still block your
while
/PeekMessage
-loop though! WinAPI just calls yourWndProc
directly.在调整大小期间,Windows 会向您的程序发送大量消息。我还没有证明这一点,但你所描述的行为很熟悉。我建议在 while(...) 循环中调用您的函数 IdleProcess() 来处理某些事件,例如您的应用程序在窗口大小调整期间将频繁收到的 WM_SIZING :
请注意,尽管这是假设的,但 IdleProcess() 不会创建或使用任何事件。如果真是这样,事情就会变得更加复杂。
During resize Windows sends quite a few messages to your program. I have not proved this, but the behavior you describe is familiar. I'd suggest to call your function IdleProcess() also within the while(...) loop for certain events such as WM_SIZING which your application will receive frequently during window resizing:
Be aware though that this assumes, that IdleProcess() does not create or consume any events. If thats the case, things get much more complicated.