VC窗口刷新InvalidateRect和…

本文详细解析了WM_PAINT消息的工作原理、无效区域的概念、如何使用InvalidateRect和InvalidateRgn函数引发重绘,以及BeginPaint和EndPaint函数在处理WM_PAINT消息中的作用。同时,阐述了UpdateWindow和RedrawWindow函数的区别,以及它们在窗口重绘中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先说说WM_PAINT这个重要的消息:

  The WM_PAINT message is generated by the system and should not be sent by an application.The system sends this message when there are no other messages in the application's message queue

  也就是说WM_PAINT消息是由系统产生,非要等应用程序的消息队列为空时才发送WM_PAINT消息,并且该消息不应该被程序(自己写代码用SendMessage)来发送。

  当调用UpdateWindow函数,或者是Window检测到窗口被覆盖的地方需要恢复的时候,比如,第一次创建窗口,改变了窗口的大小,最大化,最小化等等(其实这些事件发生时会调用UpdateWindow函数,由该函数发送WM_PAINT消息),它会向用户程序发送一个WM_PAINT消息。窗口过程收到WM_PAINT消息后,并不代表整个客户区都需要被刷新,有可能客户区被覆盖的区域只有一小块,这个区域叫做“无效区域”,程序只需要更新这个区域。与WM_TIMER消息类似,WM_PAINT消息也是一个低级别的消息,虽然它不会像WM_TIMER消息一样被丢弃,但Windows总是在消息循环空的时候才把WM_PAINT放入其中。

  无效区域的坐标并不附带在WM_PAINT消息的参数中,在程序中有其他方法可以获取。WM_PAINT消息只是通知程序有个区域需要更新而已,所以Windows也不会同时将两条WM_PAINT消息放入消息循环中。当Windows要放入一条WM_PAINT消息的时候,如果发现已经存在一个无效区域了,那么它只需要把新旧两个无效区域合并计算出一个无效区域就可以了,消息循环中还是只需要一条WM_PAINT消息。

  实际上,Windows为每个窗口维护一个“绘图信息结构”,无效区域的坐标就在其中,每当消息循环空的时候,如果Windows发现存在一个无效区域,就会放入一个WM_PAINT消息。那么“绘图信息结构”怎么获取呢?BeginPaint函数的第二个参数就是一个绘图信息结构的缓冲区地址,windows会在这里返回绘图信息结构,结构中包含了无效区域的位置和大小,绘图信息结构的定义如下:

typedef struct tagPAINTSTRUCT { // ps 
    HDC   hdc; 
    BOOL fErase; 
    RECT rcPaint; 
    BOOL fRestore; 
    BOOL fIncUpdate; 
    BYTE rgbReserved[32]; 
} PAINTSTRUCT; 

  其中hdc字段是窗口的设备环境句柄,rcPaint字段是一个RECT结构,它指定了无效区域矩形的对角顶点(如果开始有一个((0,0),(40,40)),现在又来一个((20,20),(60,30)),那么拼接后就是((0,0),(60,40))),fErase字段如果为非零值,表示Windows在发送WM_PAINT消息前已经使用背景色擦除了无效区域,后面3个字段是Windows内部使用的,应用程序不必去理会他们。

  在某些情况下,显示区域的一部分被临时覆盖,Windows试图保存一个显示区域,并在以后恢复它,但这不一定能成功。Windows可能发送WM_PAINT消息:Windows擦除覆盖了部分窗口的对话框或消息框;菜单下拉出来,然后被释放;显示工具提示消息。

  在某些情况下,Windows总是一定保存它所覆盖的显示区域,然后恢复它。这些情况是:鼠标光标穿越显示区域;图标拖过显示区域。

  有时候应用也需要能够主动引发窗口中的绘制操作,比如当窗口显示的数据改变的时候,这一般是通过InvalidateRect和 InvalidateRgn函数来完成的。InvalidateRect和InvalidateRgn把指定的区域加到窗口的Update Region中,当应用的消息队列没有其他消息时,如果窗口的Update Region不为空时,系统就会自动产生WM_PAINT消息。

  系统为什么不在调用Invalidate时发送WM_PAINT消息呢?又为什么非要等应用消息队列为空时才发送WM_PAINT消息呢?这是因为系统把在窗口中的绘制操作当作一种低优先级的操作,于是尽可能地推后做。不过这样也有利于提高绘制的效率:两个WM_PAINT消息之间通过InvalidateRect和InvaliateRgn使之失效的区域就会被累加起来,然后在一个WM_PAINT消息中一次得到更新,不仅能避免多次重复地更新同一区域,也优化了应用的更新操作。像这种通过InvalidateRect和InvalidateRgn来使窗口区域无效,依赖于系统在合适的时机发送WM_PAINT消息的机制实际上是一种异步工作方式,也就是说,在无效化窗口区域和发送WM_PAINT消息之间是有延迟的;有时候这种延迟并不是我们希望的,这时我们当然可以在无效化窗口区域后利用SendMessage 发送一条WM_PAINT消息来强制立即重画,但不如使用Windows GDI为我们提供的更方便和强大的函数:UpdateWindow和RedrawWindow。UpdateWindow会检查窗口的Update Region,当其不为空时才发送WM_PAINT消息;RedrawWindow则给我们更多的控制:是否重画非客户区和背景,是否总是发送WM_PAINT消息而不管Update Region是否为空等。

  BeginPaint

  在处理消息时,没有使用BeginPaint和EndPaint这对函数,结果导致其余的消息弹不出来,窗口拖动时,不停闪烁(其实那就是重绘)。后来还是在MSDN上找到了答案,现将原话贴出来。(在MSDN的The WM_PAINT Message标题中)

  BeginPaint sets the update region of a window to NULL. This clears the region, preventing it fromgenerating subsequent WM_PAINT messages. If an application processes a WM_PAINT message but does not call BeginPaint or otherwise clear the update region, the application continues to receive WM_PAINT messages as long as the region is not empty. In all cases, an application must clear the update region before returning from the WM_PAINT message. 
  BeginPaint函数的作用就是将窗口需要重绘的区域设置为空(也就是Update Region置空)。在正常情况下,我们接收到了WM_PAINT消息后,窗口的Update Region都是非空的(如果为空就不需要发送WM_PAINT消息了)。而当你响应这个消息的时候又不调用BeginPaint来清空,窗口的Update Region就一直是非空的,系统就会一直发送WM_PAINT消息。这样就形成了一个处理WM_PAINT消息的死循环。

  BeginPaint和WM_PAINT消息紧密相关。试一试在WM_PAINT处理函数中不写BeginPaint会怎样?程序会像进入了一个死循环一样达到惊人的CPU占用率,你会发现程序总在处理一个接一个的WM_PAINT消息。这是因为在通常情况下,当应用收到WM_PAINT消息时,窗口的Update Region都是非空的(如果为空就不需要发送WM_PAINT消息了),BeginPaint的一个作用就是把该Update Region置为空,这样如果不调用BeginPaint,窗口的Update Region就一直不为空,如前所述,系统就会一直发送WM_PAINT消息。

  BeginPaint和WM_ERASEBKGND消息也有关系。当窗口的Update Region被标志为需要擦除背景时,BeginPaint会发送WM_ERASEBKGND消息来重画背景,同时在其返回信息里有一个标志表明窗口背景是否被重画过。当我们用InvalidateRect和InvalidateRgn来把指定区域加到Update Region中时,可以设置该区域是否需要被擦除背景,这样下一个BeginPaint就知道是否需要发送WM_ERASEBKGND消息了。

  当然关于 WM_PAINT消息还有很多的知识需要学习。另外要注意的一点是,BeginPaint只能在WM_PAINT处理函数中使用,并且在调用了BeginPaint函数后,不要忘记了调用EndPaint函数,他们可是一对的。

  重画函数 InvalidateRect,Invalidate,UpdateWindow, RedrawWindow

  InvalidateRect(部分区域) 和Invalidate(整个窗口) 仅仅是用来设置无效区域,但是并不重绘窗口。

  UpdateWindow 检查窗口有无无效区域,如果有,则立即发送一个WM_PAINT 消息给窗口并立即重画。 

  RedrawWindow相当于先调用InvalidateRect,紧接着又调用UpdateWindow,此外RedrawWindow还提供了一些前两者没法做到的功能。

      如果不调用 InvalidateRect就调用 UpdateWindow,那么UpdateWindow什么都不做,因为没有无效区域。如果调用 InvalidateRect 后不调用UpdateWindow,则系统会自动在窗口消息队列为空的时候,系统自动发送一WM_PAINT消息。

 

<think>好的,用户想在VC6.0的MFC应用程序中实现点击按钮刷新窗口的功能。首先,我需要回忆一下MFC的基本结构消息处理机制。MFC中,按钮点击会发送BN_CLICKED消息,所以用户需要为按钮添加一个消息处理函数。 接下来,刷新窗口通常涉及到重绘。在MFC中,常用的方法是调用Invalidate()或InvalidateRect()来触发窗口的重绘。Invalidate()会使整个客户区无效,导致WM_PAINT消息被发送,而InvalidateRect()可以指定特定的区域。可能还需要调用UpdateWindow()来立即重绘,但一般情况下,Invalidate()后系统会自动处理WM_PAINT。 然后,用户需要在按钮的点击事件处理函数里调用这些方法。比如,在OnButtonClick函数中,先调用Invalidate(TRUE)来清空背景,或者FALSE保持背景,再可能需要UpdateWindow()。不过有时候直接Invalidate()就足够了,因为WM_PAINT会被加入消息队列,但优先级较低,所以可能需要UpdateWindow()来强制立即重绘。 另外,用户可能需要检查是否正确关联了按钮的消息处理函数。在VC6.0中,可以通过ClassWizard来添加BN_CLICKED的处理函数,确保函数被正确绑定,并且函数内部调用了Invalidate()等方法。 还有,窗口刷新可能涉及到OnPaint()函数的重写。用户需要确认在视图类或对话框类中,OnPaint()方法是否正确实现了绘制逻辑,这样当Invalidate()被调用时,OnPaint()会执行重绘操作。 可能的误区是用户忘记调用Invalidate(),或者错误地调用了其他函数。另外,如果窗口中有很多控件,可能需要使用RedrawWindow()函数,它结合了Invalidate()UpdateWindow()的功能,并且可以控制更多的重绘选项。 还需要考虑窗口刷新的性能问题,如果频繁调用Invalidate(),可能会导致界面闪烁。这时候可以尝试使用双缓冲技术,或者在Invalidate()时指定特定的区域来减少重绘的面积。 最后,建议用户按照步骤检查:添加按钮的消息处理函数,调用Invalidate()或相关函数,确保OnPaint()正确绘制。如果仍有问题,检查是否有其他代码阻塞了消息循环,或者绘制逻辑有错误。</think>在VC6.0 MFC中通过按钮点击刷新窗口,可通过以下步骤实现: 1. **创建消息映射函数** 打开ClassWizard (Ctrl+W) → 选择按钮的ID → 添加`BN_CLICKED`消息处理函数 2. **编写按钮点击事件代码** 在自动生成的`OnButtonX()`函数中插入窗口刷新代码: ```cpp void CMyDialog::OnButtonRefresh() { // 方法1:强制立即重绘 Invalidate(TRUE); // TRUE表示擦除背景 UpdateWindow(); // 立即发送WM_PAINT消息 // 方法2:区域刷新(更高效) CRect rect; GetClientRect(&rect); InvalidateRect(rect, TRUE); // 仅刷新指定区域 } ``` 3. **重写绘制逻辑** 在视图/对话框类中实现`OnPaint()`函数: ```cpp void CMyView::OnPaint() { CPaintDC dc(this); // 在此处添加自定义绘制代码 dc.TextOut(10, 10, _T("刷新后的内容")); } ``` **注意事项:** - `Invalidate()`标记需要重绘的区域,`UpdateWindow()`强制立即处理WM_PAINT - 使用`InvalidateRect()`可优化性能,仅重绘指定区域 - 频繁刷新时建议开启双缓冲防止闪烁: ```cpp // 在OnPaint()中: CDC memDC; memDC.CreateCompatibleDC(&dc); CBitmap bmp; bmp.CreateCompatibleBitmap(&dc, rect.Width(), rect.Height()); // 绘制到memDC后复制到屏幕DC ``` **调试技巧:** 1. 在`OnPaint()`中设置断点,确认是否被触发 2. 检查消息映射宏是否正确定义: ```cpp BEGIN_MESSAGE_MAP ON_BN_CLICKED(IDC_BUTTON1, OnButtonRefresh) END_MESSAGE_MAP ``` 3. 若使用对话框,确保未设置`WS_CLIPCHILDREN`样式 通过上述方法可实现安全可靠的窗口刷新操作,建议优先使用区域刷新(InvalidateRect)优化性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值