共用方式為


如何自訂工具列

大部分的 Windows 應用程式都會使用工具列控制件,讓使用者方便存取程式功能。 不過,靜態工具列有一些缺點,例如空間太少,無法有效地顯示所有可用的工具。 此問題的解決方案是讓您的應用程式工具列成為使用者可自定義的工具列。 然後,用戶可以選擇只顯示他們需要的工具,並以符合個人工作風格的方式來排列這些工具。

注意

無法自訂對話框中的工具列。

 

若要啟用自定義功能,請在建立工具列控件時包含 CCS_ADJUSTABLE 通用控件樣式旗標。 自訂有兩種基本方法:

  • 自訂對話框。 這個系統提供的對話框是最簡單的方法。 它為使用者提供圖形使用者介面,讓他們能夠新增、刪除或移動圖示。
  • 拖放工具。 實作拖放功能可讓使用者將工具移至工具列上的另一個位置,或將其從工具列上拖曳而刪除。 它為使用者提供了一種快速且簡單的方法來組織其工具列,但不允許他們新增工具。

視應用程式的需求而定,您可以實作任一種方法或兩者。 這兩種方法都未提供內建機制,例如 [取消] 或 [復原] 按鈕,以將工具列傳回其先前的狀態。 您必須明確使用工具列控制元件 API 來儲存工具列的預先自定義狀態。 如有必要,您稍後可以使用這個預存資訊,將工具列還原為其原始狀態。

您需要知道的事項

技術

先決條件

  • C/C++
  • Windows 使用者介面程序設計

指示

自訂對話框

工具列控制項會提供自訂對話框,讓使用者以簡單的方式新增、移動或刪除工具。 用戶可以按兩下工具列來啟動它。 應用程式可以透過傳送工具列控件 TB_CUSTOMIZE 訊息,以程式設計方式啟動自定義對話方塊。

下圖顯示工具列自定義對話框的範例。

具有三個項目工具列的視窗螢幕截圖,以及具有可用和目前工具列按鈕清單的對話框

右側清單框中的工具是工具列上目前的工具。 一開始,此列表會包含您在建立工具列時指定的工具。 左側清單框包含可用來新增至工具列的工具。 您的應用程式負責填入該清單(除了 [分隔符] 之外,該清單會自動出現)。

工具列控件會通知您的應用程式正在啟動自定義對話框,方法是傳送其父視窗 TBN_BEGINADJUST 通知碼,後面接著 TBN_INITCUSTOMIZE 通知碼。 在大部分情況下,應用程式不需要回應這些通知碼。 不過,如果您不希望 [自訂工具列] 對話框顯示 [說明] 按鈕,請傳回 TBNRF_HIDEHELP 來處理 TBN_INITCUSTOMIZE。

然後,工具列控件會依下列順序傳送三系列通知代碼,以收集它需要初始化對話框的資訊:

  • 工具列中每個按鈕的 TBN_QUERYINSERT 通知碼,用於判斷按鈕可以插入的位置。 傳回 FALSE 以防止按鈕插入通知訊息中指定的按鈕左邊。 如果您將 FALSE 傳回給所有 TBN_QUERYINSERT 通知碼,則不會顯示對話方塊。
  • 目前在工具列上之每個工具的 TBN_QUERYDELETE 通知碼。 如果工具可以被刪除,則傳回 TRUE,否則傳回 FALSE
  • 一系列 TBN_GETBUTTONINFO 通知代碼,以填入可用的按鈕清單。 若要將按鈕新增至清單,請填入以通知程式代碼傳遞的 NMTOOLBAR 結構,並傳回 true 。 當您沒有其他工具可新增時,請傳回 FALSE。 請注意,您可以傳回工具列上已存在之按鈕的資訊;這些按鈕將不會新增至清單。

接著會顯示對話框,用戶可以開始自訂工具列。

當對話框開啟時,您的應用程式可以根據使用者的動作接收各種通知碼:

  • TBN_QUERYINSERT。 用戶已變更工具列上工具的位置或新增工具。 傳回 FALSE,以防止工具插入該位置。
  • TBN_DELETINGBUTTON。 用戶即將從工具列移除工具。
  • TBN_CUSTHELP。 使用者已按下 [說明] 按鈕。
  • TBN_TOOLBARCHANGE。 使用者已新增、移動或刪除工具。
  • TBN_RESET。 使用者已按下 [重設] 按鈕。

對話框終結之後,您的應用程式會收到 TBN_ENDADJUST 通知碼。

下列程式代碼範例示範實作工具列自定義的其中一種方式。

// The buttons are stored in an array of TBBUTTON structures. 
//
// Constants such as STD_FILENEW are identifiers for the 
// built-in bitmaps that have already been assigned as the toolbar's 
// image list.
//
// Constants such as IDM_NEW are application-defined command identifiers.

TBBUTTON allButtons[] = 
    {
        { MAKELONG(STD_FILENEW,  ImageListID), IDM_NEW,   TBSTATE_ENABLED, 0, {0}, 0, (INT_PTR)L"New" },
        { MAKELONG(STD_FILEOPEN, ImageListID), IDM_OPEN,  TBSTATE_ENABLED, 0, {0}, 0, (INT_PTR)L"Open"},
        { MAKELONG(STD_FILESAVE, ImageListID), IDM_SAVE,  TBSTATE_ENABLED, 0, {0}, 0, (INT_PTR)L"Save"},
        { MAKELONG(STD_CUT,      ImageListID), IDM_CUT,   TBSTATE_ENABLED, 0, {0}, 0, (INT_PTR)L"Cut" },
        { MAKELONG(STD_COPY,     ImageListID), IDM_COPY,  TBSTATE_ENABLED, 0, {0}, 0, (INT_PTR)L"Copy"},
        { MAKELONG(STD_PASTE,    ImageListID), IDM_PASTE, TBSTATE_ENABLED, 0, {0}, 0, (INT_PTR)L"Paste"}
    };

// The following appears in the window's message handler.

case WM_NOTIFY: 
    {
        switch (((LPNMHDR)lParam)->code) 
        {
        
        case TBN_GETBUTTONINFO:  
            {
                LPTBNOTIFY lpTbNotify = (LPTBNOTIFY)lParam;

                // Pass the next button from the array. There is no need to filter out buttons
                // that are already used—they will be ignored.
                
                int buttonCount = sizeof(allButtons) / sizeof(TBBUTTON);
                
                if (lpTbNotify->iItem < buttonCount)
                {
                    lpTbNotify->tbButton = allButtons[lpTbNotify->iItem];
                    return TRUE;
                }
                
                else
                
                {
                    return FALSE;  // No more buttons.
                }
            }
            
            break;

            case TBN_QUERYINSERT:
            
            case TBN_QUERYDELETE:
                return TRUE; 
        }
    }

拖放工具

使用者也可以按下 SHIFT 鍵,將按鈕拖曳到另一個位置,以重新排列工具列上的按鈕。 工具列控制項會自動處理拖放操作。 它會在拖曳按鈕時顯示按鈕的虛影,並在放下後重新排列工具列。 用戶無法以這種方式新增按鈕,但是他們可以將按鈕從工具列上卸除來刪除按鈕。

雖然工具列控制項通常會自動執行這項作業,但它也會傳送應用程式兩個通知碼:TBN_QUERYDELETETBN_QUERYINSERT。 若要控制拖放程式,請依照下列方式處理這些通知碼:

  • 一旦使用者嘗試移動按鈕,便會立即傳送 TBN_QUERYDELETE 通知碼,此時幽靈按鈕尚未顯示。 傳回 FALSE,以防止移動按鈕。 如果您傳回 TRUE,使用者就能夠通過將工具從工具列拖出來,來移動工具或删除它。 如果可以移動工具,則可以刪除此工具。 不過,如果使用者刪除工具,工具列控件會將應用程式傳送 TBN_DELETINGBUTTON 通知碼,此時您可以選擇在其原始位置重新插入按鈕,從而取消刪除。
  • 當使用者嘗試卸除工具列上的按鈕時,會傳送 TBN_QUERYINSERT 通知碼。 若要防止移動的按鈕放置在通知中指定的按鈕左側,請傳回 false 。 如果使用者將工具從工具列上卸除,則不會傳送此通知程序代碼。

如果用戶嘗試拖曳按鈕而不按 SHIFT 鍵,工具列控件將不會處理拖放作業。 不過,它會傳送應用程式 TBN_BEGINDRAG 通知程式代碼,以指出拖曳作業的開始,以及 TBN_ENDDRAG 通知程式代碼來指出結尾。 如果您想要啟用這種形式的拖放功能,您的應用程式必須處理這些通知碼、提供必要的使用者介面,以及修改工具列以反映任何變更。

儲存和還原工具列

在自訂工具列的過程中,您的應用程式可能需要儲存資訊,以便您將工具列還原為其原始狀態。 若要啟動儲存或還原工具列狀態,請傳送訊息 TB_SAVERESTORE 給工具列控件,並將 wParam 設為 TRUE。 此訊息的 wParam 值會指定您要要求儲存或還原作業。 傳送訊息之後,有兩種方式可以處理儲存/還原作業:

  • 使用一般控件 4.72 版 和更早版本,您必須實作 TBN_GETBUTTONINFO 處理程式。 工具列控件會傳送此通知代碼,以在還原時請求每個按鈕的資訊。
  • 5.80 版包含儲存/還原選項。 在程式的開頭,且儲存或還原每個按鈕時,您的應用程式會收到 TBN_SAVETBN_RESTORE 通知碼。 若要使用此選項,您必須實作通知處理程式,以提供成功儲存或還原工具列狀態所需的點陣圖和狀態資訊。

工具列狀態會儲存在一個資料流中,其中包含由 Shell 定義的資料區塊與由應用程式定義的資料區塊交替出現。 每個類型的數據區塊會針對每個按鈕儲存,並且應用程式可以在數據流開頭放置一個可選的全域數據區塊。 在儲存程式期間,您的 TBN_SAVE 處理程式會將應用程式定義的區塊新增至數據流。 在還原過程中,TBN_RESTORE 處理常式會讀取每個區塊,並向殼層提供重建工具列所需的資訊。

如何處理TBN_SAVE通知

第一個 TBN_SAVE 通知程式代碼會在儲存程序開始時傳送。 儲存任何按鈕之前,會設定 NMTBSAVE 結構的成員,如下表所示。

成員 設定
iItem –1
cbData Shell 定義數據所需的記憶體數量。
cButtons 按鈕數目。
pData 應用程式定義資料所需的記憶體計算量。 一般而言,您會包含一些全域數據,以及每個按鈕的數據。 將該值加到 cbData,並配置足夠的記憶體以容納 pData
pCurrent 數據流中第一個未使用的位元組。 如果您不需要全域工具列資訊,請設定 pCurrent = pData,使其指向數據流的開頭。 如果您確實需要全域工具列資訊,請將它儲存在 pData ,然後在傳回之前,將 pCurrent 設定為數據流未使用部分的開頭。

 

如果您想要新增一些全域工具列資訊,請將它放在數據流的開頭。 請將 pCurrent 移至全域數據的結尾,以使它指向數據流未使用部分的起始處,並返回。

返回之後,Shell 會開始儲存按鈕資訊。 它會在 pCurrent 新增第一個按鈕的 Shell 定義的資料,然後將 pCurrent 前進到未使用部分的開頭。

儲存每個按鈕之後,會傳送 TBN_SAVE 通知碼,並傳回 NMTBSAVE,這些成員的設定方式如下。

成員 設置
iItem 按鈕號碼的零起始索引。
pCurrent 數據流中第一個未使用字节的指標。 如果您想要儲存按鈕的其他資訊,請將它儲存在 pCurrent 所指向的位置,並更新 pCurrent,以指向之後數據流的第一個未使用部分。
TBBUTTON 用於描述正在儲存的按鈕的 TBBUTTON 結構。

 

當您收到通知碼時,應該從 TBBUTTON擷取所需的任何按鈕特定資訊。 請記住,當您新增按鈕時,可以使用 dwDataTBBUTTON 的成員來保存應用程式特定數據。 將資料載入資料流 pCurrent。 將 pCurrent 前進到資料結尾,再次將其指向資料流未使用部分的開頭,然後返回。

殼層然後移動到下一個按鈕,將資訊新增到 pData,前進 pCurrent,載入 TBBUTTON,並傳送另一個 TBN_SAVE 通知碼。 此程式會繼續進行,直到儲存所有按鈕為止。

還原儲存的工具列

基本上,還原過程是儲存過程的反向操作。 一開始,您的應用程式會收到一個 TBN_RESTORE 通知碼,其中的 iItem 成員在 NMTBRESTORE 結構中被設定為 –1。 cbData 成員會設定為 pData的大小,而 cButtons 會設定為按鈕的數目。

您的通知處理程式應該擷取在儲存期間放置於 pData 開頭的全域資訊,並將 pCurrent 前進至 Shell 定義的數據的第一個區塊開頭。 將 cBytesPerRecord 設定為您用來儲存按鈕資料的資料區塊大小。 將 cButtons 設定為按鈕數目,然後傳回。

下一個 NMTBRESTORE 用於第一個按鈕。 pCurrent 成員指向您的第一個按鈕資料區塊的開頭,並將 iItem 設定為按鈕索引。 擷取該資料並前進 pCurrent。 將資料載入 TBBUTTON,然後返回。 若要省略已還原工具列中的按鈕,請將 TBBUTTONidCommand 成員設為零。 殼層會針對其餘按鈕重複此程式。 除了 NMTBSAVENMTBRESTORE 訊息之外,您也可以使用 TBN_RESET 等訊息來儲存和還原工具欄。

下列程式代碼範例會在自定義工具列之前儲存工具列,並在應用程式收到 TBN_RESET 訊息時加以還原。

int               i;
LPNMHDR           lpnmhdr;
static int        nResetCount;
static LPTBBUTTON lpSaveButtons;
LPARAM            lParam;

switch( lpnmhdr->code)
{
    case TBN_BEGINADJUST: // Begin customizing the toolbar.
    {
        LPTBNOTIFY  lpTB = (LPTBNOTIFY)lparam;
       
        // Allocate memory for the button information.
        
        nResetCount   = SendMessage(lpTB->hdr.hwndFrom, TB_BUTTONCOUNT, 0, 0);
        lpSaveButtons = (LPTBBUTTON)GlobalAlloc(GPTR, sizeof(TBBUTTON) * nResetCount);
      
        // In case the user presses reset, save the current configuration 
        // so the original toolbar can be restored.
        
        for(i = 0; i < nResetCount; i++)
        {
            SendMessage(lpTB->hdr.hwndFrom, 
                        TB_GETBUTTON, i, 
                        (LPARAM)(lpSaveButtons + i));
        }
    }
    
    return TRUE;
   
    case TBN_RESET:
    {
        LPTBNOTIFY lpTB = (LPTBNOTIFY)lparam;
        
        int nCount, i;
    
        // Remove all of the existing buttons, starting with the last one.
        
        nCount = SendMessage(lpTB->hdr.hwndFrom, TB_BUTTONCOUNT, 0, 0);
        
        for(i = nCount - 1; i >= 0; i--)
        {
            SendMessage(lpTB->hdr.hwndFrom, TB_DELETEBUTTON, i, 0);
        }
      
        SendMessage(lpTB->hdr.hwndFrom,      // Restore the saved buttons.
                    TB_ADDBUTTONS, 
                    (WPARAM)nResetCount, 
                    (LPARAM)lpSaveButtons);
    }
    
    return TRUE;
   
    case TBN_ENDADJUST:                // Free up the memory you allocated.
        GlobalFree((HGLOBAL)lpSaveButtons);
        
        return TRUE;
}

使用工具列控件

工具列標準按鈕影像索引值

Windows 通用控件示範 (CppWindowsCommonControls)