【漫漫开发路】窗口绘制的优化:只绘制可见的窗口

有这么一个需求
有时候你会想执行这样一个任务:当窗口没有被另一个窗口覆盖时 , 对这个窗口执行某个动作 , 例如更新一个状态窗口 。
任务栏如何更新时钟
检测一个窗口是否可见的最简单的方法 , 就是不对它进行检测 。 举个例子 , 下面是任务栏是如何更新它的时钟的:
1.计算距离下一次分钟更新还需要多长时间 。
2.使用步骤[1]中得到的结果来调用[SetTimer]创建一个定时器 。
3.当计时器超时 , 它会调用[InvalidateRect]并销毁定时器 。
4.[WM_PAINT]消息处理例程会绘制当前时间到任务栏的时钟控件上 , 然后重返步骤[1] 。
如果任务栏的时钟由于任务栏本身被设置为自动隐藏 , 或者它被其他窗口覆盖了而呈现出不可见的状态时 , Windows将不会向窗口发送[WM_PAINT]消息 , 因此 , 任务栏时钟将会进入空闲状态同时不会消耗任何CPU时间 。 根据以上的原理 , 我们可以对我们的程序应用相同的逻辑 。
在下面的代码中 , 我们的程序将会显示当前时间 。 同时 , 它还会将时间显示在窗口的标题栏 , 因此当窗口被覆盖或者最小化时 , 我们可以借助于观察任务栏来查看窗口的绘制行为 。
代码如下:
【漫漫开发路】窗口绘制的优化:只绘制可见的窗口
文章图片
下面的代码是一个定时器的回调函数 , 当我们希望更新窗口时 , 这个回调函数将会被调用 。 它仅仅是销毁定时器并将窗口绘制区域无效化 。 当窗口下一次恢复可见时 , 我们会得到一个[WM_PAINT]消息 。 (如果当窗口立即变得可见时 , 我们也会立即得到一个[WM_PAINT]消息)
【漫漫开发路】窗口绘制的优化:只绘制可见的窗口
文章图片
最后 , 我们在WM_PAINT消息处理例程中添加了一些代码 , 这样每次当我们绘制了一个非空的矩形时 , 重启定时器 。
下面是WM_PAINT消息处理例程:
【漫漫开发路】窗口绘制的优化:只绘制可见的窗口
文章图片
编译并运行这个程序 , 我们可以观察到时间会正常的更新 。 当你最小化窗口或者这个窗口被其他窗口覆盖时 , 时间更新停止了 。 当你拖动窗口到屏幕底部直到只有标题栏可见时 , 我们会观察到窗口时间的更新也会停止 。 为什么呢?因为WM_PAINT消息处理例程是用来绘制客户区的 , 在这种情况下 , 客户区已经不在屏幕上了 。
当你切换到其他用户或者锁定计算机时 , 窗口也会停止更新时间 , 虽然在这种情况下你会看不到任务栏来验证这个说法 。 但是 , 你可以使用计算机的扬声器来进行验证:在[PaintContent]中改为调用[MessageBeep]来发出声音 , 这样每当时间被重新绘制时 , 你都会听到一次扬声器的响声 。 当切换到其他用户或者锁定计算机时 , 我们不会听到这个声音 , 也即证明了之前我们的说法 。
仅绘制我们希望绘制的区域 , 而不是全部
这种无效区绘制的手法 , 同样可以被扩展到屏幕上只有一块区域需要绘制的情景:仅仅绘制你希望绘制的区域 , 然后仅当这个区域是待绘制区域的一部分的时候才重启定时器 , 而不是绘制整个客户区 。
下面是我们需要作出的代码改动:
【漫漫开发路】窗口绘制的优化:只绘制可见的窗口
文章图片
当定时器到期 , 我们仅仅更新上面定义的区域 , 而不是整个客户区(作为一个优化措施 , 我禁用了背景 , 后面我会提到我为什么这样做) 。
【漫漫开发路】窗口绘制的优化:只绘制可见的窗口
文章图片
为了更加清楚的显示这个目标绘制区域 , 我们高亮的绘制了这个区域并在里面显示时间 。 通过使用[ETO_OPAQUE]标志 , 我们同时绘制了前景和背景 。 因此 , 我们不需要再让它为我们擦除背景了 。
【漫漫开发路】窗口绘制的优化:只绘制可见的窗口
文章图片