?? wtl for mfc programmers, part ii - wtl gui base classes - wtl.htm
字號:
}
</FONT><font color="#0033FF">};</font></PRE>
<P>接下來是響應WM_TIMER消息的處理函數,它每秒鐘被調用一次。你應該知道怎樣使用F12鍵的竅門了,所以我直接給出響應函數的代碼:</P>
<PRE><SPAN class=cpp-keyword><font color="#0000FF">class</font></SPAN><font color="#0000FF"> CMyWindow : <SPAN class=cpp-keyword>public</SPAN> CFrameWindowImpl<CMyWindow>
{
<SPAN class=cpp-keyword>public</SPAN>:
BEGIN_MSG_MAP_EX(CMyWindow)
MSG_WM_CREATE(OnCreate)
MSG_WM_DESTROY(OnDestroy)</font><FONT color=red>
MSG_WM_TIMER(OnTimer)
</FONT><font color="#0000CC"> CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)
END_MSG_MAP()</font>
<FONT color=red><SPAN class=cpp-keyword>void</SPAN> OnTimer ( UINT uTimerID, TIMERPROC pTimerProc )
{
<SPAN class=cpp-keyword>if</SPAN> ( <SPAN class=cpp-literal>1</SPAN> != uTimerID )
SetMsgHandled(<SPAN class=cpp-keyword>false</SPAN>);
<SPAN class=cpp-keyword>else</SPAN>
RedrawWindow();
}
</FONT><font color="#0000CC">};</font></PRE>
<P>這個響應函數只是在每次定時器觸發時重畫窗口的客戶區。最后我們要響應WM_ERASEBKGND消息,在窗口客戶區的左上角顯示當前的時間。</P>
<PRE><SPAN class=cpp-keyword><font color="#0000FF">class</font></SPAN><font color="#0000FF"> CMyWindow : <SPAN class=cpp-keyword>public</SPAN> CFrameWindowImpl<CMyWindow>
{
<SPAN class=cpp-keyword>public</SPAN>:
BEGIN_MSG_MAP_EX(CMyWindow)
MSG_WM_CREATE(OnCreate)
MSG_WM_DESTROY(OnDestroy)
MSG_WM_TIMER(OnTimer)</font><FONT color=red>
MSG_WM_ERASEBKGND(OnEraseBkgnd)
</FONT><font color="#0000FF"> CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)
END_MSG_MAP()</font>
<FONT color=red> LRESULT OnEraseBkgnd ( HDC hdc )
{
CDCHandle dc(hdc);
CRect rc;
SYSTEMTIME st;
CString sTime;
<SPAN class=cpp-comment>// Get our window's client area.</SPAN>
GetClientRect ( rc );
<SPAN class=cpp-comment>// Build the string to show in the window.</SPAN>
GetLocalTime ( &st );
sTime.Format ( _T(<SPAN class=cpp-string>"The time is %d:%02d:%02d"</SPAN>),
st.wHour, st.wMinute, st.wSecond );
<SPAN class=cpp-comment>// Set up the DC and draw the text.</SPAN>
dc.SaveDC();
dc.SetBkColor ( RGB(<SPAN class=cpp-literal>255</SPAN>,<SPAN class=cpp-literal>153</SPAN>,<SPAN class=cpp-literal>0</SPAN>);
dc.SetTextColor ( RGB(<SPAN class=cpp-literal>0</SPAN>,<SPAN class=cpp-literal>0</SPAN>,<SPAN class=cpp-literal>0</SPAN>) );
dc.ExtTextOut ( <SPAN class=cpp-literal>0</SPAN>, <SPAN class=cpp-literal>0</SPAN>, ETO_OPAQUE, rc, sTime,
sTime.GetLength(), NULL );
<SPAN class=cpp-comment>// Restore the DC.</SPAN>
dc.RestoreDC(-<SPAN class=cpp-literal>1</SPAN>);
<SPAN class=cpp-keyword>return</SPAN> <SPAN class=cpp-literal>1</SPAN>; <SPAN class=cpp-comment>// We erased the background (ExtTextOut did it)</SPAN>
}
</FONT><font color="#0000FF">};</font></PRE>
<P>這個消息處理函數不僅使用了CRect和CString類,還使用了一個GDI包裝類CDCHandle。對于CString類我想說的是它等同與MFC的CString類,我在后面的文章中還會介紹這些包裝類,現在你只需要知道CDCHandle是對HDC的簡單封裝就行了,使用方法與MFC的CDC類相似,只是CDCHandle的實例在超出作用域后不會銷毀它所操作的設備上下文。</P>
<P>所有的工作完成了,現在看看我們的窗口是什么樣子:</P>
<P><IMG height=208 alt=" [clock window - 4K] "
src="images/firstwin2.png"
width=283 align=bottom border=0></P>
<P>例子代碼中還使用了WM_COMMAND響應菜單消息,在這里我不作介紹,但是你可以查看例子代碼,看看WTL的COMMAND_ID_HANDLER_EX宏是如何工作的。</P>
<H2><A name=appwizard></A><font color="#FFFF66">從</font><font color="#FFFF66">WTL的應用程序生成向導能得到什么</font></H2>
<P>WTL的發布版本附帶一個很棒的應用程序生成向導,讓我們以一個SDI 應用為例看看它有什么特性。</P>
<H3><A name=thruwizard></A><font color="#FFFF66">使用向導的整個過程</font></H3>
<P>在VC的IDE環境下單擊File|New菜單,從列表中選擇ATL/WTL AppWizard,我們要重寫時鐘程序,所以用WTLClock作為項目的名字:</P>
<P><IMG height=403 alt=" [AppWiz screen 1 - 14K] "
src="images/appwiz1.png"
width=561 align=bottom border=0></P>
<P>在下一頁你可以選擇項目的類型,SDI,MDI或者是基于對話框的應用,當然還有其它選項,如下圖所示設置這些選項,然后點擊“下一步”:</P>
<P><IMG height=387 alt=" [AppWiz screen 2 - 22K] "
src="images/appwiz2.png"
width=477 align=bottom border=0></P>
<P>在最后一頁你可以選擇是否使用toolbar,rebar和status bar,為了簡單起見,取消這些選項并單擊“結束”。</P>
<P><IMG height=387 alt=" [AppWiz screen 3 - 21K] "
src="images/appwiz3.png"
width=477 align=bottom border=0></P>
<H3><A name=examinecode></A><font color="#FFFF66">查看生成的代碼</font></H3>
<P>向導完成后,在生成的代碼中有三個類:CMainFrame, CAboutDlg, 和CWTLClockView,從名字上就可以猜出這些類的作用。雖然也有一個是視圖類,但它僅僅是從CWindowImpl派生出來的一個簡單的窗口類,沒有象MFC那樣的文檔/視圖結構。</P>
<P>還有一個_tWinMain()函數,它先初始化COM環境,公用控件和_Module,然后調用全局函數Run()。Run()函數創建主窗口并開始消息循環,Run()調用CMessageLoop::Run(),消息泵實際上是位于CMessageLoop::Run()內,我將在下一個章節介紹CMessageLoop的更多細節。<br>
</P>
<P>CAboutDlg是CDialogImpl的派生類,它對應于ID IDD_ABOUTBOX資源,我在第一部分已經介紹過對話框,所以你應該能看懂CAboutDlg的代碼。<br>
</P>
<P>CWTLClockView是我們的程序的視圖類,它的作用和MFC的視圖類一樣,沒有標題欄,覆蓋整個主窗口的客戶區。CWTLClockView類有一個PreTranslateMessage()函數,也和MFC中的同名函數作用相同,還有一個WM_PAINT的消息響應函數。這兩個函數都沒有什么特別之處,只是我們會填寫OnPaint()函數來顯示時間。</P>
<P>最后是我們的CMainFrame類,它有許多有趣的新東西,這是這個類的定義縮略版本:</P>
<PRE><SPAN class=cpp-keyword><font color="#0000FF">class</font></SPAN><font color="#0000FF"> CMainFrame : <SPAN class=cpp-keyword>public</SPAN> CFrameWindowImpl<CMainFrame>,
<SPAN class=cpp-keyword>public</SPAN> CUpdateUI<CMainFrame>,
<SPAN class=cpp-keyword>public</SPAN> CMessageFilter,
<SPAN class=cpp-keyword>public</SPAN> CIdleHandler
{
<SPAN class=cpp-keyword>public</SPAN>:
DECLARE_FRAME_WND_CLASS(NULL, IDR_MAINFRAME)
CWTLClockView m_view;
<SPAN class=cpp-keyword>virtual</SPAN> BOOL PreTranslateMessage(MSG* pMsg);
<SPAN class=cpp-keyword>virtual</SPAN> BOOL OnIdle();
BEGIN_UPDATE_UI_MAP(CMainFrame)
END_UPDATE_UI_MAP()
BEGIN_MSG_MAP(CMainFrame)
<SPAN class=cpp-comment>// ...</SPAN>
CHAIN_MSG_MAP(CUpdateUI<CMainFrame>)
CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>)
END_MSG_MAP()
};</font></PRE>
<P>CMessageFilter是一個嵌入類,它提供PreTranslateMessage()函數,CIdleHandler也是一個嵌入類,它提供了OnIdle()函數。CMessageLoop,
CIdleHandler 和 CUpdateUI三個類互相協同完成界面元素的狀態更新(UI update),就像MFC中的ON_UPDATE_COMMAND_UI宏一樣。</P>
<p>CMainFrame::OnCreate()中創建了視圖窗口并保存這個窗口的句柄,當主窗口改變大小時視圖窗口的大小也會隨之改變。OnCreate()函數還將CMainFrame對象添加到由CAppModule維持的消息過濾器隊列和空閑處理隊列,我將在稍后介紹這些。</p>
<PRE><font color="#0000FF">LRESULT CMainFrame::OnCreate(UINT <SPAN class=cpp-comment>/*uMsg*/</SPAN>, WPARAM <SPAN class=cpp-comment>/*wParam*/</SPAN>,
LPARAM <SPAN class=cpp-comment>/*lParam*/</SPAN>, BOOL& <SPAN class=cpp-comment>/*bHandled*/</SPAN>)
{
m_hWndClient = m_view.Create(m_hWnd, rcDefault, NULL, |
WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS |
WS_CLIPCHILDREN, WS_EX_CLIENTEDGE);
<SPAN class=cpp-comment>// register object for message filtering and idle updates</SPAN>
CMessageLoop* pLoop = _Module.GetMessageLoop();
pLoop->AddMessageFilter(<SPAN class=cpp-keyword>this</SPAN>);
pLoop->AddIdleHandler(<SPAN class=cpp-keyword>this</SPAN>);
<SPAN class=cpp-keyword>return</SPAN> <SPAN class=cpp-literal>0</SPAN>;
}</font></PRE>
<P>m_hWndClient是CFrameWindowImpl對象的一個成員變量,當主窗口大小改變時此窗口的大小也將改變。</P>
<p>在生成的CMainFrame中還添加了對File|New, File|Exit, 和 Help|About菜單消息的處理。我們的時鐘程序不需要這些默認的菜單項,但是現在將它們留在代碼中也沒有害處。現在可以編譯并運行向導生成的代碼,不過這個程序確實沒有什么用處。如果你感興趣的話可以深入CMainFrame::CreateEx()函數的內部看看主窗口和它的資源是如何被加載和創建得。</p>
<p>我們的下一步WTL之旅是CMessageLoop,它掌管消息泵和空閑處理。</p>
<H2><A name=msgloopinternals></A><font color="#FFFF66">CMessageLoop 的內部實現</font></H2>
<P>CMessageLoop為我們的應用程序提供一個消息泵,除了一個標準的DispatchMessage/TranslateMessage循環外,它還通過調用PreTranslateMessage()函數實現了消息過濾機制,通過調用OnIdle()實現了空閑處理功能。下面是Run()函數的偽代碼:</P>
<PRE><SPAN class=cpp-keyword><font color="#0000FF">int</font></SPAN><font color="#0000FF"> Run()
{
MSG msg;
<SPAN class=cpp-keyword>for</SPAN>(;;)
{
<SPAN class=cpp-keyword>while</SPAN> ( !PeekMessage(&msg) )
DoIdleProcessing();
<SPAN class=cpp-keyword>if</SPAN> ( <SPAN class=cpp-literal>0</SPAN> == GetMessage(&msg) )
<SPAN class=cpp-keyword>break</SPAN>; <SPAN class=cpp-comment>// WM_QUIT retrieved from the queue</SPAN>
<SPAN class=cpp-keyword>if</SPAN> ( !PreTranslateMessage(&msg) )
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
<SPAN class=cpp-keyword>return</SPAN> msg.wParam;
}</font></PRE>
<P>那些需要過濾消息的類只需要象CMainFrame::OnCreate()函數那樣調用CMessageLoop::AddMessageFilter()函數就行了,CMessageLoop就會知道該調用那個PreTranslateMessage()函數,同樣,如果需要空閑處理就調用CMessageLoop::AddIdleHandler()函數。</P>
<p>需要注意得是在這個消息循環中沒有調用TranslateAccelerator() 或 IsDialogMessage() 函數,因為CFrameWindowImpl在這之前已經做了處理,但是如果你在程序中使用了非模式對話框,那你就需要在CMainFrame::PreTranslateMessage()函數中添加對IsDialogMessage()函數的調用。</p>
<H2><A name=frameinternals></A><font color="#FFFF66">CFrameWindowImpl 的內部實現</font></H2>
<P>CFrameWindowImpl 和它的基類 CFrameWindowImplBase提供了對toolbars,rebars, status bars,工具條按鈕的工具提示和菜單項的掠過式幫助,這些也是MFC的CFrameWnd類的基本特征。我會逐步介紹這些特征,完整的討論CFrameWindowImpl類需要再寫兩篇文章,但是現在看看CFrameWindowImpl是如何處理WM_SIZE和它的客戶區就足夠了。需要記住一點前面提到的東西,m_hWndClient是CFrameWindowImplBase類的成員變量,它存儲主窗口內的“視圖”窗口的句柄。</P>
<p>CFrameWindowImpl類處理了WM_SIZE消息:</p>
<PRE><font color="#0000FF">LRESULT OnSize(UINT <SPAN class=cpp-comment>/*uMsg*/</SPAN>, WPARAM wParam, LPARAM <SPAN class=cpp-comment>/*lParam*/</SPAN>, BOOL& bHandled)
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -