话说昨天解决了MFC跨线程操作控件的问题,我满以为今天可以free一回,玩玩Linux、学学Vim、再准备一下毕业论文的事情,但还是有事情要做,然后又是“被”MFC郁闷了一天。
先介绍一下总体的情况。我们项目客户端的开发环境是VS2008+SP1,用的是MFC类库,里面居然用到了CMFCToolBar、CMFCMenuBar以及Appearance变化等的SPI新特性。说“居然”是因为这些东西不是项目必要的,当时可能也以为只是名字变了用法没变,估计在工程创建的时候根本就没有考虑这些,直接按着单文档工程默认配置,next、next直接创建完的,囧!当时做的时候也只是当作测试Demo来用,也没太在意,毕竟我们项目的重点在服务器而非这个MFC客户端。
后来由于项目原因,甲方要求我们把这个客户端尽快修改成一个可以发布版本。不改不知道,一改吓一跳,当准备动手修改工具栏时才发现与以前惯的CToolBar真实差距甚大。CToolBar可以用CImageList把自定义的BMP图片放到工具栏的按钮,详细可看 这里,CMFCToolBar根本就不是这样的一个玩法。直接放一个CToolBar上来,在DockControlBar()的时候会出现断言错误(缺少DockBar,貌似是这个名字,汗!)。定位代码到MainFrm的EnableDocking(),现在的MainFrm的继承关系是CMainFrm->CFrameWndEx->CFrameWnd,而以前是CMainFrm->CFrameWnd,CFrameWndEx::EnableDocking()是为DockPane()服务的,而DockControlBar()需要的DockBar并不会被初始化。调用基类的CFrameWnd::EnableDocking()后再DockControlBar()不会出现断言,但是那个工具栏没有显示。而且现在新特性下在工具栏位置能够按出右键菜单,但右键菜单中根本不可能有关于该CToolBar的信息,乍看起来很不和谐~
最后,求助本地MSDN无果,貌似SP1没有包含对MSDN文档的更新;求助MSDN官网,那个真是“言简意赅”。只能说,MS你这次真的“亮”了!
以下为google + vs2008 sp1 sample + 看代码的成果:
注意,Wizard生成的工具栏Create时没有带ID,但第二个工具栏Create时最好要带ID。加了ID之后,在工具栏右键菜单才会出现第二个工具栏的CheckBox。否则,不良后果有:1、右键菜单没有该工具栏Checkbox;2、把默认工具栏和该工具栏拖出来(浮动),可以看到名字都是一样的(英文版为Standard);3、后面要提到的UserImage不能作为按钮图标显示。
我的Demo里自定义工具栏的总创建过程:
CMFCToolBar有LoadBitmap的方法,但是测试发现,用LoadToolBar只加载工具栏资源,再用LoadBitmap加载BMP资源,虽然返回值是TRUE,但显示图标为空白,没有实际效果。
使用CMFCToolBar::ReplaceButton()可以替换已有的工具栏按钮,以下是我的Demo中的代码:
GetCmdMgr() -> GetCmdImage()可以根据工具栏上图标的ID获取出已加载图标的索引值:
特别地,如果在你将这些工具栏改来改去但显示结果却没有改变的时候,你可以尝试删除 HKEY_CURRENT_USER\Software\Local AppWizard-Generated Applications\$(你的程序名) 这个键值,当你重启程序后工具栏应该会按你的预想变化的。这是我在查资料时看到的,当时没注意但后来发现挺有用的,出处没有记录下来。
最后,ReplaceButton还可以将按钮替换为其他控件。
注意代码中,默认工具栏图标重新加载时使用的资源是IDR_MAINFRAME_256,是默认的工具栏资源。也就是说,这里用LoadBitmap加载工具栏资源也是有效果的。这样应该可以说明工具栏在创建时LoadToolBar、LoadBitmap分别成功地加载了工具栏、BMP资源,实际上是加载了两套图标资源,这两者是顺序而非重合的,所以只显示原来的工具栏资源。要想指定两者的重合关系,只有在LoadToolBar的时候同时传入工具栏资源及BMP资源的ID。
Demo下载
————————————————————————————————————————————————————————————————
好吧,终于写完了!写得很仓促,不足的地方也很多,欢迎指教!
先介绍一下总体的情况。我们项目客户端的开发环境是VS2008+SP1,用的是MFC类库,里面居然用到了CMFCToolBar、CMFCMenuBar以及Appearance变化等的SPI新特性。说“居然”是因为这些东西不是项目必要的,当时可能也以为只是名字变了用法没变,估计在工程创建的时候根本就没有考虑这些,直接按着单文档工程默认配置,next、next直接创建完的,囧!当时做的时候也只是当作测试Demo来用,也没太在意,毕竟我们项目的重点在服务器而非这个MFC客户端。
后来由于项目原因,甲方要求我们把这个客户端尽快修改成一个可以发布版本。不改不知道,一改吓一跳,当准备动手修改工具栏时才发现与以前惯的CToolBar真实差距甚大。CToolBar可以用CImageList把自定义的BMP图片放到工具栏的按钮,详细可看 这里,CMFCToolBar根本就不是这样的一个玩法。直接放一个CToolBar上来,在DockControlBar()的时候会出现断言错误(缺少DockBar,貌似是这个名字,汗!)。定位代码到MainFrm的EnableDocking(),现在的MainFrm的继承关系是CMainFrm->CFrameWndEx->CFrameWnd,而以前是CMainFrm->CFrameWnd,CFrameWndEx::EnableDocking()是为DockPane()服务的,而DockControlBar()需要的DockBar并不会被初始化。调用基类的CFrameWnd::EnableDocking()后再DockControlBar()不会出现断言,但是那个工具栏没有显示。而且现在新特性下在工具栏位置能够按出右键菜单,但右键菜单中根本不可能有关于该CToolBar的信息,乍看起来很不和谐~
最后,求助本地MSDN无果,貌似SP1没有包含对MSDN文档的更新;求助MSDN官网,那个真是“言简意赅”。只能说,MS你这次真的“亮”了!
以下为google + vs2008 sp1 sample + 看代码的成果:
- 创建默认ToolBar外的第二个ToolBar
1 //默认工具栏
2 m_wndToolBar.CreateEx(this, TBSTYLE_FLAT,
3 WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);
4 //自定义工具栏
5 m_mybar.CreateEx( this , TBSTYLE_FLAT,
6 WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC,
7 CRect( 1 , 1 , 1 , 1 ), ID_MYBAR);
2 m_wndToolBar.CreateEx(this, TBSTYLE_FLAT,
3 WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);
4 //自定义工具栏
5 m_mybar.CreateEx( this , TBSTYLE_FLAT,
6 WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC,
7 CRect( 1 , 1 , 1 , 1 ), ID_MYBAR);
注意,Wizard生成的工具栏Create时没有带ID,但第二个工具栏Create时最好要带ID。加了ID之后,在工具栏右键菜单才会出现第二个工具栏的CheckBox。否则,不良后果有:1、右键菜单没有该工具栏Checkbox;2、把默认工具栏和该工具栏拖出来(浮动),可以看到名字都是一样的(英文版为Standard);3、后面要提到的UserImage不能作为按钮图标显示。
- 加载工具栏资源
1
virtual
BOOL LoadToolBar(UINT uiResID, UINT uiColdResID
=
0
, UINT uiMenuResID
=
0
, BOOL bLocked
=
FALSE,
2 UINT uiDisabledResID = 0 , UINT uiMenuDisabledResID = 0 , UINT uiHotResID = 0 );
可以看出,uiResID代表要加载的工具栏资源,理论上只需要这一个参数就能完成工具栏的加载。但是VS的Toolbar Editor只能编辑4bit的工具栏图标,以前CToolBar是用CImagList来加载更多bits的图标的,现在应该怎么做呢?多亏了Explore sample的例子,我发现后面的几个UINT参数就是BMP的资源,最主要的是最后一个uiHotResID,即便其他用默认值,这项赋BMP ID就能按预期的图标显示。Cold、Disable表示的是不同状态下的图标样式,带Menu的是Menu有关的图标,具体可看SP1 Feature的sample。
2 UINT uiDisabledResID = 0 , UINT uiMenuDisabledResID = 0 , UINT uiHotResID = 0 );
我的Demo里自定义工具栏的总创建过程:
1
if
(
!
m_mybar.CreateEx(
this
, TBSTYLE_FLAT,
2 WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC,
3 CRect( 1 , 1 , 1 , 1 ), ID_MYBAR) ||
4 ! m_mybar.LoadToolBar( IDR_TOOLBAR1, 0 , 0 , FALSE, 0 , 0 , theApp.m_bHiColorIcons ? IDB_BITMAP1: 0 ) )
5 {
6 TRACE0( " Failed to create toolbar\n " );
7 return - 1 ; // fail to create
8 }
9 m_mybar.SetWindowText(_T( " abc " ));
最后的SetWindowText()设置工具栏的名称。
2 WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC,
3 CRect( 1 , 1 , 1 , 1 ), ID_MYBAR) ||
4 ! m_mybar.LoadToolBar( IDR_TOOLBAR1, 0 , 0 , FALSE, 0 , 0 , theApp.m_bHiColorIcons ? IDB_BITMAP1: 0 ) )
5 {
6 TRACE0( " Failed to create toolbar\n " );
7 return - 1 ; // fail to create
8 }
9 m_mybar.SetWindowText(_T( " abc " ));
CMFCToolBar有LoadBitmap的方法,但是测试发现,用LoadToolBar只加载工具栏资源,再用LoadBitmap加载BMP资源,虽然返回值是TRUE,但显示图标为空白,没有实际效果。
- 工具栏停靠
1 //
TODO: Delete these five lines if you don't want the toolbar and menubar to be dockable
2 m_wndMenuBar.EnableDocking(CBRS_ALIGN_ANY);
3 m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
4 m_mybar.EnableDocking(CBRS_ALIGN_ANY);
5 EnableDocking(CBRS_ALIGN_ANY);
6 DockPane( & m_wndMenuBar);
7 DockPane( & m_wndToolBar);
8 DockPane( &m_mybar);
与默认工具栏无异。
2 m_wndMenuBar.EnableDocking(CBRS_ALIGN_ANY);
3 m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
4 m_mybar.EnableDocking(CBRS_ALIGN_ANY);
5 EnableDocking(CBRS_ALIGN_ANY);
6 DockPane( & m_wndMenuBar);
7 DockPane( & m_wndToolBar);
8 DockPane( &m_mybar);
- 用户自定义图标
1
if
(CMFCToolBar::GetUserImages()
==
NULL)
2 {
3 // load user-defined toolbar images
4 if (m_UserImages.Load(_T( " .\\UserImages.bmp " )))
5 {
6 m_UserImages.SetImageSize(CSize( 16 , 16 ), FALSE);
7 CMFCToolBar::SetSizes(CSize( 16 , 16 ), CSize( 16 , 16 ));
8 CMFCToolBar::SetUserImages( & m_UserImages);
9 }
10 }
这个例子加载了工程路径下的一个BMP,其他方法可以查看MSDN,与CImageList有点点类似。
2 {
3 // load user-defined toolbar images
4 if (m_UserImages.Load(_T( " .\\UserImages.bmp " )))
5 {
6 m_UserImages.SetImageSize(CSize( 16 , 16 ), FALSE);
7 CMFCToolBar::SetSizes(CSize( 16 , 16 ), CSize( 16 , 16 ));
8 CMFCToolBar::SetUserImages( & m_UserImages);
9 }
10 }
使用CMFCToolBar::ReplaceButton()可以替换已有的工具栏按钮,以下是我的Demo中的代码:
1
m_mybar.ReplaceButton( ID_QTLOGO, CMFCToolBarButton(ID_QTLOGO,
0
, _T(
"
123
"
), TRUE) );
第一个参数ID_QTLOGO为自定义工具栏上的一个按钮,后面是一个
CMFCToolBarButton的临时对象。
CMFCToolBarButton构造函数第一个参数为替换后的ID,第三个参数为名称,第二个参数为图标的索引(zero-based),第四个参数为m_bUserButton,指明第二个参数是索引工具栏已加载图标(LoadToolBar或LoadBitmap)还是用户自定义图标(SetuserImages),TRUE指用户自定义图标。这里的结果是将ID_QTLOGO上的图标替换为
UserImages.bmp上的第一个图标。
GetCmdMgr() -> GetCmdImage()可以根据工具栏上图标的ID获取出已加载图标的索引值:
1
m_mybar.ReplaceButton( ID_QTLOGO, CMFCToolBarButton(ID_QTLOGO, GetCmdMgr()
->
GetCmdImage(ID_PLUS), _T(
"
123
"
)) );
这里将工具栏上ID_QTLOGO的图标替换为ID_PLUS按钮对应的图标。
特别地,如果在你将这些工具栏改来改去但显示结果却没有改变的时候,你可以尝试删除 HKEY_CURRENT_USER\Software\Local AppWizard-Generated Applications\$(你的程序名) 这个键值,当你重启程序后工具栏应该会按你的预想变化的。这是我在查资料时看到的,当时没注意但后来发现挺有用的,出处没有记录下来。
最后,ReplaceButton还可以将按钮替换为其他控件。
- 其他...
1
void
CMainFrame::OnQtLogo()
2 {
3 CMFCToolBar::ResetAllImages();
4
5 // CMFCToolBar::AddToolBarForImageCollection(IDR_MENU_IMAGES, theApp.m_bHiColorIcons ? IDB_MENU_IMAGES_24 : 0);
6
7 m_wndToolBar.LoadBitmap(IDB_BITMAP1);
8 m_mybar.LoadBitmap(IDR_MAINFRAME_256);
9 m_wndToolBar.RedrawWindow();
10 m_mybar.RedrawWindow();
11 }
更奇妙的是,后面我对两个工具栏重新加载了BMP,而且加载的BMP资源是反了的,此时默认工具栏上出现了原来自定义工具栏的4个图标,余下部分及自定义工具栏则为原来默认工具栏图标。可以想象,RestAllImages只是将图标资源都释放了,工具栏资源依然健在,重新加载BMP的时候,工具栏图标就像一个个顺序排好的空间,加载进来的BMP图标会出现从前往后补位的现象。
2 {
3 CMFCToolBar::ResetAllImages();
4
5 // CMFCToolBar::AddToolBarForImageCollection(IDR_MENU_IMAGES, theApp.m_bHiColorIcons ? IDB_MENU_IMAGES_24 : 0);
6
7 m_wndToolBar.LoadBitmap(IDB_BITMAP1);
8 m_mybar.LoadBitmap(IDR_MAINFRAME_256);
9 m_wndToolBar.RedrawWindow();
10 m_mybar.RedrawWindow();
11 }
注意代码中,默认工具栏图标重新加载时使用的资源是IDR_MAINFRAME_256,是默认的工具栏资源。也就是说,这里用LoadBitmap加载工具栏资源也是有效果的。这样应该可以说明工具栏在创建时LoadToolBar、LoadBitmap分别成功地加载了工具栏、BMP资源,实际上是加载了两套图标资源,这两者是顺序而非重合的,所以只显示原来的工具栏资源。要想指定两者的重合关系,只有在LoadToolBar的时候同时传入工具栏资源及BMP资源的ID。
Demo下载
————————————————————————————————————————————————————————————————
好吧,终于写完了!写得很仓促,不足的地方也很多,欢迎指教!