继续WTL的学习,在第二部分的WTL Clock程序中,当定时器抛出WM_TIMER消息时,处理器OnTimer做了这样的工作:
LRESULT OnTimer(UINT_PTR nIDEvent) { if(1!=nIDEvent) SetMsgHandled(false); else { GetLocalTime(&m_stLastTime);//保存当前时间 RedrawWindow();//重绘窗口 } return 0; }
而用来更新时间的处理器OnPaint由WM_PAINT消息激发,显然WM_PAINT是由RedrawWindow()抛出的。
如果直接用PostMessage(WM_PAINT)来代替RedrwaWindow(),编译运行,发现窗口显示的时间没能如愿的每隔1Sec更新。为什么?明明手动抛出了WM_PAINT消息,按理说,它应该激发OnPaint函数来处理这个消息嘛。
F12查看RedrawWindow()代码,如下:
BOOL RedrawWindow( _In_opt_ LPCRECT lpRectUpdate = NULL, _In_opt_ HRGN hRgnUpdate = NULL, _In_ UINT flags = RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASE) throw() { ATLASSERT(::IsWindow(m_hWnd)); return ::RedrawWindow(m_hWnd, lpRectUpdate, hRgnUpdate, flags); }
很明显它是对Platform SDK函数RedrawWindow的简单封装,通过查看MSDN,容易了解到RedrawWindow函数干了两件事:
好了,了解RedrawWindow足够多了。我们发现,在WTL Clock程序中,我们只需要WM_PAINT消息,但试验证明PostMessage(WM_PAINT)起不了作用,它取代不了RedrawWindow函数。我们唯一漏掉的就是窗口的“Invalidate”(过期声明)。在MSDN中搜索Invalidate,刚好得到一个CWindowImpl::Invalidate()成员函数,而WTLClockView类刚好继承于CWindowImpl,所以在它内部可以直接调用Invalidate()的。
进一步查看Invalidate()的说明,有这样一句话:“Invalidates the entire client area. Passes NULL for the RECT parameter to the InvalidateRect Win32 function”,意思就是说函数将会声明整个客户区过期,而本例中view正是客户区。另外,它是对Win32函数的封装,查看InvalidateRect的说明,再链接到Invalidating the Client Area的说明,第一句话是:“The system is not the only source ofWM_PAINT messages. TheInvalidateRect orInvalidateRgn function can indirectly generate WM_PAINT messages for your windows.” 它告诉我们系统不是WM_PAINT消息的唯一来源,函数InvalidateRect或InvalidateRgn同样可以直接为你的窗口生成WM_PAINT消息。看来,Invalidate()已经完全满足本程序的要求了:声明客户区(本例中的view区域)“过期”,并抛出了我们需要的WM_PAINT消息,这将驱动OnPaint()对时间进行更新。所以OnTimer()函数可以修改为:
OnTimer(UINT_PTR nIDEvent) { if(1!=nIDEvent) SetMsgHandled(false); else { GetLocalTime(&m_stLastTime); Invalidate(); //PostMessage(WM_NCPAINT); //PostMessage(WM_ERASEBKGND); //PostMessage(WM_PAINT); //RedrawWindow(); } return 0; }
连PostMessage(WM_PAINT)都不用写。编译运行,结果正如所需要的那样。
P.S:
Windows规范是禁止用户主动抛WM_PAINT消息的,WM_PAINT消息应该由InvalidateRect等函数来激发。只要客户区存在无效区域,消息队列中就包含一条WM_PAINT消息,但该消息的优先级很低,只有在没有其它消息的情况下才被GetMessage函数抓取到。多条WM_PAINT消息通过综合无效区域合并为一条。API函数RedrawWindow比Invalidate功能强大得多,二者有很大的不同,但在本例中它们都用于无效化整个客户区使之重绘。区别在于本例中的RedrawWindow是立马进行绘制,而Invalidate引起的WM_PAINT何时被处理是不确定的。(2013-06-22)