【问题标题】:Child Window Painting Reference子窗画参考
【发布时间】:2011-04-21 09:29:08
【问题描述】:

我从 MSDN 了解 windows 如何处理发送到特定窗口的 WM_PAINT 消息。

MSDN 似乎没有记录的一件事是窗口管理器决定哪些窗口应该接收 WM_PAINT 消息以及以什么顺序接收的实际过程。

据我了解(通过阅读 Raymond Chen 和 MSDN),没有与子窗口关联的无效区域 - 当子窗口无效时,父窗口上的相应区域也会无效。

它的 WM_PAINT 生成让我感到困惑......以及窗口无效区域被标记为有效的确切点(特别是 wrt 多线程) - 当涉及子窗口时,它变得特别有趣。 GetMessage 如何决定一组窗口(与无效区域相交的父窗口 + 子窗口)中的哪一个获得 WM_PAINT 消息,以及以什么顺序?面对 WS_CLIPSIBLINGS、WS_CLIPCHILDREN、WS_EX_COMPOSITED、WS_EX_TRANSPARENT 等等,这会如何改变?如果另一个线程在此过程中途使部分顶级窗口无效,会发生什么情况?

然后,在 Windows V6.0+ 上,DWM 如何挂钩到这个进程?


这是一个示例 C 程序,演示了使用 WS_EX_COMPOSITED 时发生的故障:-

#include <windows.h>
#include <windowsx.h>
INT delay = 50;
INT nPad = 32;

struct wnd_ctx {
  COLORREF base;
  char index;
};

HMODULE GetWindowModuleHandle(HWND hwnd){
  return (HMODULE)GetWindowLongPtr(hwnd,GWLP_HINSTANCE);
}

struct wnd_ctx* GetContext(HWND hWnd){
  struct wnd_ctx* pctx = (LPVOID)GetWindowLongPtr(hWnd,GWLP_USERDATA);
  if(!pctx)
  {
    pctx = malloc(sizeof(struct wnd_ctx));
    SetWindowLongPtr(hWnd,GWLP_USERDATA,(LONG_PTR)pctx);
  }
  return pctx;
}

LRESULT CALLBACK wnd_WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){
  PAINTSTRUCT ps;
  HDC hdc;
  RECT rect;
  HBRUSH hbr;
  struct wnd_ctx* self;
  switch (message){
  case WM_LBUTTONUP:
    GetClientRect(hWnd,&rect);
    rect.top += nPad;
    rect.bottom -= nPad;
    rect.left += nPad;
    rect.right -= nPad;
    InvalidateRect(hWnd,&rect,TRUE);
    return 0;
  case WM_ERASEBKGND:
    DefWindowProc(hWnd, message, wParam, lParam);
    Sleep(delay);
    return 0;
  case WM_PAINT:
    hdc = BeginPaint(hWnd, &ps);
    if(self = GetContext(hWnd)){
      hbr = CreateSolidBrush(self->base + ((self->index++ <<5) & 0x7f));
      GetClientRect(hWnd,&rect);
      FillRect(hdc,&rect,hbr);
      DeleteObject(hbr);
    }
    EndPaint(hWnd, &ps);
    Sleep(delay);
    break;
  case WM_DESTROY:
    PostQuitMessage(0);
    break;
  default:
    return DefWindowProc(hWnd, message, wParam, lParam);
  }
  return 0;
}

ATOM CreateClass(HINSTANCE hInstance,LPCTSTR strClass,COLORREF brush,HICON hIcon){
  WNDCLASSEX wcex;
  wcex.cbSize = sizeof(WNDCLASSEX);
  wcex.style            = CS_HREDRAW|CS_VREDRAW;
  wcex.lpfnWndProc  = wnd_WndProc;
  wcex.cbClsExtra       = 0;
  wcex.cbWndExtra       = 0;
  wcex.hInstance        = hInstance;
  wcex.hIcon            = hIcon;
  wcex.hCursor      = LoadCursor(NULL, IDC_ARROW);
  wcex.hbrBackground    = CreateSolidBrush(brush);
  wcex.lpszMenuName = 0;
  wcex.lpszClassName    = strClass;
  wcex.hIconSm      = hIcon;
  return RegisterClassEx(&wcex);
}

BOOL CreateWindows(HINSTANCE hInstance, INT nCmdShow, LPCTSTR strTitle){
  HWND hWnd, hChild;
  ATOM atm;
  RECT rect;
  DWORD dwStyleEx, dwStyle, dwChildStyle, dwChildStyleEx;
  struct  wnd_ctx* pctx;
  dwStyleEx = WS_EX_COMPOSITED;
  dwStyle = WS_OVERLAPPEDWINDOW;//|WS_CLIPCHILDREN;
  dwChildStyle = WS_CHILD|WS_VISIBLE;//|WS_CLIPSIBLINGS;
  atm = CreateClass( hInstance, TEXT("APPWINDOW"), RGB(0x80,0x80,0x80), LoadIcon(NULL,IDI_APPLICATION));
  hWnd = CreateWindowEx(dwStyleEx,(LPCTSTR)atm, strTitle, dwStyle,
      CW_USEDEFAULT, 0, 256, 256, NULL, NULL, hInstance, NULL);
   pctx = GetContext(hWnd);
   pctx->base = RGB(0x00,0xff,0xff);
   pctx->index=0;
   atm = CreateClass(hInstance,TEXT("CONTROL1"),RGB(0x00,0x80,0x40),0);
   GetClientRect(hWnd,&rect);
   hChild = CreateWindowEx(0L,(LPCTSTR)atm, TEXT("Top"), dwChildStyle,
     rect.right/2, rect.top, rect.right/2, rect.bottom, hWnd, NULL, hInstance, NULL);
   pctx = GetContext(hChild);
   pctx->base = RGB(0x00,0xff,0x80);
   pctx->index=0;
   atm = CreateClass(hInstance,TEXT("CONTROL2"),RGB(0x00,0x40,0x80),0);
   hChild = CreateWindowEx(0L,(LPCTSTR)atm, TEXT("Bottom"), dwChildStyle,
     rect.left, rect.bottom/2, rect.right , rect.bottom/2, hWnd, NULL, hInstance, NULL);
   pctx = GetContext(hChild);
   pctx->base = RGB(0x00,0x80,0xff);
   pctx->index=0;
   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);
   return TRUE;
 }

int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPTSTR lpCmdLine,int nCmdShow){
  MSG msg;
  CreateWindows(hInstance,nCmdShow,TEXT("Test Child Painting"));
  while(GetMessage(&msg, NULL, 0, 0)>0){
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
  return (int) msg.wParam;
}

【问题讨论】:

  • 我想知道的一件事是为什么您认为 WS_EX_COMPOSITED 在 DWM 组合处于活动状态时不起作用。
  • 嗯,我怀疑它没有被记录,因为它不应该是相关的。他们不想承诺以任何特定顺序发送WM_PAINT 消息,这是一个非常不寻常的情况,应用程序会关心它们被发送的顺序。实现应该是透明的。您是纯粹出于好奇而询问,还是试图解决某些特定问题?就 DWM 而言,它根本不会与流程挂钩。每个顶级窗口都会自动分层并重定向到屏幕外缓冲区。然后这个缓冲区被blitted到屏幕上。
  • 同意。这是由窗口管理器完成的,这是一段特别没有记录的代码。有很多轶事证据表明它是如何工作的,但这不是你想要的。将这个问题与您要解决的特定问题钉在墙上,这样我们就不必写一本关于未记录代码的书。
  • 好吧,作为对 David 和 Hans 的回答,我想知道我的测试应用程序,使用 WS_EX_COMPOSITED,调整大小时闪烁,启用 DWM。
  • @Chris 我相信你。只是它解决了我的问题。显然,我们在代码的其他地方做了不同的事情。

标签: c windows winapi


【解决方案1】:

传统的模型是什么都不记得(因为内存很昂贵),因此每当窗口被覆盖时,内容就会被遗忘,并根据 WM_PAINT 重新绘制。

但你知道这一点。

从应用的角度来看,DWM 对这个模型的主要改变不是绘画而是失效,即要求绘画。区域在被其他窗口覆盖时不会(必然)无效,因为 DWM 会记住窗口的外观,因此不需要询问应用程序“提醒我您想再次显示什么?”。

如果您自己显式或隐式(例如通过 SetWindowText)使某个区域无效,那么它仍然会获得 WM_PAINT。

绘制父对象时,子对象可能会被剪裁,也可能不会被剪裁,具体取决于是否设置。我相信绘画是从后到前完成的,以允许子控件(例如)在他们自己的矩形之外绘制 3D 边框,就像在 Microsoft Word 6 中一样,如果你还记得那么远的话!

据我所知,这没有记录在案,并且由于您引用了 Raymond Chen,您会知道他会警告您不要依赖 WM_PAINT 消息的顺序。

【讨论】:

    猜你喜欢
    • 2017-07-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多