有这么一个需求
有时候你会想执行这样一个任务:当窗口没有被另一个窗口覆盖时,对这个窗口执行某个动作,例如更新一个状态窗口。
任务栏如何更新时钟
检测一个窗口是否可见的最简单的方法,就是不对它进行检测。举个例子,下面是任务栏是如何更新它的时钟的:
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]标志,我们同时绘制了前景和背景。因此,我们不需要再让它为我们擦除背景了。
最后,在WM_PAINT消息处理例程中的代码需要检查目标绘制区的可见性,而不是使用整个客户区。
运行这个程序的时候,我们可以使用一些方法来覆盖窗口或者禁止高亮区域的绘制。我们可以观察到:一旦窗口被覆盖,标题栏就停止更新了。
总结
就像我上面说过的,这项技术对于大多数应用程序来说已经足够了。但是还有另外一种更加复杂(也更加昂贵)的技术,我将会在下周为大家揭秘。