WTL入门(3)---工具条和状态条

源代码下载:http://download.csdn.net/source/3522797

Toolbars and Status Bars in a Frame

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可以自动生成工具条和状态条。

How CMainFrame creates the bars

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
工具条资源ID。默认值0意味着用 DECLARE_FRAME_WND_CLASS macro指定的资源。即AppWizard自动生成的 IDR_MAINFRAME.
dwStyle
工具条风格。 默认值 ATL_SIMPLE_TOOLBAR_STYLE 表示普通可见的,鼠标放在按钮上时有toolTip。
nID
Window ID for the toolbar, you will usually use the default value.

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。

Showing and hiding the bars

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;
}

Built-in features of the bars

MFC为工具栏和状态栏提供了一些好的特性。WTL的CFrameWindowImpl也实现了相应的特性,如tooltip和flyby help:

CFrameWindowImplBase 有两个消息处理用于实现这些特性。OnMenuSelect() 处理消息WM_MENUSELECT,它像MFC一样查找flyby help字符串(从字符串资源中加载与当前选择的菜单项同ID的字符串,并搜索"\n"字符,用\n之前的字符串作为flyby help字符串)。OnToolTipTextA()和OnToolTipTextW()处理消息TTN_GETDISPINFOATTN_GETDISPINFOW ,分别为工具栏按钮提供Tooltip 文本,它用与OnMenuSelect一样的方法加载字符串,但是它用\n之后的文字作为ToolTip text。
注意:在WTL7.0/7.1中,OnMenuSelect()OnToolTipTextA() 不是DBCS(双字节字符集)安全的,在搜索\n字符时,它们不检查DBCS的lead/trail(前导/后继)字节。

Creating a toolbar with a different style

你可以通过改变CreateSimpleToolBar ()中第二个参数的值,实现改变工具栏的风格。

CreateSimpleToolBar ( 0, ATL_SIMPLE_TOOLBAR_STYLE | 
                               TBSTYLE_FLAT | TBSTYLE_LIST );

The Toolbar Editor

AppWizard 会自动为我们生成一个工具条,只有帮助按钮是实现了的。我们可以向MFC工程一样修改工具条。此处略。
修改工具栏如下所示:

  • IDC_CP_COLORS: 改变视图颜色为CodeProject颜色
  • IDC_BW_COLORS: 改变视图为白色背景,黑色前景的颜色
  • ID_VIEW_STATUS_BAR: 显示或隐藏状态栏
  • ID_VIEW_TOOLBAR: 显示或隐藏工具栏

前两个按钮的ID是新添的,绑定命令消息要使用宏COMMAND_ID_HANDLER_EX

UI Updating Toolbar Buttons

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生成的代码会调用它们的。 

Enabling toolbar UI updating

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()。

Using a Rebar Instead of a Plain Toolbar

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控件,它不支持可停靠的工具栏。

Multi-Pane Status Bars

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 updating the panes

为了更新状态栏文字,需要写入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

你可能感兴趣的:(ATL/WTL)