窗口过程(winproc), 是程序处理消息的地方, 但是不是由程序自己调用, 而是由Windows调用通过检索窗口注册类得到窗口过程函数指针(wndclass.lpfnWndProc = WndProc)。 Mfc对此的封装可谓是两万五千里长征。要了解mfc的处理, 先得弄清楚sdk吧。
消息能够被分为「队列化的」和「非队列化的」。简单讲非队列化消息直接发送给窗口过程,而队列化消息先被送进程序消息列队,程序通过消息循环检索消息,然后调用dispatchmessage , 一旦调用dispatchmessage, 程序进入windows管态, 由windows调用适当的窗口过程。
队列化消息基本上是使用者输入的结果,以击键(如WM_KEYDOWN和WM_KEYUP消息)、击键产生的字符(WM_CHAR)、鼠标移动(WM_MOUSEMOVE)和鼠标按钮(WM_LBUTTONDOWN)的形式给出。队列化消息还包含时钟消息(WM_TIMER)、更新消息(WM_PAINT)和退出消息(WM_QUIT)。
非队列化消息则是其它消息。在许多情况下,非队列化消息来自调用特定的Windows函数。如CreateWindow时发送 WM_CREATE.
消息循环如下:
While (GetMessage(&msg, NULL, 0, 0)
{
TranslateMessage (&msg) ; // 键盘鼠标消息转换
DispatchMessage (&msg) ;
}
NULL – 接收该线程内的所有消息。
如果函数取得WM_QUIT之外的其他消息,返回非零值。如果函数取得WM_QUIT消息,返回值是零。即接收到WM_QUIT, 程序退出循环, 程序就此了结一生。
调用GetMessage程序会挂起,即交出时间片,由windows接管, 直到有消息过来。
值得注意的是另一个函数PeekMessage. 先看代码
while (TRUE)
{
if (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) PM_REMOVE删除消息
{
if (msg.message == WM_QUIT)
break ;
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
else
{
// 完成某些工作的其它行程序
}
}
.
调用PeekMessage将立刻返回, 如果列队中有消息返回TRUE, 否则FALSE。所以检查WM_QUIT是必须的. 如果列队中没有消息, 不像GetMessage, 程序可以稍稍运行一段时间, 这就是游戏渲染的时机了。
无论GetMessage, 还是PeekMessage, 多是无法删除WM_PAINT消息. 那不是列队中只要有一个WM_PAINT, 程序就无法退出了吗? 实际上从队列中删除WM_PAINT消息的唯一方法是令窗口显示区域的失效区域变得有效,这可以用ValidateRect和ValidateRgn或者BeginPaint和EndPaint对来完成. MFC 对此进行了封装, 以至于我们看不到BeginPaint. 有兴趣可以去看一下CPaintDC的源码。
而InvalidateRect 使显示的一个矩形区域失效, 并可能会产生WM_PAINT消息. 为什么是可能会呢? 因为绘制操作是一种低优先级操作, 尽可能的拖后. 程序列队不为空时, InvalidateRect调用将不会产生WM_PAINT, 而是累加失效矩形区域, 等到空时,产生WM_PAINT消息, 一次性完成绘制操作。 所以计时器中调用InvalidateRect 有可能不能如你所愿。解决的方法是 InvalidateRect 后, 调用一个Send一个WM_PAINT(SendMessage, 过会儿讲)或者调用更方便和强大的函数的UpdateWindow和RedrawWindow,UpdateWindow会检查窗口的失效矩形区域,当其不为空时才发送WM_PAINT消息。
WM_TIMER 消息低优先级, 列对中只能有一个, 所以不要祈求过高的精度。WM_TIMER来绘制动画, 我认为不好。
SendMessage 如果指定的窗口通过调用线程被创建,则窗口过程作为子程序被立即调用。如果指定的窗口通过调用不同线程被创建,则系统切换到该线程并调用适当的窗口过程。线程间的消息只有在接收线程执行消息检索代码时才被处理。发送线程将被阻塞到接收线程处理完消息为止。非列队的。
PostMessage只把消息放入队列,不管程序是否处理都返回,然后继续执行,这是个异步消息投放函数。
总之, 列队内消息, 排序进行处理。 非列队的立刻处理掉。
就此打住!