源代码下载:http://download.csdn.net/source/3522797
CFrameWindowImpl包含三个HWND对象,其中一个m_hWndClient上节已经讲过,另外两个m_hWndToolBar用于表示工具条句柄,m_hWndStatusBar用于状态条句柄。
CFrameWindowImpl仅支持一个工具条,不能像MFC那样直接支持多个可停靠的工具条。如果想实现这样的效果,需要使用Rebar。
CFrameWindowImpl::OnSize()将调用UpdateLayout()做两件事情:1、定位bars;2、缩放view窗口以填充视图区域。UpdateLayout()调用UpdateBarsPosition()执行实际的工作,它仅仅发送WM_SIZE到已创建的工具条和状态条,默认处理是把工具条移动Frame窗口的顶端,把状态条移至Frame窗口的低端。
AppWizard可以自动生成工具条和状态条。
AppWizard会自动生成一些代码,用于创建Bars以及告诉CUpdateUI更新toolbar的buttons。
LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { // 通过工具条资源 IDR_MAINFRAME 创建新的工具条 CreateSimpleToolBar(); CreateSimpleStatusBar(); m_hWndClient = m_view.Create(m_hWnd, rcDefault, NULL, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, WS_EX_CLIENTEDGE); UIAddToolBar(m_hWndToolBar); UISetCheck(ID_VIEW_TOOLBAR, 1); UISetCheck(ID_VIEW_STATUS_BAR, 1); // 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()的源代码
BOOL CreateSimpleToolBar(UINT nResourceID = 0, DWORD dwStyle = ATL_SIMPLE_TOOLBAR_STYLE, UINT nID = ATL_IDW_TOOLBAR) { if(nResourceID == 0) nResourceID = T::GetWndClassInfo().m_uCommonResourceID; #ifndef _WIN32_WCE ATLASSERT(!::IsWindow(m_hWndToolBar)); m_hWndToolBar = T::CreateSimpleToolBarCtrl(m_hWnd, nResourceID, TRUE, dwStyle, nID); return (m_hWndToolBar != NULL); #else // CE specific HWND hWnd= T::CreateSimpleToolBarCtrl(m_hWndCECommandBar, nResourceID, TRUE, dwStyle, nID); return (hWnd != NULL); #endif // _WIN32_WCE }
nResourceID
DECLARE_FRAME_WND_CLASS
macro指定的资源。即AppWizard自动生成的
IDR_MAINFRAME.
dwStyle
ATL_SIMPLE_TOOLBAR_STYLE 表示普通可见的,鼠标放在按钮上时有toolTip。
nID
CreateSimpleToolBar()
检查这个工具条有没有已经被创建,然后调用 CreateSimpleToolBarCtrl()
创建控件,并返回控件句柄存入m_hWndToolBar。
CreateSimpleToolBarCtrl()
读取资源并逐个创建按钮,然后返回toolbar窗口句柄。
以下是CreateSimpleToolBar的实现代码:
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创建状态条,并把状态条句柄存入hWndStatusBar。
CMainFrame
在 “视图”菜单下有两个命令,用于显示 / 隐藏工具栏和状态栏。
其中工具栏的命令响应代码如下:
// 切换工具栏显示或隐藏,切换命令栏显示菜单状态,调用UpdateLayout定位命令栏位置(显示时)并更新视图区域大小 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; }
MFC为工具栏和状态栏提供了一些好的特性。WTL的CFrameWindowImpl
也实现了相应的特性,如tooltip和flyby help:
CFrameWindowImplBase
有两个消息处理用于实现这些特性。OnMenuSelect()
处理消息WM_MENUSELECT
,它像MFC一样查找flyby help字符串(从字符串资源中加载与当前选择的菜单项同ID的字符串,并搜索"\n"字符,用\n之前的字符串作为flyby help字符串)。OnToolTipTextA()和
一样的方法加载字符串,但是它用\n之后的文字作为ToolTip text。OnToolTipTextW()处理消息
TTN_GETDISPINFOA
和TTN_GETDISPINFOW
,分别为工具栏按钮提供Tooltip 文本,它用与OnMenuSelect
注意:在WTL7.0/7.1中,OnMenuSelect()
和 OnToolTipTextA()
不是DBCS(双字节字符集)安全的,在搜索\n字符时,它们不检查DBCS的lead/trail(前导/后继)字节。
你可以通过改变CreateSimpleToolBar ()中第二个参数的值,实现改变工具栏的风格。
CreateSimpleToolBar ( 0, ATL_SIMPLE_TOOLBAR_STYLE | TBSTYLE_FLAT | TBSTYLE_LIST );
AppWizard 会自动为我们生成一个工具条,只有帮助按钮是实现了的。我们可以向MFC工程一样修改工具条。此处略。
修改工具栏如下所示:
IDC_CP_COLORS
: 改变视图颜色为CodeProject颜色 IDC_BW_COLORS
: 改变视图为白色背景,黑色前景的颜色 ID_VIEW_STATUS_BAR
: 显示或隐藏状态栏ID_VIEW_TOOLBAR
: 显示或隐藏工具栏前两个按钮的ID是新添的,绑定命令消息要使用宏COMMAND_ID_HANDLER_EX
。
AppWizard生成的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()
另外,还有两个函数用于更新工具栏按钮,AppWizard生成的代码会调用它们的。
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,因此当需要更新按钮状态时它知道往哪个窗口发送消息。另一个重要的调用是
:OnIdle()
BOOL CMainFrame::OnIdle() { UIUpdateToolBar(); return FALSE; }
当消息队列中没有消息时,CMessageLoop::Run()
将重复调用OnIdle()
。UIUpdateToolBar()
通过Update UI map,搜索带UPDUI_TOOLBAR
标志的那些调用UISetCheck()改变的元素,并且因此改变按钮状态。注意:我们仅仅更新了push menu元素的状态,不需要去做这两步,因为
CUpdateUI
处理消息WM_INITMENUPOPUP
并且当消息送达时直接更新了菜单状态。
对于菜单栏的更新,要使用UIAddMenuBar()
和 UIUpdateMenuBar()。
CFrameWindowImpl
支持使用Rebar使我们的程序看起来像Internet Explorer。(在用Appwizard创建新工程时,选择使用Rebar)
下面是AppWizard生成的创建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
创建一个toolbar,与ATL_SIMPLE_TOOLBAR_STYLE
相比,ATL_SIMPLE_TOOLBAR_PANE_STYLE增加了
CCS_NOPARENTALIGN,它是必须的。
CreateSimpleReBar()创建一个Rebar,并把其句柄存入
m_hWndToolBar,然后调用
AddSimpleReBarBand()为这个Rebar添加一个Band,并且告诉Rebar,这个Band已经包含了这个工具栏。
CMainFrame::OnViewToolBar()
也是不一样的。它隐藏包含这个工具栏的Band,而不是这个Rebar(这样会隐藏整个rebar,而不仅仅是这一个Toolbar)。
如果你想有多个工具条,就像Appwizard生成的代码一样,在OnCreate()中调用
AddSimpleReBarBand()。既然
CFrameWindowImpl使用标准的Rebar控件,它不支持可停靠的工具栏。
WTL提供了一个实现多面板的状态栏类CMultiPaneStatusBarCtrl。它支持有限的UI更新,当PopUP 菜单显示时,默认面板会被拉伸到与状态栏同宽以显示flyby help文字。
首先要在CMainFrame类中声明一个变量
class CMainFrame : public ... { //... protected: CMultiPaneStatusBarCtrl m_wndStatusBar; };
然后,在OnCreate()中创建bar并设置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 :面板个数
bSetText
:如果为true, 所有盘子立即显示字符串. 解释如下.
面板IDs,既可以是ID_DEFAULT_PANE来创建用于显示flyby help 的面板,也可以是字符串表中的一组字符串的IDs
. 对于无默认面板的情况,WTL加载匹配ID的字符串并计算它们的宽度,然后为其对应的面板设置同样的宽度。与MFC使用的逻辑相同。
bSetText
控制面板是否立即显示字符串。 如果为true, 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更新路由:
BEGIN_UPDATE_UI_MAP(CMainFrame) //... UPDATE_ELEMENT(1, UPDUI_STATUSBAR) // clock status UPDATE_ELEMENT(2, UPDUI_STATUSBAR) // CAPS indicator END_UPDATE_UI_MAP()
第一个参数是盘子的索引,不是ID。如果你需要重排列这些盘子,你需要更新设个参数以适应其对应的顺序。
我们在SetPanes的第三个参数传入的是false,因此所有盘子初始化均为空。下一步为指定盘子设置初始文字。
// 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()
后菜单文字并没有被更新。这个问题在WTL7.1中还存在。
最后,我们需要检查Caps Lock状态并更新2号盘的文字,在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以实现多格式支持。
原文:WTL for MFC Programmers, Part III - Toolbars and Status Bars