使用 Win32 API 和 GDI 绘制 window 的无效区域

Paint invalid areas of a window using Win32 API and GDI

首先,我是新来的,世界您好!

我正在开发一个小型轻量级控件库。每个控件都是名为 "GraphicElement" 的 class 的实例,并且没有句柄。我创建了一个事件调度程序,它按预期工作,但我在绘制控件时遇到了困难。它们被储存在一棵树上,我在穿过这棵树时画它们。我还使用后台缓冲区来确保 window 的内容不会闪烁。

一切正常,但是当我移动其中一个控件时,会发生这种情况:

.

当然,我可以使整个 window 无效并重新绘制,这在理论上解决了我的问题,但我想避免这样做,尤其是在不必要和出于性能原因时。

这是一个例子:

我想移动 R2,然后重新绘制空白点(我指的是 R2 的旧位置)而不重新绘制 R4 和 R5(可能还有许多其他位置)。

如何重绘"disappeared"的背景部分?我是否必须重新绘制整个背景以及我所有的控件? 我不会在这里 post 我所有的代码,因为它很长,而且它还处理事件等其他事情,但正如我之前所说,我在遍历树时绘制控件,因此其中没有什么疯狂的地方。

预先感谢您的帮助,如果我不清楚,请见谅。

编辑:这是一些代码,但正如我之前所说,如果我使 window 的客户区无效,它就像一个魅力,但我想避免这样做。

当 Windows 发送 WM_PAINT 消息时调用此方法 ("render") :

m_hdcMem = CreateCompatibleDC(hdc);
m_bmpMem = CreateCompatibleBitmap(hdc, m_rect.right - m_rect.left, m_rect.bottom - m_rect.top);
m_bmpOld = (HBITMAP)SelectObject(m_hdcMem, m_bmpMem);
m_background->predraw(m_hdcMem); // draws the client area, which is an instance of GraphicElement
BitBlt(hdc, m_rect.left, m_rect.top, m_rect.right - m_rect.left, m_rect.bottom - m_rect.top, m_hdcMem, 0, 0, SRCCOPY);
SelectObject(m_hdcMem, m_bmpOld);
DeleteObject(m_bmpMem);
DeleteDC(m_hdcMem);

这是方法 "predraw" :

draw(hdc); // draws the current control

for (std::vector<GraphicElement*>::iterator it = m_children.begin(); it != m_children.end(); ++it)
        (*it)->predraw(hdc); // "predraws" the other controls

最后,当控件调整大小或移动时,使用此函数使其区域无效:

InvalidateRect(m_parentHwnd, lpRect, FALSE); // If I invalidate the whole window, my code works perfectly, but I'd like to know how to paint parts of my window

我不知道你所说的 "lightweight control that doesn't have a handle" 是什么意思,但我猜它们是简单的 C++ 类(而不是真正的“控件”),必须在父级上绘制 window的客户区。

"problem" 是 WM_PAINT 消息是低优先级消息,如果 window 在其客户区有无效部分,就在应用程序让步之前发送。

您应该首先阅读的文档是:Painting and Drawing

我建议的实现是两种方法的组合:

  • 处理 WM_PAINT 消息(和 BeginPaint()/EndPaint() 函数)以绘制整个客户端 window(或其中的一部分,使用 [= PAINTSTRUCT 结构的 14=] 成员,如果需要更多 "optimized" 实现)。请注意,WM_PAINT 消息的发送可能是由于移动、调整大小、将 window 置于前景或显示之前被另一部分遮挡的 window 的一部分,即由于用户操作,除了以编程方式使其全部或部分无效之外。因此,为了响应此消息,您应该在当前位置绘制父级 window 和所有控件。
  • 使用 GetDC()/ReleaseDC() 函数仅绘制受添加、删除或移动控件等操作影响的 window 部分。这种绘图立即发生,不等待 WM_PAINT 消息发送。您应该填充控件先前占据的区域并在其新位置绘制控件。您不应使客户区的任何部分无效,因为这会导致发送另一条 WM_PAINT 消息。
  • 控制绘图函数应该采用 HDC 参数(在任何其他需要的参数中),以便两种绘图方法都可以使用(BeginPaint()GetDC() 函数)。

我已经使用这种技术制作图像处理应用程序(例如,让用户选择图像的一部分并 drawing/restoring 选择矩形)和无人值守的监视器应用程序。


另一种更简单的实现方式(仅使用 "Painting" 而不是 "Drawing")可以是:

  • 调整控件大小或移动控件时,仅使控件占用的旧区域和新区域无效。
  • WM_PAINT消息的处理大体同上,但要修改为只填充PAINTSTRUCT结构体rcPaint成员中的矩形,只绘制与上述矩形相交的控件。