程序线程
跟ATL一样,WTL程序也需要一个_Module全局变量来保存全局数据,方便应用级代码访问.在WTL中,这个变量是CAppModule或CServerAppModule的实例,后者在程序同时作为一个COM服务器时用到.每个应用程序具有一个或者多个UI线程.WTL使用两种方式来管理这些线程.
如果应用程序只有一个UI线程(除了多线程SDI以外,其他程序类型默认只有一个UI线程),线程调用全局函数run():
int Run(LPTSTR /*lpstrCmdLine*/ = NULL, int nCmdShow = SW_SHOWDEFAULT)
{
CMessageLoop theLoop;
_Module.AddMessageLoop(&theLoop);
CMainFrame wndMain;
if (wndMain.CreateEx() == NULL)
{
ATLTRACE(_T("Main window creation failed!/n"));
return 0;
}
wndMain.ShowWindow(nCmdShow);
int nRet = theLoop.Run();
_Module.RemoveMessageLoop();
return nRet;
}
线程的消息循环包含在CMessageLoop内部.函数创建了一个CMessageLoop实例, 把它放入全局的消息循环映射(message loop map)数组. 以线程ID为索引,线程中运行的其他的代码可以访问到这个实例. 消息循环对象包含了message filter和idle handler. 运行在这个UI线程的UI元件(UI element)可以有它自己的idle handler,在线程的消息队列为空时运行【译注:通过CMessageLoop::AddIdleHandler()把这个UI元件加入到CMessageLoop的idle handler 数组中】. CMessageLoop::Run()包含了UI线程的主消息映射(main message map).下边是它的伪代码:
MSG m_msg;
int CMessageLoop::Run()
{
for (;;)
{
while (!::PeekMessage(&m_msg, NULL, 0, 0, PM_NOREMOVE))
DoIdleHandlers();
bRet = ::GetMessage(&m_msg, NULL, 0, 0);
if(bRet == -1)
continue;
else if(!bRet)
break;
if (!DoMessageFilters(&m_msg))
{
::TranslateMessage(&m_msg);
::DispatchMessage(&m_msg);
}
}
return (int)m_msg.wParam;
}
可以看到,这个函数推动着消息队列. 没有消息时, 运行注册到线程的idle hander. 如果在队列中检测到消息,把它取出来,传给每个message filter. 如果消息没有被这些函数处理,它将按照通常的方式,发送到目标窗口.
如果程序有超过一个的UI线程,可以用WTL的线程管理器,多线程SDI就是这样做的. 主线程作为一个管理者线程,它会为每个新窗口创建一个新的新线程. 主要流程如下:
int nRet = m_dwCount;
DWORD dwRet;
while(m_dwCount > 0)
{
dwRet = ::MsgWaitForMultipleObjects(m_dwCount, m_arrThreadHandles,
FALSE, INFINITE, QS_ALLINPUT);
if(dwRet >= WAIT_OBJECT_0 && dwRet <= (WAIT_OBJECT_0 + m_dwCount - 1))
RemoveThread(dwRet - WAIT_OBJECT_0);
else if(dwRet == (WAIT_OBJECT_0 + m_dwCount))
{
::GetMessage(&msg, NULL, 0, 0);
if(msg.message == WM_USER)
AddThread(_T(""), SW_SHOWNORMAL);
}
}
那些线程句柄放在一个数组中. 线程通过AddThread()加入到数组(同时启动线程), RemoveThread()从数组移走. wait语句在两种情况下会被打断: 线程死亡(将线程从数组中移出) 或线程收到了WM_USER消息(一个线程在一个新线程里新建了一个窗口). 线程管理者为程序中的一个类,因此可以在循环中加入自己的message handler, 比如,当程序有不止一种窗口类型时. 创建一个新的窗口非常简单,只需在任意一个窗口中调用:
::PostThreadMessage(_Module.m_dwMainThreadID, WM_USER, 0, 0L);
这个循环会一直运行下去,直到所有的UI线程都关闭了. UI线程具有一个thread procedure,它跟单UI线程的Run()方法一样.不过,由于线程管理者使用了MsgWaitForMultipleObjects(), 这意味者最多只能有MAXIMUM_WAIT_OBJECTS-1个UI线程,这也意味着最多只能创建63个窗口.
框架
WTL实际上是两类窗口: 框架窗口和视图窗口. 正如名字所暗示的那样, 框架窗口为窗口提供标题栏(caption bar)和边框,你的代码用它来处理工具条(tool bar)和菜单项命令.你看到的程序窗口实际上是视图窗口, 视图覆盖了框架窗口的客户区.客户区是指框架窗口没有被诸如状态条,工具条之类的修饰部件所遮挡的部分.
线程会创建主框架窗口的一个实例,创建视图的工作由主框架窗口的WM_CREATE消息处理函数完成. 对于SDI程序来说,这个过程很简单. 把视图类的一个实例作为主框架类的一个成员,调用视图类的Create()方法即可.MDI程序稍微有些不同, MDI主框架窗口通过CMDIFrameWindowImpl<>::CreateMDIClient()建立一个名为MDICLIENT的窗口. 这个客户窗口将CMDIChildWindowImpl<>窗口当做它的子窗口,子窗口有一个视图.这也反映了这么一个事实,MDI程序可以具有零个或者多个子窗口,每个都有边框和标题栏.
框架窗口的OnCreate()很有意思,让我看看:
LRESULT OnCreate(UINT, WPARAM, LPARAM, BOOL&)
{
// create command bar window
HWND hWndCmdBar = m_CmdBar.Create(m_hWnd, rcDefault,
NULL, ATL_SIMPLE_CMDBAR_PANE_STYLE);
// attach menu
m_CmdBar.AttachMenu(GetMenu());
// load command bar images
m_CmdBar.LoadImages(IDR_MAINFRAME);
// remove old menu
SetMenu(NULL);
HWND hWndToolBar = CreateSimpleToolBarCtrl(m_hWnd, IDR_MAINFRAME,
FALSE, ATL_SIMPLE_TOOLBAR_PANE_STYLE);
CreateSimpleReBar(ATL_SIMPLE_REBAR_NOBORDER_STYLE);
AddSimpleReBarBand(hWndCmdBar);
AddSimpleReBarBand(hWndToolBar, NULL, TRUE);
CreateSimpleStatusBar();
m_hWndClient = m_view.Create(m_hWnd, rcDefault, NULL,
WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
WS_EX_CLIENTEDGE);
UIAddToolBar(hWndToolBar);
UISetCheck(ID_VIEW_TOOLBAR, 1);
UISetCheck(ID_VIEW_STATUS_BAR, 1);
CMessageLoop* pLoop = _Module.GetMessageLoop();
pLoop->AddMessageFilter(this);
pLoop->AddIdleHandler(this);
return 0;
}
这是从一个SDI程序拿来的一段代码,该程序有一个基于command bar的工具条和一个状态条. 函数的第一行创建了一个command bar实例,然后对它进行初始化,在其中加入框架窗口的菜单和工具条位图. 这段代码先将菜单取出,把所有的下拉菜单转换为工具条按钮,并将菜单保存在一个变量中,以备后用. 给人的感觉是菜单是由工具条实现的-那我们就把它叫做工具条菜单(menu toolbar)吧. 然后Command Bar将程序工具条的图标装入image list 并将它们的ID保存在数组中. 当点击工具条菜单的按钮时,commandbar会找到对应的子菜单,创建一个弹出菜单. Command bar将子菜单项的ID和它保存的ID进行比较,这些ID跟image list中的工具条按钮图标是相关联的. 如果比较成功, 则将关联的图标加到菜单项上去. 这意味着相同ID的菜单项和工具条按钮具有相同的图标.
接下来, 创建工具条并把它关联到commandbar, 然后创建状态条和视图.可以看到视图的HWND存放在框架窗口的m_hWndClient变量中. 这个窗口句柄在框架窗口的WM_SIZE handler中会用到.当框架窗口改变大小时,它告知视图改变自身,于此同时也要考虑状态条和command bar.
在下来的三行(从调用UIAddToolBar()开始) 用来显示在运行时会改变状态的UI项(UI item).文章后面还会重提这个话题. 最后,访问消息循环(message loop), 你应该还记得该消息循环存放在一全局数组中. GetMessageLoop() 取得当前线程的消息循环,加入框架窗口的message filter和idle handler, 分别默认是PreTranslateMessage()和OnIdle().
框架窗口继承于以下类:
class CMainFrame :
public CFrameWindowImpl<CMainFrame>,
public CUpdateUI<CMainFrame>,
public CMessageFilter,
public CIdleHandler
后两个抽象类宣称了框架窗口类实现了PreTranslateMessage()和OnIdle(). 从CUpdateUI<>继承表示框架类支持UI update map.