對第三部分的介紹
自從作為Windows 95的通用控件出現以來,工具條和狀態條就變成了很普遍的事物。由於MFC支持浮動的工具條從而使它們更受歡迎。隨著通用控件的更新,Rebars(最初被稱為Coollbar)使得工具條有了另一種展示方式。在第三部分,我將介紹WTL對這些控制條的支持和如何在你的程序中使用它們。
主窗口的工具條和狀態條
CFrameWindowImpl有三個HWND類型的成員變量在窗口創建時被初始化,我們已經見過m_hWndClient,它是填充主窗口客戶區的“視圖”窗口的句柄,現在我們遇到了另外兩個:
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*/,
LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
CreateSimpleToolBar();
CreateSimpleStatusBar();
m_hWndClient = m_view.Create(...);
// ...
// register object for message filtering and idle updates
CMessageLoop* pLoop = _Module.GetMessageLoop();
ATLASSERT(pLoop != NULL);
pLoop->AddMessageFilter(this);
pLoop->AddIdleHandler(this);
return 0;
}
這是新添加的代碼的開始部分,CFrameWindowImpl::CreateSimpleToolBar()函數使用資源IDR_MAINFRAME創建工具條並將其句柄賦值給m_hWndToolBar,下面是CreateSimpleToolBar()函數的代碼:
BOOL CFrameWindowImpl::CreateSimpleToolBar(
UINT nResourceID = 0,
DWORD dwStyle = ATL_SIMPLE_TOOLBAR_STYLE,
UINT nID = ATL_IDW_TOOLBAR)
{
ATLASSERT(!::IsWindow(m_hWndToolBar));
if(nResourceID == 0)
nResourceID = T::GetWndClassInfo().m_uCommonResourceID;
m_hWndToolBar = T::CreateSimpleToolBarCtrl(m_hWnd, nResourceID, TRUE,
dwStyle, nID);
return (m_hWndToolBar != NULL);
}
參數:
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(
UINT nTextID = ATL_IDS_IDLEMESSAGE,
DWORD dwStyle = ... SBARS_SIZEGRIP,
UINT nID = ATL_IDW_STATUS_BAR)
{
TCHAR szText[128]; // max text lentgth is 127 for status bars
szText[0] = 0;
::LoadString(_Module.GetResourceInstance(), nTextID, szText, 128);
return CreateSimpleStatusBar(szText, dwStyle, nID);
}
顯示在狀態條的文字是從字符串資源中裝載的,這個函數的參數是:
nTextID 用於在狀態條上顯示的字符串的資源ID,向導生成的ATL_IDS_IDLEMESSAGE對應的字符串是“Ready”。 dwStyle 狀態條的樣式。默認值包含了SBARS_SIZEGRIP風格,這使得狀態條的右下角會顯示一個改變窗口大小的標志。 nID 狀態條的窗口ID,通常都會使用默認值。CreateSimpleStatusBar()調用另外一個重載函數創建狀態條:
BOOL CFrameWindowImpl::CreateSimpleStatusBar(
LPCTSTR lpstrText,
DWORD dwStyle = ... SBARS_SIZEGRIP,
UINT nID = ATL_IDW_STATUS_BAR)
{
ATLASSERT(!::IsWindow(m_hWndStatusBar));
m_hWndStatusBar = ::CreateStatusWindow(dwStyle, lpstrText, m_hWnd, nID);
return (m_hWndStatusBar != NULL);
}
這個重載的版本首先檢查是否已經創建了狀態條,然後調用CreateStatusWindow()創建狀態條,狀態條的句柄存放在m_hWndStatusBar中。
顯示和隱藏工具條和狀態條
CMainFrame類也有一個視圖菜單,它有兩個命令:顯示/隱藏工具條和狀態條,它們的ID是ID_VIEW_TOOLBAR和ID_VIEW_STATUS_BAR。CMainFrame類有這兩個命令的響應函數,分別顯示和隱藏相應的控制條,下面是OnViewToolBar()函數的代碼:
LRESULT CMainFrame::OnViewToolBar(WORD /*wNotifyCode*/, WORD /*wID*/,
HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
BOOL bVisible = !::IsWindowVisible(m_hWndToolBar);
::ShowWindow(m_hWndToolBar, bVisible ? SW_SHOWNOACTIVATE : SW_HIDE);
UISetCheck(ID_VIEW_TOOLBAR, bVisible);
UpdateLayout();
return 0;
}
這些代碼翻轉控制條的顯示狀態,相應的翻轉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()用這個工具條資源創建工具條。下面是向導生成的工具條在編輯器中的樣子:
對於我們的時鐘程序,我們添加四個按鈕,兩個按鈕用來改變視圖窗口的顏色,另外兩個用來顯示/隱藏工具條和狀態條。下面是我們的新工具條:
這些按鈕是:
前兩個按鈕都有相應的菜單項,它們都調用視圖類的一個新函數SetColor(),向這個函數傳遞前景顏色和背景顏色,視圖窗口用這兩個參數改變窗口的顯示。響應這兩個按鈕的處理函數與響應相應的菜單項的處理函數在使用COMMAND_ID_HANDLER_EX宏上沒有區別,你可以查看例子工程的代碼了解這些消息處理的細節。在下一節我將介紹狀態條和工具條按鈕的UI狀態更新,使它們能夠反映工具條或狀態條當前的狀態。
工具條按鈕的UI狀態更新
向導生成的代碼已經為CMainFrame添加了對View|Toolbar和View|Status Bar兩個菜單項的Check和Uncheck的UI更新處理。這和第二章的程序一樣:對CMainFrame類的兩個命令使用UI更新的宏:
BEGIN_UPDATE_UI_MAP(CMainFrame)
UPDATE_ELEMENT(ID_VIEW_TOOLBAR, UPDUI_MENUPOPUP)
UPDATE_ELEMENT(ID_VIEW_STATUS_BAR, UPDUI_MENUPOPUP)
END_UPDATE_UI_MAP()
我們的時鐘程序的工具條按鈕與對應的菜單項有相同的ID,所以第一步就是為每個宏添加UPDUI_TOOLBAR標志:
BEGIN_UPDATE_UI_MAP(CMainFrame)
UPDATE_ELEMENT(ID_VIEW_TOOLBAR, UPDUI_MENUPOPUP | UPDUI_TOOLBAR)
UPDATE_ELEMENT(ID_VIEW_STATUS_BAR, UPDUI_MENUPOPUP | UPDUI_TOOLBAR)
END_UPDATE_UI_MAP()
還需要添加兩個函數響應工具條按鈕的更新,但幸運的是向導已經為我們做了,所以如果此時編譯這個程序,菜單項和工具條按鈕都會更新。
使一個工具條支持UI狀態更新
如果查看CMainFrame::OnCreate()的代碼你就會發現一段新的代碼,這段代碼設置了兩個菜單項的初始狀態:
LRESULT CMainFrame::OnCreate( ... )
{
// ...
m_hWndClient = m_view.Create(...);
UIAddToolBar(m_hWndToolBar);
UISetCheck(ID_VIEW_TOOLBAR, 1);
UISetCheck(ID_VIEW_STATUS_BAR, 1);
// ...
}
UIAddToolBar()將工具條的窗口句柄傳給CUpdateUI,所以當需要更新按鈕的狀態時CUpdateUI會向這個窗口發消息。另一個重要的調用位於OnIdle()中:
BOOL CMainFrame::OnIdle()
{
UIUpdateToolBar();
return FALSE;
}
當消息隊列中沒有消息等待時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(...)
{
HWND hWndToolBar = CreateSimpleToolBarCtrl ( m_hWnd,
IDR_MAINFRAME, FALSE,
ATL_SIMPLE_TOOLBAR_PANE_STYLE );
CreateSimpleReBar(ATL_SIMPLE_REBAR_NOBORDER_STYLE);
AddSimpleReBarBand(hWndToolBar);
// ...
}
代碼從創建工具條開始,只是使用了不同的風格,也就是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 ...
{
//...
protected:
CMultiPaneStatusBarCtrl m_wndStatusBar;
};
接著在OnCreate()中創建狀態條並這只UI更新:
m_hWndStatusBar = m_wndStatusBar.Create ( *this );
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.
int anPanes[] = { ID_DEFAULT_PANE, IDPANE_STATUS,
IDPANE_CAPS_INDICATOR };
m_wndStatusBar.SetPanes ( anPanes, 3, false );
IDPANE_STATUS對應的字符串是“@@@@”,這樣應該有足夠的寬度(希望是)顯示兩個時鐘狀態字符串“Running”和“Stopped”。和MFC一樣,你需要自己估算窗格的寬度,IDPANE_CAPS_INDICATOR對應的字符串是“CAPS”。
窗格的UI狀態更新
為了更新窗格上的文本,我們需要將相應的窗格添加到UI更新表:
BEGIN_UPDATE_UI_MAP(CMainFrame)
//...
UPDATE_ELEMENT(1, UPDUI_STATUSBAR) // clock status
UPDATE_ELEMENT(2, UPDUI_STATUSBAR) // CAPS indicator
END_UPDATE_UI_MAP()
這個宏的第一個參數是窗格的索引而不是ID,這很不幸,因為如果你重新排列了窗格,你要記得更新UI更新表。
由於我們在調用SetPanes()是第三個參數是false,所以窗格初始是空的。我們下一步要做得就是將時鐘狀態窗格的初始文本設為“Running”
// Set the initial text for the clock status pane.
UISetText ( 1, _T("Running") );
和前面一樣,第一個參數是窗格的索引。UISetText()是狀態條唯一支持的UI更新函數。
最後,在CMainFrame::OnIdle()中添加對UIUpdateStatusBar()函數的調用,使狀態條的窗格能夠在空閒時間被更新:
BOOL CMainFrame::OnIdle()
{
UIUpdateToolBar();
UIUpdateStatusBar();
return FALSE;
}
當你使用UIUpdateStatusBar()時CUpdateUI的一個問題就暴露出來了--菜單項的文本在調用UISetText()後沒有改變!如果你在看WTLClock3工程的代碼,時鐘的開始/停止菜單項被移到了Clock菜單,在菜單項命令的響應處理函數中設置菜單項的文本。無論如何,如果當前調用的是UIUpdateStatusBar(),那麼對UISetText()的調用就不會起作用。我沒有研究這個問題是否可以被修復,所以如果你打算改變菜單的文本,你需要留意這個地方。
最後,我們需要檢查CAPS LOCK鍵的狀態,更新相應的兩個窗格。這些代碼是通過OnIdle()被調用的,所以程序會在每次空閒時間檢查它們的狀態。
BOOL CMainFrame::OnIdle()
{
// 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("") );
UIUpdateToolBar();
UIUpdateStatusBar();
return FALSE;
}
第一次調用UISetText()時將從字符串資源中裝載“CAPS”字符串,但是在CString的構造函數中使用了一個機靈的竅門(有充分的文檔說明)。
在完成所有的代碼之後,狀態條看起來是這個樣子:
承上啟下:有關對話框的話題
在第四章我將介紹對話框的用法(包括ATL的類和WTL的增強功能),控件的包裝類和WTL有關對話框消息處理的改進。
引用和參考
"How to use the WTL multipane status bar control" by Ed Gadziemski 更詳細的介紹了CMultiPaneStatusBarCtrl類的用法。 修改記錄
2003年4月11日,本文第一次發表。