話說昨天解決了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 + 看代碼的成果:
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);
注意,Wizard生成的工具欄Create時沒有帶ID,但第二個工具欄Create時最好要帶ID。加了ID之后,在工具欄右鍵菜單才會出現第二個工具欄的CheckBox。否則,不良后果有:1、右鍵菜單沒有該工具欄Checkbox;2、把默認工具欄和該工具欄拖出來(浮動),可以看到名字都是一樣的(英文版為Standard);3、后面要提到的UserImage不能作為按鈕圖標顯示。
我們先來看看CMFCToolBar加載工具欄的函數原型:
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。
我的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()設置工具欄的名稱。
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);
與默認工具欄無異。
CMFCToolBar可以讓用戶自定義工具欄圖標,使用靜態成員函數SetUserImages()將一個CMFCToolBarImages對象設置進去,由所有CMFCToolBar對象共享。Wizard自動生成代碼中有這樣的例子:
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有點點類似。
使用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還可以將按鈕替換為其他控件。
我在自定義工具欄上做了一個有效響應,里面使用靜態成員函數CMFCToolBar::ResetAllImages()將所有圖標都清空了,此時會發現默認工具欄、自定義工具欄的圖標都為空。
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圖標會出現從前往后補位的現象。
注意代碼中,默認工具欄圖標重新加載時使用的資源是IDR_MAINFRAME_256,是默認的工具欄資源。也就是說,這里用LoadBitmap加載工具欄資源也是有效果的。這樣應該可以說明工具欄在創建時LoadToolBar、LoadBitmap分別成功地加載了工具欄、BMP資源,實際上是加載了兩套圖標資源,這兩者是順序而非重合的,所以只顯示原來的工具欄資源。要想指定兩者的重合關系,只有在LoadToolBar的時候同時傳入工具欄資源及BMP資源的ID。
Demo下載————————————————————————————————————————————————————————————————
好吧,終于寫完了!寫得很倉促,不足的地方也很多,歡迎指教!