關於vs2008 SP1中CMFCToolBar的一些事兒

原文地址:http://www.cppblog.com/neverwinter/archive/2010/05/20/115984.html

先介紹一下總體的情況。我們項目客戶端的開發環境是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,
3WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY |CBRS_SIZE_DYNAMIC);

4//自定義工具欄
5m_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不能作爲按鈕圖標顯示。

  • 加載工具欄資源    

我們先來看看CMFCToolBar加載工具欄的函數原型:

1virtual BOOL LoadToolBar(UINT uiResID, UINT uiColdResID = 0, UINTuiMenuResID = 0, BOOL bLocked = FALSE,
2UINT uiDisabledResID = 0, UINT uiMenuDisabledResID = 0, UINTuiHotResID = 0);

可以看出,uiResID代表要加載的工具欄資源,理論上只需要這一個參數就能完成工具欄的加載。但是VS的Toolbar Editor只能編輯4bit的工具欄圖標,以前CToolBar是用CImagList來加載更多bits的圖標的,現在應該怎麼做呢?多虧了Explore sample的例子,我發現後面的幾個UINT參數就是BMP的資源,最主要的是最後一個uiHotResID,即便其他用默認值,這項賦BMP ID就能按預期的圖標顯示。Cold、Disable表示的是不同狀態下的圖標樣式,帶Menu的是Menu有關的圖標,具體可看SP1 Feature的sample。
我的Demo裏自定義工具欄的總創建過程:

1if ( !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");
7return -1;
// fail to create
8
}
9m_mybar.SetWindowText(_T("abc"));

最後的SetWindowText()設置工具欄的名稱。
CMFCToolBar有LoadBitmap的方法,但是測試發現,用LoadToolBar只加載工具欄資源,再用LoadBitmap加載BMP資源,雖然返回值是TRUE,但顯示圖標爲空白,沒有實際效果。

  • 工具欄停靠    

1// TODO: Delete these five lines if you don't want the toolbar and menubar tobe dockable
2
m_wndMenuBar.EnableDocking(CBRS_ALIGN_ANY);
3
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
4m_mybar.EnableDocking(CBRS_ALIGN_ANY);

5
EnableDocking(CBRS_ALIGN_ANY);
6
DockPane(&m_wndMenuBar);
7
DockPane(&m_wndToolBar);
8DockPane(&m_mybar);

與默認工具欄無異。

  • 用戶自定義圖標    

CMFCToolBar可以讓用戶自定義工具欄圖標,使用靜態成員函數SetUserImages()將一個CMFCToolBarImages對象設置進去,由所有CMFCToolBar對象共享。Wizard自動生成代碼中有這樣的例子:

1if (CMFCToolBar::GetUserImages() == NULL)
2
{
3
// load user-defined toolbar images
4if
(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有點點類似。
使用CMFCToolBar::ReplaceButton()可以替換已有的工具欄按鈕,以下是我的Demo中的代碼:

1m_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獲取出已加載圖標的索引值:

1m_mybar.ReplaceButton(ID_QTLOGO, CMFCToolBarButton(ID_QTLOGO, GetCmdMgr()->GetCmdImage(ID_PLUS),_T("123")) );

這裏將工具欄上ID_QTLOGO的圖標替換爲ID_PLUS按鈕對應的圖標。

特別地,如果在你將這些工具欄改來改去但顯示結果卻沒有改變的時候,你可以嘗試刪除HKEY_CURRENT_USER\Software\LocalAppWizard-Generated Applications\$(你的程序名)這個鍵值,當你重啓程序後工具欄應該會按你的預想變化的。這是我在查資料時看到的,當時沒注意但後來發現挺有用的,出處沒有記錄下來。

最後,ReplaceButton還可以將按鈕替換爲其他控件。

  • 其他...    

我在自定義工具欄上做了一個有效響應,裏面使用靜態成員函數CMFCToolBar::ResetAllImages()將所有圖標都清空了,此時會發現默認工具欄、自定義工具欄的圖標都爲空。

1void 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圖標會出現從前往後補位的現象。
注意代碼中,默認工具欄圖標重新加載時使用的資源是IDR_MAINFRAME_256,是默認的工具欄資源。也就是說,這裏用LoadBitmap加載工具欄資源也是有效果的。這樣應該可以說明工具欄在創建時LoadToolBar、LoadBitmap分別成功地加載了工具欄、BMP資源,實際上是加載了兩套圖標資源,這兩者是順序而非重合的,所以只顯示原來的工具欄資源。要想指定兩者的重合關係,只有在LoadToolBar的時候同時傳入工具欄資源及BMP資源的ID。

Demo下載

————————————————————————————————————————————————————————————————
好吧,終於寫完了!寫得很倉促,不足的地方也很多,歡迎指教!

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章