invalidaterect
invalidaterect是Windows操作系统提供的一个图形界面编程接口(API)函数,声明于winuser.h头文件,由User32.dll实现,作用是把指定窗口客户区中的一块矩形区域标记为无效。被标记的区域会被加入该窗口的更新区域(updatereregion),系统随后在消息队列空闲时投递WM_PAINT消息,让窗口过程只重绘这块区域。它不直接触发绘制,而是把多次失效请求累积合并,是Windows延迟重绘机制的核心入口之一,广泛用于GDI界面和自绘控件的局部刷新。
定义
invalidaterect属于Win32 API中窗口管理相关的一组函数,声明在winuser.h中(通常经Windows.h引入),由User32.dll导出,调用时需要链接User32.lib[1][2]。它的语义是把一个矩形加入目标窗口的更新区域,而更新区域表示窗口客户区中需要重新绘制的部分[1]。
该函数有3个参数:hWnd为更新区域发生变化的窗口句柄,lpRect指向以客户区坐标描述待加入矩形的一个RECT结构,bErase指定处理更新区域时是否擦除背景[3]。返回值为非零表示成功,为零表示失败,失败原因可通过GetLastError查询[2]。hWnd不可为NULL,官方文档明确不推荐把该参数置空,因为那会使系统中所有窗口而不只是本程序的窗口失效并重绘,并在函数返回前发出WM_ERASEBKGND和WM_NCPAINT消息[1]。
原理
Windows为每个窗口维护一个更新区域。应用程序调用invalidaterect或同族的InvalidateRgn时,系统把给出的矩形或区域与系统、程序此前已加入的内容合并,而不是替换原有内容。失效区域会一直累积,直到下一次WM_PAINT消息处理该区域,或者被ValidateRect、ValidateRgn验证掉为止[1]。
这类函数本身并不生成WM_PAINT消息。系统在其后窗口处理消息队列中其他消息的过程中,把多次失效请求一并积累,使窗口一次处理完所有变化,而不是零碎地逐步更新画面。当某窗口的更新区域非空、且该窗口在应用队列中没有其他消息时,系统才向它发送WM_PAINT[1]。
窗口过程在WM_PAINT中调用BeginPaint时,系统把设备上下文的剪裁区域设为待更新部分与用户可见部分的交集,绘制区外的输出会被裁掉、不显示在屏幕上;同时BeginPaint把窗口更新区域清空,避免重复产生WM_PAINT,并填写PAINTSTRUCT中的rcPaint等信息供优化绘制[4]。若bErase对更新区域的任何一部分为TRUE,则整个区域而不仅是指定部分的背景都会被擦除[1]。
可以用下面的流程概括一次失效请求的生命周期:
flowchart LR
A["调用 invalidaterect"] --> B["矩形并入更新区域"]
B --> C{"队列中已有 WM_PAINT?"}
C -- "是" --> D["合并到现有待处理区域"]
C -- "否" --> E["推迟投递 WM_PAINT"]
D --> F["消息循环处理 WM_PAINT"]
E --> F
F --> G["BeginPaint: 剪裁并清空更新区域"]
G --> H["EndPaint 结束绘制"]
发展历程
该函数属于Win32 API中较早成型的绘图接口,其文档要求中给出的最低支持客户端为Windows 2000 Professional,最低支持服务器为Windows 2000 Server,均限于桌面应用;API集一项则标注为windows 8引入的ext-ms-win-ntuser-draw-l1-1-0[2]。这说明接口本身稳定,后续变化主要体现为API集的归类调整。
同一套语义也延伸到了嵌入式平台:Windows CE 1.0及以后版本、Windows Mobile 5.0及以后版本提供同名函数,头文件为winuser.h,库为coredll.lib与Winmgr.lib[5]。在Windows Embedded CE中还有内部版本InvalidateRect_I,用于把矩形加入指定窗口的更新区域,其文档标注的适用系统为Windows CE .NET 4.0及以后[6]。
在面向对象的封装层中,该接口以方法形式出现,例如MFC的CWnd类提供InvalidateRect成员函数,将矩形加入CWnd的更新区域,并在下一次WM_PAINT时连同更新区域中的其他部分一起标记为待绘制[7]。
应用
该函数最常见的用途是局部重绘:当窗口只有一小块内容发生变化时,只把这一块标为失效,从而让后续绘制只覆盖必要范围,避免整窗重画带来的开销。在定时刷新、动画等场景中,程序常在每次数据更新后对变化区域调用该函数,由系统合并后再统一绘制[8]。
在自绘控件与游戏界面中,该函数常与抑制背景擦除、内存位图配合使用:先把内容画到兼容内存设备上下文,再用BitBlt把结果复制到窗口设备上下文,同时处理WM_ERASEBKGND以避免背景被反复清除,以此减少重绘时的画面跳动[8]。有实践建议在需要即时更新时把该函数与UpdateWindow或RedrawWindow配合:UpdateWindow在返回前立即发送WM_PAINT,而RedrawWindow不加RDW_UPDATENOW标志时只是把WM_PAINT放入队列[9]。
与此配套的还有验证方向的接口:ValidateRect与ValidateRgn把指定矩形或区域从更新区域中移除,通常用于窗口在收到WM_PAINT之前已经自行更新了屏幕某一部分的场合。
局限
该函数只登记待重绘范围,并不立即产生画面更新,也不保证何时绘制;实际的WM_PAINT要等到窗口更新区域非空且队列中没有其他消息时才被发送[1]。因此需要同步刷新时,单靠它无法满足,须额外调用UpdateWindow一类接口[9]。
它描述的失效范围是矩形,不适合形状复杂的区域;非矩形范围需要改用InvalidateRgn之类的区域接口。同时,失效区域会合并累积,系统内部以包围整个无效区域的最小矩形来描述,因此多个分散的小矩形失效后,实际重绘范围可能大于直觉预期[10]。
bErase的粒度也较粗:只要更新区域中任一部分要求擦除背景,整个区域的背景都会被擦除,无法只擦指定部分[1]。此外,若在WM_PAINT处理中不调用BeginPaint或以其他方式清空更新区域,窗口会因区域持续非空而不断收到WM_PAINT消息[4]。把hWnd传为NULL会使系统中所有窗口失效并触发额外消息,文档明确不推荐这种用法[1]。
参见
-
InvalidateRgn —— 以区域而非矩形为单位把范围加入窗口更新区域的同类函数
-
ValidateRect —— 从更新区域中移除指定矩形,与该函数作用相反
-
WM_PAINT —— 更新区域非空且队列空闲时系统发出的重绘消息
-
BeginPaint —— 在WM_PAINT处理中获取设备上下文并清空更新区域的函数
-
RedrawWindow —— 可通过标志组合一次性完成失效与立即更新的函数
参考资料
- UID: NF:winuser [引用日期2026-10-29]
- InvalidateRect 函数 (winuser.h) [引用日期2026-10-29]
- nf-winuser-invalidaterect [引用日期2026-10-29]
- WM_PAINT消息 [引用日期2026-10-29]
- InvalidateRect [引用日期2026-10-29]
- ms906118(v=msdn [引用日期2026-10-29]
- CWnd::InvalidateRect [引用日期2026-10-29]
- 问无闪烁的绘图EN [引用日期2026-10-29]
- 问InvalidateRect/UpdateWindow失败,但RedrawWindow工作正常,为什么?EN [引用日期2026-10-29]
- 无效矩形 [引用日期2026-10-29]