Toolbars and Status Bars in a Frame
CFrameWindowImpl有个三个HWND对象成员:
1. m_hWndClient 子窗体HWND
2. m_hWndToolBar 工具栏或rebar HWND
3. m_hWndStatusBar 状态栏HWND
CFrameWindowImpl只支持一个工具栏。如果需要使用多个工具栏的话需要用rebar。
CFrameWindowImpl::OnSize()中调用UpdateLayout()时做了两件事:设置bar的位置、调整视图窗口大小以符合当前客户区域。
AppWizard Code for Toolbars and Stauts Bars
创建SDI工程的时候选择特征: Toolbar、Status Bar,这样主窗体上就加上了这两个bar。
How CMainWindow creates the bars
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;
}
BOOL CFrameWindowImpl::CreateSimpleToolBar(
UINT nResourceID = 0, // 0表示使用DECLARE_FRAME_WND_CLASS宏中指定的ID
DWORD dwStyle = ATL_SIMPLE_TOOLBAR_STYLE, // TBSTYLE_TOOLTIPS | usual child | visible styles
UINT nID = ATL_IDW_TOOLBAR) // 工具栏的Window ID,一般使用默认值
{
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);
}
BOOL CFrameWindowImpl::CreateSimpleStatusBar(
UINT nTextID = ATL_IDS_IDLEMESSAGE, // “Ready” 字符串
DWORD dwStyle = ... SBARS_SIZEGRIP, // 状态栏样式,在右侧显示拖动区域
UINT nID = ATL_IDW_STATUS_BAR) // 状态栏Window ID
{
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);
}
调用重载方法:
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);
}
Showing and hiding the bars
View菜单中有两个菜单控制toolbar和statusbar的show/hide。有相应的处理函数,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;
}
Built-in features of the bars
CFrameWindowImple中实现了MFC中相同的工具栏和状态栏特征,如:工具提示(tooltips for toolbar buttons)、菜单功能提示(flyby help for menu items)。
CFrameWindowImplBase中由两个消息处理来实现这两个特征:OnMenuSelect()、OnToolTipTextA()/OnToolTipTextW()。它们从字符串表中依据相同ID获取相同菜单/工具的提示串,OnMenuSelect获取/n前的字符串作为菜单提示,/n后的字符串作为工具提示。
Create a toolbar with a different style
如果希望创建不同样式的工具栏,只要在CMainwindow::OnCreate调用CreateSimpleToolBar时传递样式参数。例如,创建3D按钮的工具:
CreateSimpleToolBar(0, ATL_SIMPLE_TOOLBAR_STYLE | TBSTYLE_FLAT | TBSTYLE_LIST);
添加工具,通过COMMAND_ID_HANDLER_EX映射消息。
UI Updating Toolbar Buttons
相同菜单项和工具项(拥有相同的ID)采用相同的更新映射
BEGIN_UPDATE_UI_MAP(CMainWindow)
UPDATE_ELEMENT(ID_VIEW_TOOLBAR, UPDUI_MENUPOPUP | UPDUI_TOOLBAR)
UPDATE_ELEMENT(ID_VIEW_STATUS_BAR, UPDUI_MENUPOPUP | UPDUI_TOOLBAR)
END_UPDATE_UI_MAP()
Enabling toolbar UI updating
LRESULT CMainFrame::OnCreate( ... )
{
// ...
m_hWndClient = m_view.Create(...);
UIAddToolBar(m_hWndToolBar); // 告诉CUpdateUI toolbar的HWND,这样当需要更新按钮状态时向哪个窗体发送消息
UISetCheck(ID_VIEW_TOOLBAR, 1);
UISetCheck(ID_VIEW_STATUS_BAR, 1);
// ...
}
除了OnCreate,另外需要注意的是OnIdle(该方法在消息空闲状态时被触发,CMessageLoop中调用)
BOOL CMainFrame::OnIdle()
{
UIUpdateToolBar();
return FALSE;
}
注意:如果仅更新菜单的话(没有工具栏需要更新)的话不需要这两步,因为CUpdateUI处理了WM_INITMENUPOPUP消息,并且在更新消息来的时候更新菜单。
Using a Rebar Instead of a Plain Toolbar
LRESULT CMainFrame::OnCreate(...)
{
HWND hWndToolBar = CreateSimpleToolBarCtrl ( m_hWnd,
IDR_MAINFRAME, FALSE,
ATL_SIMPLE_TOOLBAR_PANE_STYLE ); // 创建工具栏,采用不同的样式,相当于ATL_SIMPLE_TOOLBAR_STYLE | CSS_NOPARENTALIGN
CreateSimpleReBar(ATL_SIMPLE_REBAR_NOBORDER_STYLE); // 创建rebar
AddSimpleReBarBand(hWndToolBar); // 将toolbar添加到rebar
// ...
}
CMainFrame::OnViewToolBar()中隐藏工具栏,如果还是m_hWndToolBar
的话就会隐藏整个rebar.
Multi-Pane Status Bars
WTL还有另外一个多区域状态栏类CMultiPaneStatusBarCtrl,该类提供了优先的UI更新。
class CMainFrame : public ...
{
//...
protected:
CMultiPaneStatusBarCtrl m_wndStatusBar;
};
在OnCreate中
m_hWndStatusBar = m_wndStatusBar.Create ( *this );
UIAddStatusBar ( m_hWndStatusBar );
设置状态栏区域,调用CMultiPaneStatusBarCtrl::SetPanes()
BOOL SetPanes(int* pPanes, // 状态区域ID数组,其实是字符串资源ID,资源值宽度决定了区域宽度
int nPanes, // 区域个数
bool bSetText = true); // 是否设置初始值
// Create the status bar panes.
int anPanes[] = { ID_DEFAULT_PANE, IDPANE_STATUS,
IDPANE_CAPS_INDICATOR };
m_wndStatusBar.SetPanes ( anPanes, 3, false );
UI updating the panes
填写消息映射
BEGIN_UPDATE_UI_MAP(CMainFrame)
UPDATE_ELEMENT(1, UPDUI_STATUSBAR) // CLOCK STATUS
UPDATE_ELEMENT(2, UPDUI_STATUSBAR) // CAPS indicator
END_UPDATE_UI_MAP()
注意:第一个参数不是ID,而是状态栏中区域的索引号(从0开始)。如果区域顺序改变,映射宏中序号也要同时改变。
因为在调用SetPanes()的时候第三个参数是false,状态栏区域初始为空,现在设置区域文本:
// Set the initial text for the clock status pane
UISetText(1, _T(“Running”));
为了在空闲时更新状态栏,需要在CMainFrame::OnIdle()中调用UIUpdateStatusBar()
BOOL CMainFrame::OnIdle()
{
UIUpdateToolBar();
UIUpdateStatusBar();
return FALSE;
}
存在BUG:当调用了UIUpdateStatusBar(),通过UISetText()修改菜单项文本就不起作用了。
在状态栏显示CAPS LOCK的状态:
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;
}