Win32:当用户调整窗口大小时,我的应用程序会冻结

10

我正在编写一个Win32应用程序。我自己实现了消息循环,如下所示:

     bool programcontinue = true;
     while(programcontinue)
     {
              while (PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE))
              {
                       TranslateMessage(&Msg);
                       DispatchMessage(&Msg);
              }

              IdleProcess();
     }

我的应用程序中有一个可调整大小的窗口。通常,每秒钟会多次调用IdleProcess()。当用户抓住可调整大小窗口的角落或边缘时,直到用户释放鼠标按钮,IdleProcess()不再被调用。

这是怎么回事?

我尝试将内部while替换为if,但这并没有改变行为。似乎当调整大小开始时,处理该消息的处理程序不会返回,直到调整大小完成?

是否有一种方法可以在调整大小期间每秒钟调用几次IdleProcess()?

谢谢 Marc

编辑:

我所说的用if替换内部while是指:

 bool programcontinue = true;
 while(programcontinue)
 {
          if (PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE))  // <<<<
          {
                   TranslateMessage(&Msg);
                   DispatchMessage(&Msg);
          }

          IdleProcess();
 }

我的窗口处理函数有点长,但我用一个小的测试应用程序得到了相同的行为。这与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;
}

你能否发布一下你的WndProc(...)窗口过程,它将接收来自DispatchMessage()的消息,因为你关于将while改为if的评论有点奇怪。 - codencandy
完成,"if"代替"while"的意思是指在程序中使用条件语句来判断是否需要执行循环操作,而不是一直循环直到满足某个条件为止。 - marc40000
根据更新,很明显TranslateMessage或DispatchMessage没有立即返回。你的下一个任务是找出哪一个,并确定哪个消息触发了这个问题。 - Mark Ransom
4个回答

18

有许多模态操作在窗口中发生。Win32模态操作是指通过启动自己的事件处理循环将应用程序置于“模式”中,直到该模式完成的函数。常见的应用程序模式包括拖放操作、移动/大小调整操作以及在应用程序可以继续之前需要输入的对话框弹出时。

所以正在发生的是:您的消息循环没有运行。您的窗口接收到一个WM_LBUTTONDOWN消息,您将其传递给了DefWindowProc。DefWindowProc确定用户正在尝试交互式地调整或移动窗口,并进入大小调整/移动模态功能。这个功能处于一个消息处理循环中,监听鼠标消息,以便拦截它们以提供交互式的调整体验,并且只有在调整操作完成时才会退出 - 通常是因为用户释放了按下的按钮或按下了Escape键。

您会收到通知 - DefWindowProc发送了一个WM_ENTERSIZEMOVE和一个WM_EXITSIZEMOVE消息,当它进入和退出模态事件处理循环时。

要继续生成“空闲”消息,通常在调用模态函数之前创建一个定时器(SetTimer)- 或在获取到DefWindowProc正在进入模态函数的消息时 - 模态循环将继续分派WM_TIMER消息...并从计时器消息处理程序调用空闲过程。当模态函数返回时销毁定时器。


6
当DefWindowProc处理wParam中带有SC_MOVE或SC_SIZE的WM_SYSCOMMAND时,它会进入一个循环,直到用户通过释放鼠标按钮或按下Enter或Escape键停止它。它这样做是因为它允许程序通过处理WM_PAINT和WM_NCPAINT消息来呈现客户区域(您的小部件、游戏或其他绘制内容所在的区域)以及边框和标题区域(您的窗口过程仍然应该接收这些事件)。
对于普通的Windows应用程序,它可以正常工作,因为大多数处理都在窗口过程中进行,以响应消息。它只会影响那些在窗口过程之外进行处理的程序,例如游戏(通常是全屏的,不受影响)。
然而,有一种方法可以解决这个问题:自己处理WM_SYSCOMMAND,自行调整大小或移动。这需要付出很大的努力,但可能值得尝试。或者,您可以使用setjmp/longjmp在发送WM_SIZING时从窗口过程中跳出,或使用类似的Windows Fibers;但这些都是hackish解决方案。
我在上周末解决了这个问题(使用第一种方法),如果您感兴趣,我已经将代码发布到了sourceforge的公共领域中。只需确保阅读README,特别是注意事项部分。这是链接: https://sourceforge.net/projects/win32loopl/

1
你仍然可以接收到WM_PAINT消息,你只需要告诉WinAPI你想要它(在NeHe OpenGL教程中看到):
windowClass.style           = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;   // Redraws The Window For Any Movement / Resizing

但它仍然会阻塞您的while/PeekMessage循环! WinAPI直接调用您的WndProc


0

在调整窗口大小期间,Windows会向您的程序发送许多消息。我没有证明这一点,但您所描述的行为很熟悉。我建议您在while(...)循环中针对某些事件(例如WM_SIZING)也调用IdleProcess()函数,因为您的应用程序在窗口调整期间会频繁接收到这些消息:

 bool programcontinue = true;
 while(programcontinue)
 {
          while (PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE))
          {
                   TranslateMessage(&Msg);
                   DispatchMessage(&Msg);
                   if(Msg.message == WM_SIZING)
                       IdleProcess();
          }

          IdleProcess();
 }

请注意,这里假定 IdleProcess() 不会创建或消费任何事件。如果有这样的情况,问题会变得更加复杂。

那并没有帮助。正如我在问题中所写的“我尝试用if替换内部while”。 - marc40000

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