一、對第三部分的介紹
自從作為Windows 95的通用控制項出現以來,工具條和狀態條就變成了很普遍的事物。由於MFC支援浮動的工具條從而使它們更受歡迎。隨著通用控制項的更新,Rebars(最初 被稱為Coollbar)使得工具條有了另一種展示方式。在第三部分,我將介紹WTL對這些控制條的支援和如何在你的程式中使用它們。
二、主視窗的工具條和狀態條
CFrameWindowImpl有三個HWND類型的成員變數在視窗建立時被初始化,我們已經見過m_hWndClient,它是填充主視窗客戶區的“視圖”視窗的控制代碼,現在我們遇到了另外兩個:
- m_hWndToolBar: 工具條或Rebar的視窗控制代碼
- m_hWndStatusBar: 狀態條的視窗控制代碼
CFrameWindowImpl只支援一個工具條,也沒有像MFC那樣的可多點停靠的工具條,如果你想使用多個工具條又不想修改CFrameWindowImpl的內部代碼,你就需要使用Rebar。我將介紹它們二者並示範如何使用應用程式嚮導添加工具條和Rebar。
CFrameWindowImpl::OnSize()訊息響應函數調用了UpdateLayout(),UpdateLayout()做兩件事: 從新定位所有控制條和改變視圖視窗的大小使之填充整個客戶區。實際工作是由UpdateBarsPosition()完成 的,UpdateLayout()只是調用了該函數。實現的代碼相當簡單,向工具條和狀態條發送WM_SIZE訊息,由這些控制條的預設視窗處理過程將它 們定位到主視窗的頂部或底部。
當你告訴應用程式嚮導給你的視窗添加工具條和狀態條時,嚮導就在CMainFrame::OnCreate()中添加了建立它們的代碼。現在我們來看看這些代碼,當然是為了再寫一個時鐘程式。
三、嚮導為工具條和狀態條產生得代碼
我們將開始一個新的工程,讓嚮導為主視窗建立工具條和狀態條。首先建立一個名為WTLClock2的新工程,在嚮導的第一頁,選SDI並使“產生CPP檔案”檢查框被選中:
在第二頁,取消Rebar使嚮導僅僅建立一個普通的工具條:
從第二部分的程式中複製相應的代碼,新程式看起來是這樣的:
四、CMainFraCMainFrame 如何建立工具條和狀態條
在這個例子中,嚮導向CMainFrame::OnCreate()函數添加了更多的代碼,這些代碼的作用就是建立控制條並通知CUpdateUI更新工具條上的按鈕。
LRESULT CMainFrame::OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/,<br /> LPARAM /*lParam*/, BOOL& /*bHandled*/)<br />{<br /> CreateSimpleToolBar();<br /> CreateSimpleStatusBar();</p><p> m_hWndClient = m_view.Create(...);</p><p>// ...</p><p> // register object for message filtering and idle updates<br /> CMessageLoop* pLoop = _Module.GetMessageLoop();<br /> ATLASSERT(pLoop != NULL);<br /> pLoop->AddMessageFilter(this);<br /> pLoop->AddIdleHandler(this);</p><p> return 0;<br />}
這是新添加的代碼的開始部分,CFrameWindowImpl::CreateSimpleToolBar()函數使用資源 IDR_MAINFRAME建立工具條並將其控制代碼賦值給m_hWndToolBar,下面是CreateSimpleToolBar()函數的代碼:
BOOL CFrameWindowImpl::CreateSimpleToolBar(<br /> UINT nResourceID = 0,<br /> DWORD dwStyle = ATL_SIMPLE_TOOLBAR_STYLE,<br /> UINT nID = ATL_IDW_TOOLBAR)<br />{<br /> ATLASSERT(!::IsWindow(m_hWndToolBar));</p><p> if(nResourceID == 0)<br /> nResourceID = T::GetWndClassInfo().m_uCommonResourceID;</p><p> m_hWndToolBar = T::CreateSimpleToolBarCtrl(m_hWnd, nResourceID, TRUE,<br /> dwStyle, nID);<br /> return (m_hWndToolBar != NULL);<br />}
參數:
-
nResourceID
-
工具條資源得ID。如果使用預設值0作為參數,程式將使用DECLARE_FRAME_WND_CLASS宏指定得資源,這裡使用的IDR_MAINFRAME是嚮導產生的程式碼。
-
dwStyle
-
工具條的類型或樣式。預設值ATL_SIMPLE_TOOLBAR_STYLE被定義為TBSTYLE_TOOLTIPS,子視窗和可見三種風格的結合,這使得滑鼠移到按鈕上時工具條會彈出工具提示。
-
nID
-
工具條的視窗ID,通常都會使用預設值。
CreateSimpleToolBar()首先檢查是否已經建立了一個工具條,然後調用CreateSimpleToolBarCtrl()函數 建立工具條控制,CreateSimpleToolBarCtrl()返回的工具條控制控制代碼儲存在m_hWndToolBar中。 CreateSimpleToolBarCtrl()負責讀出資源並建立相應的工具條按鈕,然後返回工具條視窗的控制代碼。這部分的代碼相當長,我不在這裡做 具體介紹,如果你對此感興趣得話何以在atlframe.h中找到這些代碼。
OnCreate()函數接下來會調用CFrameWindowImpl::CreateSimpleStatusBar()函數,此函數建立狀態條並將控制代碼存在m_hWndStatusBar,下面是該函數的代碼:
BOOL CFrameWindowImpl::CreateSimpleStatusBar(<br /> UINT nTextID = ATL_IDS_IDLEMESSAGE,<br /> DWORD dwStyle = ... SBARS_SIZEGRIP,<br /> UINT nID = ATL_IDW_STATUS_BAR)<br />{<br /> TCHAR szText[128]; // max text lentgth is 127 for status bars<br /> szText[0] = 0;<br /> ::LoadString(_Module.GetResourceInstance(), nTextID, szText, 128);<br /> return CreateSimpleStatusBar(szText, dwStyle, nID);<br />}
顯示在狀態條的文字是從字串資源中裝載的,這個函數的參數是:
-
nTextID
-
用於在狀態條上顯示的字串的資源ID,嚮導產生的ATL_IDS_IDLEMESSAGE對應的字串是“Ready”。
-
dwStyle
-
狀態條的樣式。預設值包含了SBARS_SIZEGRIP風格,這使得狀態條的右下角會顯示一個改變視窗大小的標誌。
-
nID
-
狀態條的視窗ID,通常都會使用預設值。
CreateSimpleStatusBar()調用另外一個重載函數建立狀態條:
BOOL CFrameWindowImpl::CreateSimpleStatusBar(<br /> LPCTSTR lpstrText,<br /> DWORD dwStyle = ... SBARS_SIZEGRIP,<br /> UINT nID = ATL_IDW_STATUS_BAR)<br />{<br /> ATLASSERT(!::IsWindow(m_hWndStatusBar));<br /> m_hWndStatusBar = ::CreateStatusWindow(dwStyle, lpstrText, m_hWnd, nID);<br /> return (m_hWndStatusBar != NULL);<br />}
這個重載的版本首先檢查是否已經建立了狀態條,然後調用CreateStatusWindow()建立狀態條,狀態條的控制代碼存放在m_hWndStatusBar中。
五、顯示和隱藏工具條和狀態條
CMainFrame類也有一個視圖菜單,它有兩個命令:顯示/隱藏工具條和狀態條,它們的ID是ID_VIEW_TOOLBAR和 ID_VIEW_STATUS_BAR。CMainFrame類有這兩個命令的響應函數,分別顯示和隱藏相應的控制條,下面是 OnViewToolBar()函數的代碼:
LRESULT CMainFrame::OnViewToolBar(WORD /*wNotifyCode*/, WORD /*wID*/,<br /> HWND /*hWndCtl*/, BOOL& /*bHandled*/)<br />{<br /> BOOL bVisible = !::IsWindowVisible(m_hWndToolBar);<br /> ::ShowWindow(m_hWndToolBar, bVisible ? SW_SHOWNOACTIVATE : SW_HIDE);<br /> UISetCheck(ID_VIEW_TOOLBAR, bVisible);<br /> UpdateLayout();<br /> return 0;<br />}
這些代碼翻轉控制條的顯示狀態,相應的翻轉View|Toolbar菜單上的檢查標記,然後調用UpdateLayout()重新置放控制條並改變視圖視窗的大小。
六、工具條和狀態條的內在特徵
MFC的架構提供了很多好的特性,例如工具條按鈕的工具提示和功能表項目的掠過式協助。WTL中相對應的功能實現在CFrameWindowImpl類中。下面的螢幕顯示了工具提示和掠過式協助。
CFrameWindowImplBase類有兩個訊息相應函數用來實現這些功能,OnMenuSelect()處理WM_MENUSELECT消 息,它像MFC那樣尋找掠過式協助的字串:首先裝載與菜單資源ID相同的字串資源,在字串中尋找 /n 字元,使用/n之前的內容作為掠過協助的內容。OnToolTipTextA() 和 OnToolTipTextW() 函數分別響應 TTN_GETDISPINFOA訊息和TTN_GETDISPINFOW訊息,提供工具條按鈕的工具提示。這兩個處理函數和 OnMenuSelect()函數一樣裝載相應的字串,只是使用/n後面的字串。(邊註:OnMenuSelect()和 OnToolTipTextA()函數對於DBCS字元是不安全的,因為它在尋找/n字元時沒有檢查DBCS字串的頭部和尾部)下面是工具條及其關聯的 協助字串的例子:
七、建立不同樣式的工具條
如果你不喜歡在工具條上顯示3D按鈕(儘管從可用性觀點來看平面的介面元素是件糟糕的事情),你可以通過改變 CreateSimpleToolBar()函數的參數來改變工具條的樣式。例如,你可以在CMainFrame::OnCreate()使用如下代碼創 建一個IE風格的工具條:
CreateSimpleToolBar ( 0, ATL_SIMPLE_TOOLBAR_STYLE | TBSTYLE_FLAT | TBSTYLE_LIST );
如果你使用嚮導為你的程式添加了manifest檔案,它就會在Windows XP系統上使用6.0版的通用控制項,你不能選擇按鈕的類型,工具條會自動使用平面按鈕即使你建立工具條時沒有添加TBSTYLE_FLAT風格。
八、工具條編輯器
正如我們前面所見,嚮導為我們的程式建立了幾個預設的按鈕,當然只有About按鈕有事件處理。你可以像在MFC的工程中一樣使用工具條編輯器修改 工具條資源,CreateSimpleToolBarCtrl()用這個工具條資源建立工具條。下面是嚮導產生的工具條在編輯器中的樣子:
對於我們的時鐘程式,我們添加四個按鈕,兩個按鈕用來改變視圖視窗的顏色,另外兩個用來顯示/隱藏工具條和狀態條。下面是我們的新工具條:
這些按鈕是:
- IDC_CP_COLORS: 將視圖視窗顏色改為CodeProject網站的顏色
- IDC_BW_COLORS: 將視圖視窗顏色改為黑白顏色
- ID_VIEW_STATUS_BAR: 顯示或隱藏狀態條
- ID_VIEW_TOOLBAR: 顯示或隱藏工具條
前兩個按鈕都有相應的功能表項目,它們都調用視圖類的一個新函數SetColor(),向這個函數傳遞前景顏色和背景顏色,視圖視窗用這兩個參數改變窗 口的顯示。響應這兩個按鈕的處理函數與響應相應的功能表項目的處理函數在使用COMMAND_ID_HANDLER_EX宏上沒有區別,你可以查看例子工程的 代碼瞭解這些訊息處理的細節。在下一節我將介紹狀態條和工具條按鈕的UI狀態更新,使它們能夠反映工具條或狀態條當前的狀態。
九、工具條按鈕的UI狀態更新
嚮導產生的程式碼已經為CMainFrame添加了對View|Toolbar和View|Status Bar兩個功能表項目的Check和Uncheck的UI更新處理。這和第二章的程式一樣:對CMainFrame類的兩個命令使用UI更新的宏:
BEGIN_UPDATE_UI_MAP(CMainFrame)<br /> UPDATE_ELEMENT(ID_VIEW_TOOLBAR, UPDUI_MENUPOPUP)<br /> UPDATE_ELEMENT(ID_VIEW_STATUS_BAR, UPDUI_MENUPOPUP)<br />END_UPDATE_UI_MAP()
我們的時鐘程式的工具條按鈕與對應的功能表項目有相同的ID,所以第一步就是為每個宏添加UPDUI_TOOLBAR標誌:
BEGIN_UPDATE_UI_MAP(CMainFrame)<br /> UPDATE_ELEMENT(ID_VIEW_TOOLBAR, UPDUI_MENUPOPUP | UPDUI_TOOLBAR)<br /> UPDATE_ELEMENT(ID_VIEW_STATUS_BAR, UPDUI_MENUPOPUP | UPDUI_TOOLBAR)<br />END_UPDATE_UI_MAP()
還需要添加兩個函數響應工具條按鈕的更新,但幸運的是嚮導已經為我們做了,所以如果此時編譯這個程式,功能表項目和工具條按鈕都會更新。
十、使一個工具條支援UI狀態更新
如果查看CMainFrame::OnCreate()的代碼你就會發現一段新的代碼,這段代碼設定了兩個功能表項目的初始狀態:
LRESULT CMainFrame::OnCreate( ... )<br />{<br />// ...<br /> m_hWndClient = m_view.Create(...);</p><p> UIAddToolBar(m_hWndToolBar);<br /> UISetCheck(ID_VIEW_TOOLBAR, 1);<br /> UISetCheck(ID_VIEW_STATUS_BAR, 1);<br />// ...<br />}
UIAddToolBar()將工具條的視窗控制代碼傳給CUpdateUI,所以當需要更新按鈕的狀態時CUpdateUI會向這個視窗發訊息。另一個重要的調用位於OnIdle()中:
BOOL CMainFrame::OnIdle()<br />{<br /> UIUpdateToolBar();<br /> return FALSE;<br />}
當訊息佇列中沒有訊息等待時CMessageLoop::Run()就會調用OnIdle(),UIUpdateToolBar()遍曆UI更新 表,尋找那些帶有UPDUI_TOOLBAR標誌又被UISetCheck()之類的函數改變了狀態的的介面元素(當然是工具條),相應的改變按鈕的狀 態。需要注意得是如果更新彈出式菜單的狀態就不需要做以上兩步,因為CUpdateUI響應WM_INITMENUPOPUP訊息,只有接到此訊息時才更 新菜單狀態。
如果查看例子代碼就會發現它也示範了如何更新架構視窗的菜單條上的頂級功能表項目的狀態。有一個功能表項目是執行Start和Stop命令,起到開始和停止 時鐘的作用,當然這需要做一些不平常的事情:菜單條上的功能表項目總是處於彈出狀態。為了完整的介紹CUpdateUI我將它們也加進例子代碼中,要瞭解它們 可以尋找對UIAddMenuBar()和UIUpdateMenuBar()兩個函數的調用。
十一、使用Rebar代替簡單的工具條
CFrameWindowImpl也支援使用Rebar控制項,使你的程式看起來像IE,使用Rebar也是在程式中使用多個工具條的一個方法(譯者 加:前面講過,另一個方法就是修改WTL的原始碼)。要使用Rebar需要在嚮導的第二頁選上支援Rebar的檢查框,如下所示:
第二個例子工程WTLClock3就使用了Rebar控制項,如果你正在跟著例子代碼學習,那現在就開啟WTLClock3。
你首先會注意到建立工具條的代碼有些不同,出現這種感覺是因為我們在程式中使用了rebar。以下是相關的代碼:
LRESULT CMainFrame::OnCreate(...)<br />{<br /> HWND hWndToolBar = CreateSimpleToolBarCtrl ( m_hWnd,<br /> IDR_MAINFRAME, FALSE,<br /> ATL_SIMPLE_TOOLBAR_PANE_STYLE );</p><p> CreateSimpleReBar(ATL_SIMPLE_REBAR_NOBORDER_STYLE);<br /> AddSimpleReBarBand(hWndToolBar);<br />// ...<br />}
代碼從建立工具條開始,只是使用了不同的風格,也就是ATL_SIMPLE_TOOLBAR_PANE_STYLE,它定義在atlframe.h 檔案中,與ATL_SIMPLE_TOOLBAR_STYLE風格相似,只是附加了一些諸如CCS_NOPARENTALIGN之類的風格,這是使工具條 作為Rebar的子視窗能夠正常工作所必需的風格。
下一行代碼是調用CreateSimpleReBar()函數,該函數建立Rebar控制項並將控制代碼存到m_hWndToolBar中。接下來調用AddSimpleReBarBand()函數為Rebar建立一個條位並告訴Rebar這個條位上是一個工具條。
CMainFrame::OnViewToolBar()函數也有些不同,它只隱藏Rebar上工具條所在的條位而不是隱藏m_hWndToolBar(如果隱藏m_hWndToolBar將隱藏整個Rebar而不僅僅是工具條)。
如果你使用多個工具條,只需像嚮導為我們產生的關於第一個工具條的代碼那在OnCreate()建立它們並調用 AddSimpleReBarBand()添加到Rebar就行了。CFrameWindowImpl使用標準的Rebar控制項,不像MFC那樣支援可停 靠的工具條,你所能作得就是排列這些工具條在Rebar中的位置。
十二、多窗格的狀態條
WTL另有一個狀態條類實現多窗格的狀態條,與MFC的預設的狀態條一樣有CAPS,LOCK和NUM LOCK指標,這個類就是CMultiPaneStatusBarCtrl,在WTLClock3例子工程中示範了如何使用這個類。這個類支援有限的UI更新,當彈出式菜單被顯示時有“Default”屬性的窗格會延伸到整個狀態條的寬度用於顯示菜單的掠過式協助。
第一步就是在CMainFrame中聲明一個CMultiPaneStatusBarCtrl類型的成員變數:
class CMainFrame : public ...<br />{<br />//...<br />protected:<br /> CMultiPaneStatusBarCtrl m_wndStatusBar;<br />};
接著在OnCreate()中建立狀態條並這隻UI更新:
m_hWndStatusBar = m_wndStatusBar.Create ( *this );<br /> UIAddStatusBar ( m_hWndStatusBar );
就像CreateSimpleStatusBar()函數做得那樣,我們也將狀態條的控制代碼存放在m_hWndStatusBar中。
下一步就是調用CMultiPaneStatusBarCtrl::SetPanes()函數建立窗格:
BOOL SetPanes(int* pPanes, int nPanes, bool bSetText = true);
參數:
-
pPanes
-
存放窗格ID的數組
-
nPanes
-
窗格ID數組中元素的個數(譯者加:就是窗格數)
-
bSetText
-
如果是true,所有的窗格被立即設定文字,這一點將在下面解釋。
窗格ID可以是ID_DEFAULT_PANE,此ID用於建立支援掠過式協助的窗格,窗格ID也可以是字串資源ID。對於非預設的窗格WTL裝載這個ID對應的字串並計算寬度,並將窗格設定為相應的寬度,這和MFC使用的邏輯是一樣的。
bSetText控制著窗格是否立即顯示相關的字串,如果是true,SetPanes()顯示每個窗格的字串,否則窗格就被置空。
下面是我們對SetPanes()的調用:
// Create the status bar panes.<br />int anPanes[] = { ID_DEFAULT_PANE, IDPANE_STATUS,<br /> IDPANE_CAPS_INDICATOR };</p><p> m_wndStatusBar.SetPanes ( anPanes, 3, false );
IDPANE_STATUS對應的字串是“@@@@”,這樣應該有足夠的寬度(希望是)顯示兩個時鐘狀態字串“Running”和“Stopped”。和MFC一樣,你需要自己估算窗格的寬度,IDPANE_CAPS_INDICATOR對應的字串是“CAPS”。
十三、窗格的UI狀態更新
為了更新窗格上的文本,我們需要將相應的窗格添加到UI更新表:
BEGIN_UPDATE_UI_MAP(CMainFrame)<br /> //...<br /> UPDATE_ELEMENT(1, UPDUI_STATUSBAR) // clock status<br /> UPDATE_ELEMENT(2, UPDUI_STATUSBAR) // CAPS indicator<br /> END_UPDATE_UI_MAP()
這個宏的第一個參數是窗格的索引而不是ID,這很不幸,因為如果你重新排列了窗格,你要記得更新UI更新表。
由於我們在調用SetPanes()是第三個參數是false,所以窗格初始是空的。我們下一步要做得就是將時鐘狀態窗格的初始文本設為“Running”
// Set the initial text for the clock status pane.<br /> UISetText ( 1, _T("Running") );
和前面一樣,第一個參數是窗格的索引。UISetText()是狀態條唯一支援的UI更新函數。
最後,在CMainFrame::OnIdle()中添加對UIUpdateStatusBar()函數的調用,使狀態條的窗格能夠在空閑時間被更新:
BOOL CMainFrame::OnIdle()<br />{<br /> UIUpdateToolBar();<br /> UIUpdateStatusBar();<br /> return FALSE;<br />}
當你使用UIUpdateStatusBar()時CUpdateUI的一個問題就暴露出來了--功能表項目的文本在調用UISetText()後沒有 改變!如果你在看WTLClock3工程的代碼,時鐘的開始/停止功能表項目被移到了Clock菜單,在功能表項目命令的響應處理函數中設定功能表項目的文本。無論如 何,如果當前調用的是UIUpdateStatusBar(),那麼對UISetText()的調用就不會起作用。我沒有研究這個問題是否可以被修複,所 以如果你打算改變菜單的文本,你需要留意這個地方。
最後,我們需要檢查CAPS LOCK鍵的狀態,更新相應的兩個窗格。這些代碼是通過OnIdle()被調用的,所以程式會在每次空閑時間檢查它們的狀態。
BOOL CMainFrame::OnIdle()<br />{<br /> // Check the current Caps Lock state, and if it is on, show the // CAPS indicator in pane 2 of the status bar. if ( GetKeyState(VK_CAPITAL) & 1 ) UISetText ( 2, CString(LPCTSTR(IDPANE_CAPS_INDICATOR)) ); else UISetText ( 2, _T("") );</p><p> UIUpdateToolBar();<br /> UIUpdateStatusBar();<br /> return FALSE;<br />}
第一次調用UISetText()時將從字串資源中裝載“CAPS”字串,但是在CString的建構函式中使用了一個機靈的竅門(有充分的文檔說明)。
在完成所有的代碼之後,狀態條看起來是這個樣子: