在调整窗口大小时是否可以完全消除闪烁?

时间:2012-08-26 13:30:18

标签: c winapi double-buffering

通常情况下,即使使用双缓冲,在调整窗口大小时,似乎也不可避免地会发生闪烁。

第1步,原始窗口。

  

Step 1

步骤2,调整窗口大小,但未绘制额外区域。

  

Step 2

步骤3,调整窗口大小,并绘制额外区域。

  

Step 3

有可能以某种方式隐藏setp 2吗?我可以暂停调整大小过程直到绘画操作完成吗?

以下是一个例子:

#include <Windows.h>
#include <windowsx.h>
#include <Uxtheme.h>

#pragma comment(lib, "Uxtheme.lib")

LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
BOOL MainWindow_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct);
void MainWindow_OnDestroy(HWND hWnd);
void MainWindow_OnSize(HWND hWnd, UINT state, int cx, int cy);
void MainWindow_OnPaint(HWND hWnd);

int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
  WNDCLASSEX wcex = { 0 };
  HWND hWnd;
  MSG msg;
  BOOL ret;

  wcex.cbSize = sizeof(wcex);
  wcex.lpfnWndProc = WindowProc;
  wcex.hInstance = hInstance;
  wcex.hIcon = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
  wcex.hCursor = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
  wcex.lpszClassName = TEXT("MainWindow");
  wcex.hIconSm = wcex.hIcon;

  if (!RegisterClassEx(&wcex))
  {
    return 1;
  }

  hWnd = CreateWindow(wcex.lpszClassName, TEXT("CWin32"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, HWND_DESKTOP, NULL, hInstance, NULL);
  if (!hWnd)
  {
    return 1;
  }

  ShowWindow(hWnd, nCmdShow);
  UpdateWindow(hWnd);

  while ((ret = GetMessage(&msg, NULL, 0, 0)) != 0)
  {
    if (ret == -1)
    {
      return 1;
    }
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }

  return msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  switch (uMsg)
  {
    HANDLE_MSG(hWnd, WM_CREATE, MainWindow_OnCreate);
    HANDLE_MSG(hWnd, WM_DESTROY, MainWindow_OnDestroy);
    HANDLE_MSG(hWnd, WM_SIZE, MainWindow_OnSize);
    HANDLE_MSG(hWnd, WM_PAINT, MainWindow_OnPaint);
  default:
    return DefWindowProc(hWnd, uMsg, wParam, lParam);
  }
}

BOOL MainWindow_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct)
{
  BufferedPaintInit();
  return TRUE;
}

void MainWindow_OnDestroy(HWND hWnd)
{
  BufferedPaintUnInit();
  PostQuitMessage(0);
}

void MainWindow_OnSize(HWND hWnd, UINT state, int cx, int cy)
{
  InvalidateRect(hWnd, NULL, FALSE);
}

void MainWindow_OnPaint(HWND hWnd)
{
  PAINTSTRUCT ps;
  HPAINTBUFFER hpb;
  HDC hdc;

  BeginPaint(hWnd, &ps);
  hpb = BeginBufferedPaint(ps.hdc, &ps.rcPaint, BPBF_COMPATIBLEBITMAP, NULL, &hdc);

  FillRect(hdc, &ps.rcPaint, GetStockBrush(DKGRAY_BRUSH));
  Sleep(320); // This simulates some slow drawing actions.

  EndBufferedPaint(hpb, TRUE);
  EndPaint(hWnd, &ps);
}

是否可以消除闪烁?

4 个答案:

答案 0 :(得分:8)

在拖动操作期间更新窗口时,操作系统必须在扩展窗口区域中显示某些内容。如果你不能提供任何东西,它将显示背景,直到你这样做。由于您未指定任何背景,因此会出现黑度。当然你应该指定背景画笔?只需在代码中添加以下内容即可使行为更加可口:

wcex.hbrBackground = GetStockBrush(DKGRAY_BRUSH);

但是,如果您花费320毫秒来响应WM_PAINT,则会破坏用户的调整大小UI。它变得生涩,反应迟钝。该系统的设计基于这样的假设:您可以足够快地绘制窗口以便拖动以感觉平滑。解决问题的正确方法是让WM_PAINT在合理的时间内运行。

如果你真的无法实现足够快速的绘画以实现平滑拖动,那么我建议采用以下几种方法:

  1. 在拖动过程中禁用窗口更新。我确信这可以针对个别窗户进行,但我不记得如何做到这一点。
  2. 在调整大小/拖动处于活动状态时绘制伪造的东西,并推迟真实绘画,直到调整大小/拖动完成为止。倾听WM_ENTERSIZEMOVEWM_EXITSIZEMOVE是关键。此MSDN文章说明了如何执行此操作:http://support.microsoft.com/kb/121541

答案 1 :(得分:3)

使用WM_SIZING代替WM_SIZE,不要忘记WM_ERASEBKGND

答案 2 :(得分:2)

如果您进入系统属性控制面板小程序,请选择高级标签,然后点击设置... strong>效果组框,您会看到一个名为拖动时显示窗口内容的复选框设置。如果取消选中并尝试调整窗口大小,您将看到只有窗口框架移动,直到您完成拖动操作,然后窗口仅以新尺寸重新绘制一次。这就是当我们使用缓慢,苛刻的计算机时,窗口大小调整的工作方式。

现在我们真的不想全局更改设置(通过使用SPI_SETDRAGFULLWINDOWS调用SystemParametersInfo可以做到这一点,但实际上并没有这样做,因为您的用户不喜欢它。)

当用户抓住调整大小边框时,线程进入由窗口管理器控制的模态循环时会发生什么。当循环开始时,您的窗口将获得WM_ENTERSIZEMOVE,当操作完成时,您将看到WM_EXITSIZEMOVE。在某些时候,你还会得到一个WM_GETMINMAXINFO,这可能与你需要做的事情无关。当用户拖动大小调整框架时,您还会快速收到WM_SIZINGWM_SIZE条消息(而快速WM_SIZE通常会导致WM_PAINT s。)

全局拖动时显示窗口内容设置负责获取快速WM_SIZE消息。如果该设置已关闭,那么当它全部结束时,您只会收到一条WM_SIZE消息。

如果您的窗口很复杂,您可能在WM_SIZE处理程序中有布局代码计算内容(可能还有移动子窗口)以及WM_PAINT处理程序中的大量绘制代码。如果所有代码都太慢(因为你的样本延迟时间为320毫秒),那么你将获得一个闪烁的,不稳定的体验。

我们真的不想改变全局设置,但它确实激发了解决问题的方法:

  

在调整大小操作期间执行更简单的绘制,然后在操作结束时执行一次(较慢)复杂绘制。

解决方案:

  1. 当您看到WM_ENTERSIZEMOVE时设置一个标志。
  2. 更改WM_SIZE处理程序以检查标志,如果已设置则不执行任何操作。
  3. 更改WM_PAINT处理程序以检查标志,如果已设置,则以纯色快速填充窗口。
  4. 当您看到WM_EXITSIZEMOVE时清除标记,然后触发布局代码并使窗口无效,以便根据最终大小更新所有内容。
  5. 如果您的慢窗口是孩子而不是应用程序的顶级窗口,那么当顶级窗口获取WM_ENTERSIZEMOVE和WM_EXITSIZEMOVE以执行步骤1和4时,您必须发出子窗口的信号。

答案 3 :(得分:0)

是的,你可以完全删除闪烁:)

您可以在一个线程中执行所有窗口消息处理,并在另一个线程中绘制其上下文。您的窗口始终保持响应。它很有效,无法理解为什么这不是最佳实践。

例如,如果绑定Direct3D上下文,它可以在调整大小时进行即时缩放,完全不需要更新上下文!

我的代码如下所示:

int WINAPI wWinMain( HINSTANCE a_hInstance, HINSTANCE a_hPrevInstance, LPWSTR a_lpCmdLine, int a_nCmdShow )
{
    Win32WindowRunnable* runnableWindow=new Win32WindowRunnable(a_hInstance, a_nCmdShow);
    IThread* threadWindow=new Win32Thread(runnableWindow);
    threadWindow->start();

    Scene1* scene=new Scene1(runnableWindow->waitForWindowHandle());
    IThread* threadRender=new Win32Thread(scene);
    threadRender->start();

    threadWindow->join();
    threadRender->pause();
    threadRender->kill();

    delete runnableWindow;
    return 0;
}

此处的完整源代码示例: https://github.com/TheWhiteAmbit/TheWhiteAmbit/blob/master/Win32App/Win32Main.cpp