1. 程式人生 > >演練VC中的COMMON一族(轉貼)之二

演練VC中的COMMON一族(轉貼)之二

第6章 演練CToolBar
6.1 工具條控制的主要功能
    所謂工具條就是具有點陣圖和分隔符組成的一組命令按鈕,點陣圖按鈕部分可以是下推按鈕、檢查盒按鈕、無線按鈕等。工具條物件類派生於主視窗架框類CframeWnd或CMDIFrameWnd,其類控制CToolBar::GetToolBarCtrl是MFC類庫中封裝的一個成員函式,允許使用類庫中提供的一般控制和附加功能,CtoolBar類控制成員控制提供了Windows一般控制的所有功能,然而,通過呼叫 GetToolBarCtrl成員函式取得引用後,可以使工具條具有更強的特性。
    工具條的建立具有四個步聚:首先是建立工具條資源;然後建立工具條物件結構;其次通過呼叫建立函式建立工具條物件並繫結;最後呼叫LoadToolBar調入工具條資源。
    另外,還可以通過直接載入點陣圖的方法來建立,步驟如下:首先建立工具條物件;然後通過呼叫建立函式建立工具條並繫結物件;其次調入包含按鈕的點陣圖;最後利用SetButtons 函式設定按鈕的風格並與點陣圖建立聯絡。
    其中,所有按鈕點陣圖均存放在一個位圖檔案中,按鈕點陣圖的大小相同,預設為16點寬、15點高,點陣圖必須從左至右存放。設定按鈕函式具有指向一組控制識別符號ID的指標和索引值,用來確定每個按鈕的位置,如果存在分隔符ID_SEPARATOR, 那麼該影象就不存在索引值。正常情況下工具條中的按鈕都是單排從左至右排列的,可以通過SetButtonInfo函式改變排序規則。 工具條中最終形成的按鈕大小相同,均為24 x 22 象素,每個按鈕只對象一幅影象。工具條中的按鈕預設為下推按鈕,通過設定TBBS_CHECKBOX風格可以實現檢查盒按鈕,通過呼叫SetRadio成員函式可以實現無線按鈕。
6.2 工具條控制的物件結構
6.2.1 工具條的物件結構
6.2.1.1 工具條的建立方法
    CToolBar &ToolBar  建立工具條物件結構
    Create              建立工具條物件並繫結
    工具條類CToolBar::Create 的呼叫格式如下:
    BOOL Create( CWnd* pParentWnd, DWORD dwStyle = WS_CHILD | WS_VISIBLE | CBRS_TOP,
    UINT nID = AFX_IDW_TOOLBAR );
    其中引數pParentWnd用來確定指向工具條父視窗的指標;引數dwStyle用來確定工具條的風格,其取值如下;引數nID用來確定工具條子視窗的識別符號。
    CBRS_TOP          表示工具條在框架視窗的頂部
    CBRS_BOTTOM       表示工具條在框架視窗的底部
    CBRS_NOALIGN      表示工具條在父視窗改變大小時不響應
    CBRS_TOOLTIPS     表示工具條具有動態提示功能
    CBRS_SIZE_DYNAMIC 表示工具條是靜態的不能改變
    CBRS_SIZE_FIXED   表示工具條是動態的可以改變
    CBRS_FLOATING     表示工具條是浮動的
    CBRS_FLYBY        表示狀態條上顯示工具條中按鈕的資訊
    CBRS_HIDE_INPLACE 表示工具條隱藏
    除以上函式外,還包括設定按鈕和點陣圖的大小SetSizes、設定工具條的高度SetHeight、調入工具條資源LoadToolBar、調入工具條按鈕點陣圖LoadBitmap、設定工具條按鈕點陣圖SetBitmap、設定工具條中點陣圖按鈕的風格和索引值SetButtons等控制函式。
6.2.1.2 工具條的類屬性
  工具條控制類的屬性包括取得識別符號ID物件按鈕索引CommandToIndex、取得索引對應的命令識別符號ID或分隔符GetItemID、取得索引對應的矩形區域GetItemRect、取得按鈕風格    GetButtonStyle、設定按鈕風格SetButtonStyle、取得按鈕的ID標識-風格-圖象數GetButtonInfo、設定按鈕ID標識-風格-圖象數SetButtonInfo、取得按鈕提示文字GetButtonText、設定按鈕提示文字SetButtonText和取得工具條直接存取控制GetToolBarCtrl等。
6.2.2 工具條控制的物件結構
6.2.2.1 工具條控制的建立方法
    CToolBarCtrl &ToolBarCtrl  建立工具條控制物件結構
    Create                     建立工具條控制物件並繫結
    工具條控制類CToolBarCtrl::Create的呼叫格式如下:
    BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );
    其中引數dwStyle用來確定工具條控制的風格,必須存在WS_CHILD風格;引數rect用來確定工具條控制的大小和位置;引數pParentWnd用來確定工具條控制的父視窗指標,不能為NULL;引數nID用來確定工具條控制的識別符號。
    可以利用WS_CHILD、WS_VISIBLE和WS_DISABLED來設定工具條視窗的風格,但必須合理設定如下控制風格:
CCS_ADJUSTABLE  允許使用者處理工具條視窗大小,如果存在工具條視窗必須處理相應信
CCS_BOTTOM      使控制處於父視窗客戶區域底部並與視窗同樣寬
CCS_NODIVIDER   禁止在控制的頂部繪製2個象素的高亮條
CCS_NOHILITE    禁止在控制的頂部繪製1個象素的高亮條
CCS_NOMOVEY     使控制改變大小和移動時自動水平對齊,垂直對齊必須處理WM_SIZE訊息
    如果CCS_NORESIZE風格有效,則該風格無效
CCS_NOPARENTALIGN禁止控制自動移到父視窗頂部或底部,如果CCS_TOP或 CCS_BOTTOM風格
    有效,則高度調整為預設而寬度可以改變
CCS_NORESIZE    禁止設定新的大小或無效值時使用預設寬度和高度值,而使用建立值
CCS_TOP         使控制自動停靠在父視窗客戶區域頂部並與父視窗同樣寬度
    最後,還必須利用下面的風格來控制工具條
TBSTYLE_TOOLTIPS   使工具條建立並管理動態提示控制
TBSTYLE_WRAPABLE   使工具條控制按鈕具有多行排列格式
6.2.2.2 工具條控制中的資料結構
    工具條控制中最常用的資料結構為TBBUTTON,其具體結構如下:
    typedef struct _TBBUTTON {
    int iBitmap;    // 基於0的點陣圖索引值
    int idCommand;  // 按鈕按下時傳送的命令值
    BYTE fsState;   // 按鈕的狀態
    BYTE fsStyle;   // 按鈕的風格
    DWORD dwData;   // 應用程式定義的資料
    int iString;    // 基於0的按鈕標籤字串索引值
    } TBBUTTON;
    其中按鈕狀態fsState的值如下:
TBSTATE_CHECKED  表示按鈕具有TBSTYLE_CHECKED風格並且被按下
    TBSTATE_ENABLED  表示按鈕允許接受輸入,否則變灰不接受任何輸入
    TBSTATE_HIDDEN   表示按鈕不可見並且不接受任何輸入
    TBSTATE_INDETERMINATE  表示按鈕是變灰的
    TBSTATE_PRESSED  表示按鈕正被按下
    TBSTATE_WRAP     表示按鈕具有換行特性,該按鈕必須具有TBSTATE_ENABLED狀態
    按鈕風格style可以是下列值的組合:
    TBSTYLE_BUTTON   表示建立標準下推按鈕
    TBSTYLE_CHECK    表示建立檢查狀態按鈕
    TBSTYLE_CHECKGROUP表示建立檢查按鈕群
    TBSTYLE_GROUP    表示建立按下狀態按鈕群
    TBSTYLE_SEP      表示建立按鈕分隔符
6.2.2.3 工具條控制的類屬性
    工具條控制的類屬性必然的聯絡判斷按鈕使能狀態IsButtonEnabled、判斷按鈕檢查狀態    IsButtonChecked、判斷按鈕按下狀態IsButtonPressed、判斷按鈕是否隱藏IsButtonHidden、判斷按鈕變灰狀態IsButtonIndeterminate、設定按鈕狀態SetState、取得按鈕狀態GetState、取得按鈕有關資訊GetButton、取得按鈕總數GetButtonCount、取得按鈕矩形區域GetItemRect、設定按鈕結構大小SetButtonStructSize、設定按鈕大小SetButtonSize、設定按鈕點陣圖大小SetBitmapSize、取得按鈕提示控制GetToolTips、設定按鈕提示控制SetToolTips等。
6.2.2.4 工具條控制類的操作方法
    工具條控制類的操作方法包括使能按鈕EnableButton、檢查按鈕CheckButton、按下按鈕PressButton、隱藏按鈕HideButton、變灰按鈕Indeterminate、增加按鈕AddButtons、插入按鈕InsertButton、刪除按鈕DeleteButton、取得控制符ID對應的索引CommandToIndex、恢復工具條狀態RestoreState、儲存工具條狀態SaveState和重新確定工具條大小AutoSize等。
6.3 工具條控制的應用技巧
    可以這樣說,工具條和上述常用控制是應用程式中不可缺少的功能元素,它的優劣會直接影響程式的基本功能和操作特性。所以這裡將對工具條的建立技巧、狀態儲存與恢復、平面特性、停靠位置、排序方法、訊息對映、狀態更新、控制使用和屬性控制等方面,全面闡述工具條的使用技巧。
6.3.1 工具條的建立技巧
6.3.1.1 普通工具條的建立方法
    如果應用程式在建立時就具有工具條,則只需對工具條中的按鈕圖示進行簡單的增加、修改和刪除等操作就可滿足要求。如果未建立或者想增加其它工具條,則應按步驟追加建立。
    首先開啟已建立好的基於單文件的框架工程檔案CTool並選擇"Insert->Resource->ToolBar"選項,插入工具條資源並設定資源識別符號;然後編輯工具欄中的按鈕圖示和相應的按鈕識別符號,並利用類嚮導ClassWizard 為按鈕訊息增加COMMAND和UPDATE_COMMAND_UI兩種處理函式;在資原始檔中增加和修改工具條圖示的動態提示等內容;開啟MainFrm.h包含檔案在"CToolBar m_wndMainToolBar"後增加"CToolBar m_wndTestToolBar" 等來建立增加的工具條物件;在MainFrm.h 中設定建立函式所需的成員變數,如顏色變數為m_bColor、動態提示功能變數為m_bToolTips 等,注意成員變數名與其獲取的引數應完全對應以便使用;最後在MainFrm.cpp中的OnCreate()建立函式中按下述示例規則增加控制程式碼,其實現具體步驟如下:
    ①在MainFrm.h中增加工具條物件控制和成員變數
    #define TOOLLEFT   18
    class CMainFrame:public CFrameWnd
    ......//其它程式碼
    public:
    BOOL m_bToolTips;//工具條提示功能
    ......//其它程式碼
    protected://工具條控制成員變數
        CStatusBar   m_wndStatusBar;  file://框
架程式的狀態條
        CTestToolBar m_wndMainToolBar;//框架程式的工具條
        CTestToolBar m_wndTestToolBar;//新增工具條
        CTestToolBar m_wndDockToolBar;//浮動工具條
        CTestToolBar m_wndDockNextBar;//浮動工具條
    ......//其它程式碼
    }
    框架程式中工具條的控制類正常應為CToolBar,可以是自己設計的派生類CtestToolBar(為筆者擴充平面特性等功能後的新工具條控制類名)等,具體根據實際需要而定。利用CDialogBar類和CStyleBar 類還可以建立擴充套件型別的工具條,詳見後面工具條中控制應用技巧,但在該檔案頭處必須
包含如下命令:
    #ifndef __AFXEXT_H__
    #include //直接存取CToolBar和CStatusBar
    #endif
    ②在MainFrm.cpp中完善視窗建立函式
    int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
    {  if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
        return -1;
        WINDOWPLACEMENT wp;//儲存主視窗及工具欄視窗位置狀態
        if (ReadWindowPlacement(&wp))//讀取位置狀態資訊
           SetWindowPlacement(&wp);  file://設
置位置狀態資訊
        m_bToolTips=(AfxGetApp()->GetProfileInt(//讀提示功能
           _T("General"),_T("ToolTips"),1)!=0); file://默認值為1
        m_wndMainToolBar.SetState(TOOLLEFT,TRUE);//設定初始狀態
        EnableDocking(CBRS_ALIGN_ANY);//停靠位置,必須提前位置
        if (!m_wndMainToolBar.Create(this,WS_CHILD|WS_VISIBLE
           |CBRS_SIZE_DYNAMIC|CBRS_TOP|((m_bToolTips)?
           (CBRS_TOOLTIPS|CBRS_FLYBY):0),IDR_MAINFRAME)||
           !m_wndMainToolBar.LoadToolBar(IDR_MAINFRAME))
        {  file://CBRS_SIZE_DYNAMIC
為鎖定位置風格
           TRACE0("主工具條MAINFRAME建立失敗/n");
           return -1;} // 建立失敗處理
        ......//建立其它工具條程式碼,基本相同
        if (!m_wndStatusBar.Create(this)||
           !m_wndStatusBar.SetIndicators(indicators,
           sizeof(indicators)/sizeof(UINT)))
        {  file://建立狀態條
           TRACE0("Failed to create status bar/n");
           return -1;} // fail to create
        m_wndMainToolBar.SetWindowText(_T("主工具欄"));//設定標題
        m_wndMainToolBar.EnableDocking(CBRS_ALIGN_ANY);//停靠位置
        file://m_wndMainToolBar.ModifyStyle(0,TBSTYLE_FLAT);//平面特性
        ......//設定其它工具條位置程式碼,基本相同
        DockControlBar(&m_wndMainToolBar,
          AFX_IDW_DOCKBAR_TOP);//鎖定位置
        DockControlBarLeftOf(&m_wndTestToolBar,
          &m_wndMainToolBar);//連線工具條
        DockControlBar(&m_wndDockToolBar,AFX_IDW_DOCKBAR_RIGHT);
        m_wndDockToolBar.SetColumns(AfxGetApp()->GetProfileInt(
          _T("General"),_T("Columns"),3));//恢復列格式,預設為3
        DockControlBarLeftOf(&m_wndDockNextBar,&m_wndDockToolBar);
        m_wndDockNextBar.SetColumns(AfxGetApp()->GetProfileInt(
          _T("General"),_T("Columns"),3));
        LoadBarState(_T("General"));//恢復儲存的狀態和位置
        return 0;
    }
    以上建立過程除工具條建立和資源呼叫函式外,還涉及到了視窗和工具條的狀態儲存和恢復函式、登錄檔引數讀取函式、工具條停靠位置函式、工具條標題修改函式、工具條連線函式、工具條列格式控制函式和工具條風格修改函式,其中工具條建立函式中的風格設定很重要,如果建立的工具條需要重新設定多行多列的排序功能,除正確設定工具條停靠位置引數外,還必須設定CBRS_SIZE_FIXED 風格,即允許程式改變工具條視窗的尺寸,如果工具條不需要重新排序,則必須設定為CBRS_SIZE_DYNAMIC 風格,否則工具欄不但不能進行重新排序和正確停靠到理想的位置,而且也無法正確儲存和恢復工具條的位置和狀態,這一點應引起程式設計者高度重視。其餘函式以後分別介紹。
6.3.1.2 浮動工具條的建立方法
    如果要建立浮動工具條,必須使用如下工具條的控制方法:
    Cpoint pt(GetSystemMetrics(SM_CXSCREEN)-100,GetSystemMetrics(SM_CYSCREEN)/3);
    FloatControlBar(&m_wndPaletteBar,pt);//浮動工具條
6.3.1.3 多點陣圖工具條的建立方法
    如果工具條存在多幅按鈕點陣圖,如單色和彩色等,則必須將工具條按鈕存在在點陣圖資原始檔中而不是工具條資源中,並如下建立:
    if(!m_wndDockToolBar.Create(this,WS_CHILD|WS_VISIBLE|
      CBRS_SIZE_FIXED|CBRS_TOP|CBRS_TOOLTIPS,ID_PALETTEBAR)||
      !m_wndDockToolBar.LoadBitmap(IDR_DOCKTOOLBAR)||
      !m_wndDockToolBar.SetButtons(DockTool,
      sizeof(DockTool)/sizeof(UINT)))
    其中DockTool為按鈕IDs資料結構,其定義方法如下:
    static UINT BASED_CODE DockTool[]=
    {   ID_SEPARATOR,
        ID_STYLE_LEFT,
        ID_STYLE_CENTERED,
        ID_STYLE_RIGHT,
        ID_STYLE_JUSTIFIED,
    };
    上述建立過程中的EnableDocking 函式必須放在所有工具條建立函式之前,否則可能出現很難發現的錯誤,如特殊工具條初始位置控制等。工具條的所有特性均在上述建立函式中確定,所以其建立過程是實現理想工具條的關鍵環節。
6.3.2 工具條狀態儲存和恢復6.3.3
    很多應用程式中都具有儲存和恢復應用程式及其工具條等狀態的功能,即下次啟動應用程式後進入上次的執行狀態,這種功能只需進行一次介面佈局便可永久儲存,極大方便使用者。
    要正確儲存和恢復應用程式介面狀態,必須對應用程式視窗和工具條視窗等均進行儲存和恢復,這需要完善應用程式的建立和關閉過程。具體步驟如下:
    (1)首先利用類嚮導ClassWizard為應用程式增加視窗關閉WM_CLOSE訊息處理功能OnClose();
    (2)在MainFrm.cpp中為應用程式狀態設定成員變數
    static TCHAR BASED_CODE szSection[]=_T("Settings");
    static TCHAR BASED_CODE szWindowPos[]=_T("WindowPos");
    static TCHAR szFormat[]=_T("%u,%u,%d,%d,%d,%d,%d,%d,%d,%d");
    (3)編制視窗位置狀態讀取和寫入函式
    static BOOL PASCAL NEAR ReadWindowPlacement(LPWINDOWPLACEMENT pwp)
    {   file://窗口位置狀態讀取函式,從INI檔案中
        CString strBuffer=AfxGetApp()->GetProfileString(szSection,szWindowPos);
        if (strBuffer.IsEmpty()) return FALSE;
        WINDOWPLACEMENT wp;//視窗位置資料結構
        int nRead=_stscanf(strBuffer,szFormat,
            &wp.flags,&wp.showCmd,//為資料結構讀取數值
            &wp.ptMinPosition.x,&wp.ptMinPosition.y,
            &wp.ptMaxPosition.x,&wp.ptMaxPosition.y,
            &wp.rcNormalPosition.left,&wp.rcNormalPosition.top,
            &wp.rcNormalPosition.right,&wp.rcNormalPosition.bottom);
        if (nRead!=10) return FALSE;
        wp.length=sizeof wp;//結構大小
        *pwp=wp;            file://結構指標
        return TRUE;
    }
    static void PASCAL NEAR WriteWindowPlacement(
        LPWINDOWPLACEMENT pwp)
    {   file://窗口位置狀態寫入函式,寫到INI檔案
        TCHAR szBuffer[sizeof("-32767")*8+sizeof("65535")*2];
        wsprintf(szBuffer,szFormat,//將引數值轉換為字串
          pwp->flags,pwp->showCmd,
          pwp->ptMinPosition.x,pwp->ptMinPosition.y,
          pwp->ptMaxPosition.x,pwp->ptMaxPosition.y,
          pwp->rcNormalPosition.left,pwp->rcNormalPosition.top,
          pwp->rcNormalPosition.right,pwp->rcNormalPosition.bottom);
        AfxGetApp()->WriteProfileString(szSection,szWindowPos,szBuffer);
    }
    (4)在應用程式建立函式OnCreate()中增加狀態讀取和設定功能
        WINDOWPLACEMENT wp;//儲存主視窗及工具條視窗位置狀態
        if (ReadWindowPlacement(&wp))//讀取位置狀態資訊
           SetWindowPlacement(&wp);  file://設置位置狀態資訊
    (5)在應用程式建立函式OnCreate()中增加工具條狀態恢復功能
        m_wndDockToolBar.SetColumns(AfxGetApp()->GetProfileInt(
          _T("General"),_T("Columns"),3));//恢復列格式,預設為3
        m_wndDockNextBar.SetColumns(AfxGetApp()->GetProfileInt(
          _T("General"),_T("Columns"),3));
        LoadBarState(_T("General"));//恢復儲存的狀態和位置
    (6)在應用程式關閉函式OnClose()中完善狀態儲存功能
    void CMainFrame::OnClose()
    {   file://保存工具條等的狀態
        SaveBarState(_T("General"));//儲存工具條狀態
        AfxGetApp()->WriteProfileInt(_T("General"),//寫入列數
          _T("Columns"),m_wndDockToolBar.GetColumns());
        AfxGetApp()->WriteProfileInt(_T("General"),
          _T("ToolTips"),(m_bToolTips!=0));//寫入提示功能
        WINDOWPLACEMENT wp;
        wp.length=sizeof wp;
        if (GetWindowPlacement(&wp)){
           wp.flags=0;
           if (IsZoomed()) wp.flags|=WPF_RESTORETOMAXIMIZED;
              file://如果視窗被放大,則儲存為最大化狀態
              WriteWindowPlacement(&wp);
        }
        CFrameWnd::OnClose();
    }
    雖然SaveBarState()和LoadBarState()函式儲存和恢復了工具條的所有預設位置狀態,但在實際自己實現的功能引數部分並不能被儲存,所以應單獨編寫這些引數的儲存程式碼,如工具欄的排列格式列引數值、顏色狀態標誌和是否存在動態提示功能標誌等,在實際程式設計時一定要注意。
6.3.4 工具條的平面特性
    工具條的平面特性給人耳目一新之感,很多大型應用程式中的工具條都採用這一特性,並取得了巨大成功。利用VC++5中的COMCTL32.DLL動態連結庫可以實現平面式工具條,其主要解決問題包括:由於MFC使用風格控制位來控制工具條的外觀,所以在建立工具條時不能直接設定這種風格,必須在建立後利用SetFlatLookStyle()函式來修改;工具條控制本身也不在各級按鈕之間繪製分隔線,其另一個任務就是擷取WM_PAINT訊息,並在相應的位置處增加分隔線;工具條控制也不繪製左邊的把手(gripper) ,最後的任務就是調整客戶區域並繪製並繪製相應的gripper。
    顯然,實際工作中需要動態連結庫COMCTL32.DLL支援的上述方法很不方便。儘管最簡便的方法是利用VC++ 5中的未公開工具欄風格TBSTYLE_FLAT,可以得到工具條的平面特性,只需在工具條建立後簡單地增加一條程式碼"m_WndMainToolBar.ModifyStyle(0,TBSTYLE_FLAT)",但筆者經試驗發現這種方法存在兩個嚴重錯誤:其一是所建立的平面工具條在移動時,不能自動清除移動前的按鈕圖示,使工具條畫面雜亂無章;其二是當建立的平面工具條具有浮動特性時,只要滑鼠指標移動到浮動工具條上,整個應用程式視窗就會自動消失。所以第二種方法根本不可行。實現平面工具條的最好方法是在派生類中自己來完成,雖然這一過程比較複雜普通使用者很難做到,但如果存在一個完美的平面工具條控制類,在自己的應用程式中增加相應控制類就是一件很容易的事了。下面是筆者實現完美平面工具條派生類的步驟:
    (1)首先利用類嚮導ClassWizard為工具條控制類派生一個新類CTESTTOOLBAR ,並設定相應的派生類實現檔名。由於新類的基類無法直接選擇CTOOLBAR,所以在選擇新類的基類時先選擇CTOOLBARCTRL為基類,當派生類生成後再將實現檔案中的所有CTOOLBARCTRL類名修改為CTOOLBAR控制類,並利用ClassWizard 為新類增加訊息WM_PAINT、WM_NCPAINT、WM_MOUSEMOVE、WM_LBUTTONDOWN和WM_LBUTTONUP訊息處理功能函式,以便實現新類中平面工具條的各種特性。同時,要在MainFrm.cpp中增加包含檔案TestToolBar.h。
    (2)完善派生類實現檔案TestToolBar.h內容
    class CTestToolBar : public CToolBar
    {......//其它程式碼
    public:
        CTestToolBar(); file://新類建構函式
        UINT GetColumns() { return m_nColumns;};//取得列數
        void SetState(UINT nLeft,BOOL nStated);//設定列數和狀態
        void OnDrawBorder(int index,CDC &dc,int flag);//畫邊框
        void OnEraseBorder(int index,CDC &dc);//刪除邊框
        void OnDrawBorders();//畫平面特性
        void OnDrawSep(int index,CDC &dc);//畫分隔線
        void OnDrawGrapper();//畫把手
    ......//其它程式碼
    #ifdef _DEBUG file://增加插入控制
        virtual void AssertValid() const;
        virtual void Dump(CDumpContext& dc) const;
    #endif
    protected:    file://增加成員變數
        UINT m_nColumns; file://工具欄按鈕列數
        UINT m_nFlags;   file://鼠標按鍵標誌
        int  m_nIndex;   file://按下的按鈕號
        int  m_nFlagl;   file://左鍵按下標誌
        UINT m_nStated;  file://工具欄狀態
        CRect rt;        file://關閉按鈕矩形區域
    ......//其它程式碼
    }
    (3)完善派生類實現檔案TestToolBar.cpp內容
    ......//其它程式碼
    #define TOOLLEFT    18
    #define LBUTTONDOWN 1
    #define LBUTTONUP   2
    ......//其它程式碼
    CTestToolBar::CTestToolBar()
    {   file://在建構函式中初始化變數
        m_nColumns=0;      file://工具欄按鈕列數
        m_cxLeftBorder=16; file://左邊界
        m_cxRightBorder=3; file://右邊界
        m_cyTopBorder=3;   file://頂邊界
        m_cyBottomBorder=3;//底邊界
        m_nFlags=0;        file://按鍵標誌成員變數
        m_nIndex=0xffff;   file://按下的按鈕號
        m_nFlagl=0;        file://左鍵按下標誌
        m_nStated=TRUE;    file://工具欄狀態
    }
    ......//其它程式碼
    #ifdef _DEBUG//插入程式碼完善
    void CTestToolBar::AssertValid() const
    {   CToolBar::AssertValid(); }
    void CTestToolBar::Dump(CDumpContext& dc) const
    {   CToolBar::Dump(dc); }
    #endif file://_DEBUG
......//其它程式碼
    雖然需要實現的函式比較多,但總起來說不過是取得客戶區域或視窗所有區域的文字裝置、建立畫筆和繪圖函式的集合,所以這裡只給出了畫按鈕凸凹邊線的函式,其它函式可仿造實現。
    void CTestToolBar::OnDrawBorder(int index,CDC &dc,int flag)
    {   file://畫按鈕邊線flag=0凸=1凹
        CRect rect;
        GetItemRect(index,&rect);//取得客戶區域
        rect.right--;rect.bottom--;
        CPen *oldpen;
        UINT color1,color2;
        if (flag==0){//兩種狀態的顏色處理
           color1=COLOR_BTNHILIGHT;//按鈕高度顏色
           color2=COLOR_BTNSHADOW; file://按鈕陰影顏色
        } else {
           color1=COLOR_BTNSHADOW;
           color2=COLOR_BTNHILIGHT;
        }
        CPen pen1(PS_SOLID,1,::GetSysColor(color1));
        CPen pen2(PS_SOLID,1,::GetSysColor(color2));
        dc.SelectStockObject(NULL_BRUSH);
        oldpen=dc.SelectObject(&pen1);
        dc.MoveTo(rect.right,rect.top);//畫按鈕邊亮線
        dc.LineTo(rect.left,rect.top);
        dc.LineTo(rect.left,rect.bottom);
        dc.SelectObject(&pen2);     file://畫按鈕邊暗線
        dc.MoveTo(rect.right,rect.top);
        dc.LineTo(rect.right,rect.bottom);
        dc.LineTo(rect.left,rect.bottom);
        file://dc.SelectStockObject(BLACK_PEN);//畫按鈕邊黑線
        file://dc.MoveTo(rect.right+1,rect.top);
        file://dc.LineTo(rect.right+1,rect.bottom+1);
        file://dc.LineTo(rect.left,rect.bottom+1);
        dc.SelectObject(oldpen);
        DeleteObject(pen1);
        DeleteObject(pen2);
    }
    void CTestToolBar::OnDrawBorders()
    {  file://實現平面工具條
        CRect rect;
        CPoint pt;
        GetCursorPos(&pt);  file://取得滑鼠指標
        ScreenToClient(&pt);//變成視窗座標
        int index;
        int count=GetCount();//工具條按鈕總數
        CClientDC dc(this); file://窗口客戶區域
        TBBUTTON button;    file://按鈕資料結構
        CToolBarCtrl &ToolBarCtrl=GetToolBarCtrl();
        OnDrawGrapper();    file://畫把手
        for(index=0;index            GetItemRect(index,&rect);//取得按鈕矩形區域
            rect.left++;rect.top++;
            ToolBarCtrl.GetButton(index,&button);//取得按鈕資訊
            if(button.fsState&(TBSTATE_CHECKED|TBSTATE_HIDDEN))
              continue;
            if(button.fsStyle&TBSTYLE_SEP){//畫分隔線
              if(m_nNew!=0) OnDrawSep(index,dc);
              } else if ((m_nIndex==index)||
                button.fsState&TBSTATE_PRESSED){//凹按鈕
                OnEraseBorder(index,dc);//刪除按鈕邊界
                if (rect.PtInRect(pt)) OnDrawBorder(index,dc,1);//繪下凹按鈕
                   else OnDrawBorder(index,dc,0);//繪凸出按鈕
              } else if (!rect.PtInRect(pt)||m_nFlags==LBUTTONUP||
                  !(button.fsState&TBSTATE_ENABLED)){
                  OnEraseBorder(index,dc);//刪除按鈕邊界
              } else if (m_nFlags!=LBUTTONDOWN){//凸按鈕
                  OnEraseBorder(index,dc);//刪除按鈕邊界
                  if(m_nFlagl==0)//滑鼠按下防止再次重新出現凸起
                     OnDrawBorder(index,dc,0);//繪按鈕邊界
              }
            m_nFlags=0;//按下後移動後不正常凸起
        }
        ReleaseDC(&dc);
    }
    void CTestToolBar::OnPaint()
    {   file://完善重繪按鈕功能
        CToolBar::OnPaint();
        OnDrawBorders();//處理所有按鈕邊界
    }
    void CTestToolBar::OnLButtonDown(UINT nFlags, CPoint point)
    {   file://完善滑鼠左鍵按下功能
        m_nFlags=LBUTTONDOWN;//設定滑鼠按鍵標誌
        m_nFlagl=1;
        CToolBar::OnLButtonDown(nFlags,point);//調原函式
        int index;
        int count=GetCount();//工具欄按鈕總數
        TBBUTTON button;
        CToolBarCtrl &ToolBarCtrl=GetToolBarCtrl();
        for(index=0;index          ToolBarCtrl.GetButton(index,&button);//取得按鈕資訊
          if (button.fsState&TBSTATE_PRESSED){ file://記錄按下按鈕號
            m_nIndex=index;
          }
        }
    }
    void CTestToolBar::OnLButtonUp(UINT nFlags, CPoint point)
    {   file://完善滑鼠釋放功能
        m_nFlags=LBUTTONUP;//設定滑鼠按鍵標誌
        m_nFlagl=0;
        CToolBar::OnLButtonUp(nFlags, point);//調原函式
        CRect rect;
        CPoint pt;
        GetCursorPos(&pt);//取得游標位置
        ScreenToClient(&pt);//變成視窗座標
        CClientDC dc(this);//視窗客戶區域
        if (m_nIndex!=0xffff){//判斷按下按鈕執行功能時仍下凹
           GetItemRect(m_nIndex,&rect);//取得矩形區域
           rect.left++;rect.top++;
           OnEraseBorder(m_nIndex,dc);//刪除按鈕邊界
           if (rect.PtInRect(pt)) OnDrawBorder(m_nIndex,dc,1);//繪下凹按鈕
        }
        m_nIndex=0xffff;
    }
    void CTestToolBar::OnMouseMove(UINT nFlags, CPoint point)
    {   file://完善滑鼠移動功能
        CToolBar::OnMouseMove(nFlags, point);
        int index;
        int count=GetCount();//工具欄按鈕總數
        CRect rect;
        if (nFlags&MK_LBUTTON) m_nFlagl=1;//防止再次重新出現凸起
        else m_nFlagl=0;
        OnDrawBorders();//繪製所有按鈕
        for(index=0;index           GetItemRect(index,&rect);
           rect.left++;rect.top++;
           if (rect.PtInRect(point)&&//取得移動過程中輸入焦點
              !(GetButtonStyle(index)&TBBS_SEPARATOR)){
              SetCapture();//設定滑鼠輸入焦點
              return;
           }
        }
        if (nFlags&MK_LBUTTON){//防止移出而失去輸入焦點
           SetCapture();//設定滑鼠輸入焦點
           m_nFlagl=1;
           return;
        } else m_nFlagl=0;
        ReleaseCapture();
        return;
    }
    void CTestToolBar::OnNcPaint()
    {   file://背景重畫函式
        CToolBar::OnNcPaint();
        OnDrawGrapper();
    }
    void CTestToolBar::SetState(UINT nLeft,BOOL nStated)
    {   file://狀態設定函式
        m_cxLeftBorder=nLeft;//左邊界
        m_nStated=nStated;   file://工具欄狀態
    }
    (4)有關派生類函式幾點說明
    ①畫按鈕凹凸邊線函式OnDrawBorder()
    正常工具條中的按鈕具有黑色的邊線,使按鈕凹凸感更強烈,但在平面工具條中的這種按鈕並不美觀,所以應省略黑色邊線部分,並且必須使用系統的API函式GetSysColor函式來取得邊線顏色,以便系統改變顏色時按鈕邊線也隨之改變,同時由於凹凸按鈕邊線畫法完全相同,只是顏色相反,所以兩者完全可由這個函式來實現;
    ②畫分隔線函式OnDrawSep()
    畫分隔線時應遍歷每個按鈕,來取得分隔線的位置,並且利用客戶區域文字描述表就可實現,只需畫亮暗兩條線就可實現;
    ③畫把手函式OnDrawGripper()
    畫把手時應使用整個視窗的文字描述表,因為客戶區域描述表不能在視窗的非客戶區域畫線,而且還必須判斷按鈕是否以多行多列方式排列,根據不同的排列方式畫水平或垂直把手,同時還要實現畫關閉按鈕功能,以和VC++5 等介面工具欄功能完全相同,另外還要判斷工具欄是否為子視窗狀態,以確定是否畫把手和關閉按鈕;
    ④刪除按鈕邊線函式OnEraseBorder()
    函式用於消除系統所繪按鈕凹凸邊線,使按鈕具有平面效果,也必須利用系統的API函式GetSysColor函式來取得系統顏色,以保證系統改變顏色時能夠正常消除按鈕邊線;
    ⑤實現平面工具欄所有功能函式OnDrawBorders()
    在該函式中應特別注意對按鈕分隔符判斷、按鈕凹凸狀態判斷、滑鼠左鍵按下後按鈕凹凸狀態判斷、刪除系統所畫按鈕邊線判斷判斷和按下滑鼠左鍵並移動滑鼠按鈕的凹凸狀態判斷等,並需要利用工具條控制取得對工具欄的引用;
    ⑥工具條更新功能函式OnPaint()
    在這個函式中應注意對原系統更新功能函式的呼叫,以實現動態提示和按鈕圖示的顯示等功能;
    ⑦滑鼠左鍵按下功能函式OnLButtonDown()
    該函式中除需要呼叫原系統滑鼠左鍵按下功能函式,以實現訊息的傳送等功能外,還需要設定滑鼠左鍵按下標誌並記錄按下按鈕的位置,以便程式正確判斷按鈕的凹凸狀態;
    ⑧滑鼠左鍵釋放功能函式OnLButtonDown()
    該函式中除需要呼叫原系統滑鼠左鍵釋放功能函式,以實現按鈕執行訊息的傳送等功能外,還需要設定滑鼠左鍵釋放標誌,以便程式正確判斷按鈕的凹凸狀態,此外還應重繪按鈕凹下狀態以使按鈕功能執行時按鈕應處於凹下狀態,來保證工具欄按鈕與其它高階應用程式實現的功能完全相同;
    ⑨滑鼠移動功能函式OnMouseMove()
    該函式中應記錄滑鼠左鍵按下狀態標誌,並在滑鼠移動到按鈕上和滑鼠左鍵按下時設定滑鼠輸入焦點,來保證平面工具條在滑鼠移動過程中的正常凸起狀態和滑鼠點選按鈕後對按鈕狀態的控制,如利用這一點可實現滑鼠點選按鈕後按鈕下凹,不釋放滑鼠並移動到任何位置時按鈕凸起,重新移動到按鈕上按鈕仍下凹,這些全是控制滑鼠焦點的功能,並及時釋放滑鼠輸入焦點;
    ⑩背景重繪等函式OnNcPaint()
    背景重繪函式是用來防止一個工具條被切換顯示狀態後,另一個工具欄中的把手和關閉按鈕等消失,這在滑鼠反覆雙擊排序後工具條非客戶區域時,另一個排序後工具欄就會出現的現象;另外函式SetState()用來設定工具條左邊界和狀態,以便為畫把手和關閉按鈕調整客戶區域並提供繪圖狀態。
    此外,還有滑鼠移動到把手上游標改變形狀和關閉按鈕功能,由於篇幅所限這裡從略,有興趣的讀者完全可以自己實現。
6.3.5 工具條的停靠位置
6.3.5.1 標6.3.5.2 準工具條的停靠位置
    工具條類CToolBar是控制條類CControlBar 的派生類,其顯示的初始停靠位置是通過呼叫繼承的函式CControlBar::EnableDocking(DWORD dwStyle)來確定的,其引數dwStyle用來指定停靠具體位置,與本文有關的風格如下,其餘請參閱VC5的聯機幫助:
    CBRS_ALIGN_TOP    工具條停靠在客戶區域頂部
    CBRS_ALIGN_BOTTOM 工具條停靠在客戶區域底部
    CBRS_ALIGN_LEFT   工具條停靠在客戶區域左邊
    CBRS_ALIGN_RIGHT  工具條停靠在客戶區域右邊
    CBRS_ALIGN_ANY    工具條停靠在客戶區域任何位置
    利用應用程式嚮導AppWizard 生成的應用程式,其預設的停靠位置為CBRS_ALIGN_ANY,即允許停靠在客戶區域的任何邊,正常顯示時為靠近客戶區域的頂部:EnableDocking(CBRS_ALIGN_ANY) ,詳見上述的工具欄建立函式ONCREATE()。
    應用程式的單文件和多文件的視窗框架類均為CFrameWnd 的派生類,其指定工具條的停靠位置均是通過呼叫繼承的函式 CFrameWnd::EnableDocking(DWORD dwDockStyle)來實現的,其可選的引數除上述五種之外,還增加了CBRS_FLOAT_MULTI引數,這個引數主要是為設計浮動工具條而增加的,其用來確定一個框架視窗中允許存在多個浮動工具欄。同樣利用應用程式嚮導AppWizard 生成的應用程式,其預設的停靠位置也是CBRS_ALIGN_ANY,即允許停靠在框架視窗的任何邊,正常顯示時為靠近框架視窗的頂部,即為EnableDocking(CBRS_ALIGN_ANY),詳見上述的工具條建立函式ONCREATE()。
6.3.5.3 浮動工具條的停靠位置
    當一個框架視窗中存在多個浮動工具條時,需要利用函式void DockControlBar(CControlBar *pBar,UINT nDockBarID=0,LPCRECT lpRect= NULL)來確定要控制停靠位置的工具條,它也是CFrameWnd類的成員函式,其中引數pBar用來指向被控制停靠位置的工具條物件,引數nDockBarID用來確定工具條停靠在框架視窗的哪條邊上,取值為:
    AFX_IDW_DOCKBAR_TOP    工具條停靠在框架視窗的頂部
    AFX_IDW_DOCKBAR_BOTTOM 工具條停靠在框架視窗的底部
    AFX_IDW_DOCKBAR_LEFT   工具條停靠在框架視窗的左邊
    AFX_IDW_DOCKBAR_RIGHT  工具條停靠在框架視窗的右邊
    如果引數nDockBarID取值為0,則工具條可以停靠在框架視窗中的任何一個可停靠的邊上,其預設位置為頂部。
6.3.5.4 工具條的連線停靠方法
    在很多應用程式中都存在將多個工具條同時停靠在某視窗的某一條邊上的同一工具條視窗中的情況,利用上述工具條控制函式DockControlBar的lpRect引數,通過控制工具條的停靠矩形區域來實現這個功能,如筆者實現的函式如下:
    ①在主程式實現檔案MainFrm.h中增加函式定義
    public:
      void DockControlBarLeftOf(CToolBar* Bar,CToolBar* LeftOf);
    ②在主程式實現檔案MainFrm.cpp中增加如下函式
    void CMainFrame::DockControlBarLeftOf(
         CToolBar* Bar,CToolBar* LeftOf)
    {   file://設置工具條停靠在同一邊視窗中
        CRect rect;
        DWORD dw;
        UINT n;
        RecalcLayout();//重新顯示
        LeftOf->GetWindowRect(&rect);
        rect.OffsetRect(1,0);//設定偏移值以停靠在同一視窗中
        dw=LeftOf->GetBarStyle();
        n=0;
        n=(dw&CBRS_ALIGN_TOP)?AFX_IDW_DOCKBAR_TOP:n;
        n=(dw&CBRS_ALIGN_BOTTOM&&n==0)?AFX_IDW_DOCKBAR_BOTTOM:n;
        n=(dw&CBRS_ALIGN_LEFT&&n==0)?AFX_IDW_DOCKBAR_LEFT:n;
        n=(dw&CBRS_ALIGN_RIGHT&&n==0)?AFX_IDW_DOCKBAR_RIGHT:n;
        DockControlBar(Bar,n,&rect);
    }
    在這個函式中應注意對RecalcLayout()函式和OffsetRect()函式的呼叫,前一個函式用來重新顯示被調整的客戶區和工具條,後一個函式用來重新確定矩形區域,這相當於用滑鼠將第二個工具條拖動到前一個工具條上。
    ③修改應用程式建立函式OnCreate()函式中的相應DockControlBar()函式為DoctControlBarOf()函式,並正確設定工具條指標,見工具條的建立技巧中的有關函式。
6.3.5.5 定製工具條的頂部停靠控制
    另一種工具條的停靠位置是定製工具條的停靠位置,如具有通用控制功能工具條的停靠位置,這主要實現左側定製工具條與頂部工具條之間的位置關係。其實現方法如下:
    ①開啟選單資源增加頂部位置控制選單項IDD_DLGBARTOP;
    ②在實現檔案MainFrm.h中增加成員控制變數m_bDialogTop;
    BOOL m_bDialogTop;
    並在建構函式中為其設定初始值;
    ③利用類嚮導函式為選單項設定響應函式;
    ④在實現檔案MainFrm.cpp中完善訊息對映函式。
    void CMainFrame::OnButtonDlgbartop()
    {   file://定制工具條頂部位置控制函式
        if (m_bDialogTop) m_wndDlgBar.SetWindowPos(
           &m_wndStatusBar,0,0,0,0,SWP_NOSIZE|SWP_NOMOVE);
           file://其它工具條停靠在最頂部,該工具條停靠其下
        else m_wndDlgBar.SetWindowPos(&wndTop,0,0,0,0,
           SWP_NOSIZE|SWP_NOMOVE);//停靠在最頂部
        RecalcLayout(); file://重新顯示視窗
        m_bDialogTop=!m_bDialogTop;//改變變數標誌
    }
    void CMainFrame::OnUpdateButtonDlgbartop(CCmdUI* pCmdUI)
    {   file://設置選單項檢查狀態
        pCmdUI->SetCheck(m_bDialogTop);
    }
6.3.6 工具條按鈕的排序方法
    利用應用程式嚮導AppWizard 生成的應用程式工具條,其按鈕均為單行水平排列的,這在實際程式開發時既不美觀又不實用,很多大型應用程式等介面中的工具條都採用多行多列的排序方式,要在自己的應用程式中實現這種排列方式,應按下述方法在派生類中控制:
    (1)在TestToolBar.h中增加函式定義和成員變數控制
    class CTestToolBar : public CToolBar
    {......//其它程式碼
    public:
        CTestToolBar(); file://在建構函式後增加下一行
        void SetColumns(UINT nColumns);//增加列控制
    ......//其它程式碼
    protected:    file://增加成員變數
        UINT m_nColumns; file://工具條列按鈕數
    ......//其它程式碼
    }
    (2)在TestToolBar.cpp中增加變數初始化和函式
    CTestToolBar::CTestToolBar()
    {   file://在建構函式中初始化變數
        m_nColumns=0;      file://工具條按鈕列數
        ......//其它程式碼
    }
    void CTestToolBar::SetColumns(UINT nColumns)
    {   file://設置按鈕排列格式
        m_nColumns=nColumns;//列數
        int nCount=GetToolBarCtrl().GetButtonCount();//按鈕數
        for(int i=0;i           UINT nStyle=GetButtonStyle(i);//按鈕風格
           BOOL bWrap=(((i+1)%nColumns)==0);
           if(bWrap) nStyle|=TBBS_WRAPPED;//設定換行
           else nStyle&=~TBBS_WRAPPED;//不換行
           SetButtonStyle(i,nStyle);//設定風格
        }
        Invalidate();//視窗更新
        GetParentFrame()->RecalcLayout();//工具欄狀態更新
    }
    (3)在應用程式建立函式OnCreate()中為相應的工具條增加列控制功能,並注意對儲存和恢復工具條狀態函式的列控制引數處理,請參閱工具條建立技巧和狀態儲存與恢復中的有關函式,重新編譯並執行應用程式就可以看到多行多列的工具條。