【问题标题】:Paint invalid areas of a window using Win32 API and GDI使用 Win32 API 和 GDI 绘制窗口的无效区域
【发布时间】:2017-10-05 03:32:31
【问题描述】:

首先,我是新来的,所以你好!

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

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

.

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

这是一个例子:

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

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

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

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

当 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);

这是“预绘制”方法:

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

【问题讨论】:

  • 没有代码我们无法告诉你你做错了什么。
  • 改进不大。创建minimal reproducible example
  • 代码真的很长,拆分成多个文件,很难把它压缩成一个“最小”的例子,但我会试一试......
  • 您在正确的轨道上,但是您忘记了使 R2 曾经是的屏幕部分无效。否则屏幕的该部分将不会重绘,因此仍会显示 R2 的剩余部分。
  • 非常感谢您的评论。如果我理解正确,我将不得不重绘那部分背景以及 R1 和 R3。如果我设置背景图像怎么办?这是否意味着每次在窗口上移动某些东西时我都必须重新绘制它?或者我可以使用 BitBlt 绘制它的一部分?以及如何有效地检测重叠矩形?

标签: c++ winapi gdi


【解决方案1】:

我不知道您所说的“没有句柄的轻量级控件”是什么意思,但我猜它们是必须在父窗口的客户区绘制的简单 C++ 类(而不是真正的“控件”)。

“问题”是WM_PAINT 消息是低优先级消息,如果窗口在其客户区中有无效部分,则在应用程序退出之前发送。

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

我建议的实现是两种方法的组合,因为我已经使用了很多次并且效果很好:

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

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


另一种更简单的实现(仅使用“绘画”而不是“绘图”)可以是:

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

【讨论】:

  • 非常感谢,你帮了我很多!是的,这正是我的意思,我的控件只是普通的 C++ 类。我刚刚删除了 InvalidateRect。我以为我不应该在 WM_PAINT 消息之外绘图,但我并没有真正理解 WinAPI 绘画和绘图之间的区别,现在我明白了。但是我不知道如何重新绘制控件占用的旧区域。假设我的背景是图像,如何重新绘制无效区域?我想过使用 BitBlt,但它需要源 DC 的句柄,在我的情况下,它与目标 DC(背景)相同。
  • 并非如此,目标 DC 是窗口,而源 DC 是内存 DC(您已经在“渲染”方法中这样做了!)。另外,我似乎不明白为什么你在 BitBlt() 之前调用 predraw。控件应绘制在背景之上。不?无论如何,需要什么“预画”?不只是画背景然后控制(draw())就足够了吗?
  • 还有一些性能提示,位图、mem DC 和选择到其中,可以以某种方式缓存,即只执行一次(在某些初始化阶段)并在应用程序的生命周期内保留,而不是创建 (两者),选择等,最后为每个绘画请求取消选择和销毁。那么绘制请求只能调用 BitBlt()。
  • 在BitBlt“准备”稍后绘制的屏幕外DC之前绘制,如果我做对了,这就是双缓冲的工作原理。这里有解释:msdn.microsoft.com/en-us/library/ms969905.aspx 正如您在示例中看到的,所有“绘图”函数都在 BitBlt 之前被调用。我在屏幕外的 DC 上绘制我的控件,然后我将整个东西 BitBlt 到背景上。 Predraw 只是我创建的一种方法,用于分离绘图代码和用于迭代其他控件的 for 循环。
  • 你说得对,我应该缓存我的位图。但我想当窗口调整大小时,无论如何我都必须调用 CreateCompatibleBitmap,对吗?
猜你喜欢
  • 1970-01-01
  • 2011-08-29
  • 2014-03-04
  • 2019-03-16
  • 2019-11-03
  • 2010-10-01
  • 1970-01-01
  • 2013-04-09
  • 2021-08-03
相关资源
最近更新 更多