只有在使用MFC框架时才有UI线程和工作者线程之分。UI线程与工作者线程的区别是操作系统为UI线程创建并维护了一个消息队列。
其实线程在创建时(无论是API还是MFC),都是工作者线程。当线程调用发送消息或提取消息或图形用户界面相关的函数时,系统才为其创建一个消息队列和THREADINFO结构,这时的线程才称为UI线程。
VC开发的控制台程序的主线程是工作线程,其他程序的主线程为UI线程。_beginthreadex/CreateThread等函数创建的线程默认为工作线程,AfxBeginThread可以根据参数创建工作者线程和UI线程。
1 从用户输入到系统消息队列
操作系统会监视计算机的键盘和鼠标等输入设备,为每一个输入事件生成一个消息,将消息统一临时放到“系统消息队列”内。其中消息的窗口句柄由系统根据鼠标或光标所在区域计算出的。
2 从系统消息队列到线程消息队列
系统有专门的线程负责从系统消息队列中取出消息,根据消息的目标对象(窗口句柄),将消息投递到创建它的UI线程对应的消息队列中。每个UI线程有且仅有一个消息队列。
3 UI线程处理消息
UI线程启动一个消息循环(Message Loop),每次从本线程所对应的消息队列中取出一条消息,然后根据消息所包容的信息,将其转发给特定的窗体对象,此窗体对象所对应的“窗体过程”函数被调用以处理这些消息。
MSG msg; // 代表一条消息
BOOL bRet;
// 从UI线程消息队列中取出一条消息
while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
{
if (bRet == -1)
{
// 错误处理代码,通常是直接退出程序
}
else
{
TranslateMessage(&msg); // 转换消息格式
DispatchMessage(&msg); // 分发消息给相应的窗体
}
}
GetMessage()等到有消息才返回,peekmessage()则马上返回消息或空值;GetMessage()获得消息后从队列中删除(WM_PAINT除外),peekmessage()则根据参数PM_NOREMOVE或PM_REMOVE来决定要不要删除。
TranslateMessage()函数主要用于将WM_KEYDOWN和WM_KEYUP消息转换WM_CHAR消息。所以如果要截获WM_KEYDOWN和WM_KEYUP消息,需要重载窗体类的PreTranslateMessage()函数进行分析。
DispatchMessage()函数根据取出的消息中所包含的窗体句柄,将这一消息转发给引此句柄所对应的窗体对象。负责响应消息的函数称为窗体过程(Window Procedure)。如果需要自定义处理,则重载窗体类的DefWindowProc()函数。窗体过程是一个函数,每个窗体一个,形式如下:
LRESULT CALLBACK MainWndProc(……)
{
//……
switch (uMsg) // 依据消息标识符进行分类处理
{
case WM_CREATE:
// 初始化窗体.
return 0;
case WM_PAINT:
// 绘制窗体
return 0;
//
// 处理其他消息
//
default:
// 转去调用系统默认的消息处理函数
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
//……
}