10

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

第一步,原始窗口。

第1步

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

第2步

第 3 步,调整窗口大小,并绘制多余的区域。

第 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

4 回答 4

9

在拖动操作期间更新窗口时,操作系统必须在扩展窗口区域中显示某些内容。如果您无法提供任何内容,那么它将显示背景,直到您提供为止。由于您没有指定任何背景,因此您会变黑。当然,您应该指定背景画笔?只需将以下内容添加到您的代码中,就可以使行为更加可口:

wcex.hbrBackground = GetStockBrush(DKGRAY_BRUSH);

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

如果你真的不能实现足够快的绘画来平滑拖动,那么我建议几个替代方案:

  1. 在拖动过程中禁用窗口更新。我确信这可以为单个窗口完成,但我不记得如何从头顶上做到这一点。
  2. 在调整大小/拖动处于活动状态时绘制假的东西,并将真实的绘画推迟到调整大小/拖动完成时。倾听WM_ENTERSIZEMOVEWM_EXITSIZEMOVE是关键。此 Microsoft 示例程序说明了如何执行此操作:https ://github.com/microsoft/Windows-classic-samples/blob/master/Samples/Win7Samples/winui/fulldrag/
于 2012-08-26T13:57:08.383 回答
3

使用WM_SIZING代替WM_SIZE并且不要忘记WM_ERASEBKGND.

于 2012-08-26T13:35:09.323 回答
2

如果您进入“系统属性”控制面板小程序,选择“高级”选项卡,然后单击“性能”组框中的“设置... ”,您将看到一个名为“拖动时显示窗口内容”的复选框设置。如果您取消选中该选项并尝试调整窗口大小,您将看到只有窗口框架移动,直到您完成拖动操作,然后窗口仅以新大小重新绘制一次。当我们拥有缓慢而笨拙的计算机时,这就是窗口大小的工作方式。

现在我们真的不想全局更改设置(您可以通过使用 SPI_SETDRAGFULLWINDOWS 调用SystemParametersInfo来完成,但不要真的这样做,因为您的用户不会喜欢它)。

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

全局“拖动时显示窗口内容”设置负责获取快速 WM_SIZE 消息。如果该设置关闭,则在一切结束时您只会收到一条 WM_SIZE 消息。

如果您的窗口很复杂,您可能在 WM_SIZE 处理程序中有布局代码计算内容(可能还有移动子窗口),在 WM_PAINT 处理程序中有很多绘画代码。如果所有代码都太慢(正如您的示例 320 毫秒延迟所暗示的那样),那么您将获得闪烁、生涩的体验。

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

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

解决方案:

  1. 当您看到 WM_ENTERSIZEMOVE 时设置一个标志。
  2. 更改您的 WM_SIZE 处理程序以检查标志,如果已设置,则不执行任何操作。
  3. 更改您的 WM_PAINT 处理程序以检查标志,并在已设置的情况下以纯色简单、快速地填充窗口。
  4. 当您看到 WM_EXITSIZEMOVE 时清除该标志,然后触发您的布局代码并使您的窗口无效,以便根据最终大小更新所有内容。

如果您的慢速窗口是子窗口而不是应用程序的顶级窗口,则您必须在顶级窗口获取 WM_ENTERSIZEMOVE 和 WM_EXITSIZEMOVE 时向子窗口发出信号,以实现步骤 1 和 4。

于 2014-01-30T22:54:48.430 回答
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

于 2016-07-14T22:21:12.267 回答