如何自訂工具列
大部分的 Windows 應用程式都會使用工具列控制件,讓使用者方便存取程式功能。 不過,靜態工具列有一些缺點,例如空間太少,無法有效地顯示所有可用的工具。 此問題的解決方案是讓您的應用程式工具列成為使用者可自定義的工具列。 然後,用戶可以選擇只顯示所需的工具,而且他們可以使用適合其個人工作方式組織工具。
注意
無法自訂對話框中的工具列。
若要啟用自定義功能,請在 建立工具列控件時包含CCS_ADJUSTABLE 通用控件樣式旗標。 自訂有兩種基本方法:
- 自訂對話框。 這個系統提供的對話框是最簡單的方法。 它為使用者提供圖形使用者介面,讓他們能夠新增、刪除或移動圖示。
- 拖放工具。 實作拖放功能可讓使用者將工具移至工具列上的另一個位置,或將其從工具列上拖曳而刪除。 它為使用者提供了一種快速且簡單的方法來組織其工具列,但不允許他們新增工具。
視應用程式的需求而定,您可以實作任一種方法或兩者。 這兩種方法都未提供內建機制,例如 [取消] 或 [復原] 按鈕,以將工具列傳回其先前的狀態。 您必須明確使用工具列控制元件 API 來儲存工具列的預先自定義狀態。 如有必要,您稍後可以使用這個預存資訊,將工具列還原為其原始狀態。
您需要知道的事項
技術
必要條件
- C/C++
- Windows 使用者介面程序設計
指示
自訂對話框
工具列控制項會提供自訂對話框,讓使用者以簡單的方式新增、移動或刪除工具。 用戶可以按兩下工具列來啟動它。 應用程式可以透過程式設計方式啟動自定義對話框,方法是傳送工具列控件 TB_CUSTOMIZE 訊息。
下圖顯示工具列自定義對話框的範例。
右側清單框中的工具是工具列上目前的工具。 一開始,此列表會包含您在建立工具列時指定的工具。 左側清單框包含可用來新增至工具列的工具。 您的應用程式負責填入該清單(除了 [分隔符] 之外,該清單會自動出現)。
工具列控件會通知您的應用程式正在啟動自定義對話框,方法是傳送其父視窗TBN_BEGINADJUST通知程式代碼,後面接著TBN_INITCUSTOMIZE通知碼。 在大部分情況下,應用程式不需要回應這些通知碼。 不過,如果您不希望 [自定義工具列] 對話框顯示 [說明] 按鈕,請傳回TBNRF_HIDEHELP來處理TBN_INITCUSTOMIZE。
然後,工具列控件會依下列順序傳送三系列通知代碼,以收集它需要初始化對話框的資訊:
- 工具列上每個按鈕的TBN_QUERYINSERT通知碼,以判斷可以插入按鈕的位置。 傳回 FALSE 以防止按鈕插入通知訊息中指定的按鈕左邊。 如果您將所有TBN_QUERYINSERT通知碼傳回 FALSE ,將不會顯示對話方塊。
- 目前在工具列上之每個工具的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_QUERYDELETE 和 TBN_QUERYINSERT。 若要控制拖放程式,請依照下列方式處理這些通知碼:
- 一旦使用者嘗試移動按鈕,就會在顯示準刪除按鈕之前,立即傳送TBN_QUERYDELETE通知碼。 傳回 FALSE 以防止移動按鈕。 如果您傳回 TRUE,使用者就可以將工具從工具列上卸載,來移動工具或刪除此工具。 如果可以移動工具,則可以刪除此工具。 不過,如果使用者刪除工具,工具列控件會將應用程式 傳送TBN_DELETINGBUTTON 通知碼,此時您可以選擇在其原始位置重新插入按鈕,從而取消刪除。
- 當使用者嘗試卸除工具列上的按鈕時,會 傳送TBN_QUERYINSERT 通知碼。 若要防止將移動的按鈕卸除至通知中指定的按鈕左邊,請傳回 FALSE。 如果使用者將工具從工具列上卸除,則不會傳送此通知程序代碼。
如果用戶嘗試拖曳按鈕而不按 SHIFT 鍵,工具列控件將不會處理拖放作業。 不過,它會傳送應用程式TBN_BEGINDRAG通知程式代碼,以指出拖曳作業的開始,以及TBN_ENDDRAG通知程式代碼來指出結尾。 如果您想要啟用這種形式的拖放功能,您的應用程式必須處理這些通知碼、提供必要的使用者介面,以及修改工具列以反映任何變更。
儲存和還原工具列
在自訂工具列的過程中,您的應用程式可能需要儲存資訊,以便您將工具列還原為其原始狀態。 若要起始儲存或還原工具列狀態,請將 lParam 設定為 TRUE 的TB_SAVERESTORE訊息傳送工具列控制件。 此 訊息的 lParam 值會指定您要要求儲存或還原作業。 傳送訊息之後,有兩種方式可以處理儲存/還原作業:
- 使用 4.72 版和更早版本的一般控件,您必須實作TBN_GETBUTTONINFO處理程式。 工具列控件會傳送此通知程式代碼,以在還原時要求每個按鈕的相關信息。
- 5.80 版包含儲存/還原選項。 在程式的開頭,而且隨著每個按鈕的儲存或還原,您的應用程式會收到 TBN_SAVE 或 TBN_RESTORE 通知碼。 若要使用此選項,您必須實作通知處理程式,以提供成功儲存或還原工具列狀態所需的點陣圖和狀態資訊。
工具列狀態會儲存在數據流中,其中包含殼層定義數據區塊與應用程式定義數據區塊交替的區塊。 每個類型的一個數據區塊會針對每個按鈕儲存,以及應用程式可以在數據流開頭放置的選擇性全域數據區塊。 在儲存程式期間,您的 TBN_SAVE 處理程式會將應用程式定義的區塊新增至數據流。 在還原程式期間, TBN_RESTORE 處理程式會讀取每個區塊,並提供殼層重建工具列所需的資訊。
如何處理TBN_SAVE通知
第一個TBN_SAVE通知碼會在儲存程序開始時傳送。 儲存任何按鈕之前,系統會設定 NMTBSAVE 結構的成員,如下表所示。
member | 設定 |
---|---|
iItem | –1 |
cbData | Shell 定義數據所需的記憶體數量。 |
cButtons | 按鈕數目。 |
pData | 應用程式定義資料所需的記憶體計算量。 一般而言,您會包含一些全域數據,以及每個按鈕的數據。 將該值新增至 cbData ,並將足夠的記憶體配置給 pData 來保存它。 |
pCurrent | 數據流中第一個未使用的位元組。 如果您不需要全域工具列資訊,請設定 pCurrent = pData,使其指向數據流的開頭。 如果您需要全域工具列資訊,請將它儲存在 pData,然後在傳回之前,將 pCurrent 設定為數據流未使用部分的開頭。 |
如果您想要新增一些全域工具列資訊,請將它放在數據流的開頭。 將 pCurrent 前進到全域資料的結尾,使其指向資料流未使用部分的開頭,並傳回 。
返回之後,Shell 會開始儲存按鈕資訊。 它會在 pCurrent 新增第一個按鈕的 Shell 定義數據,然後將 pCurrent 前進到未使用部分的開頭。
儲存每個按鈕之後,會傳送TBN_SAVE通知碼,並傳回NMTBSAVE,並依照下列方式設定這些成員。
member | 設定 |
---|---|
iItem | 按鈕編號以零起始的索引。 |
pCurrent | 數據流中第一個未使用位元組的指標。 如果您想要儲存按鈕的其他資訊,請將它儲存在 pCurrent 所指向的位置,並更新 pCurrent 以指向之後數據流的第一個未使用部分。 |
TBBUTTON | TBBUTTON 結構,描述要儲存的按鈕。 |
當您收到通知程式代碼時,應該從 TBBUTTON 擷取您需要的任何按鈕特定資訊。 請記住,當您新增按鈕時,您可以使用 TBBUTTON 的 dwData 成員來保存應用程式特定的數據。 將數據載入 pCurrent 的數據流。 將 pCurrent 前進到資料結尾,再次指向資料流未使用部分的開頭,並傳回 。
殼層接著會移至下一個按鈕,將其資訊新增至 pData、前進 pCurrent、載入 TBBUTTON,並傳送另一個 TBN_SAVE 通知碼。 此程式會繼續進行,直到儲存所有按鈕為止。
還原儲存的工具列
還原程式基本上會反轉儲存程式。 一開始,您的應用程式會收到TBN_RESTORE通知程式代碼,且 NMTBRESTORE 結構的 iItem 成員設定為 –1。 cbData 成員會設定為 pData 的大小,而 cButtons 會設定為按鈕數目。
您的通知處理程式應該擷取儲存期間 pData 開頭的全域資訊,並將 pCurrent 前進到殼層定義數據的第一個區塊的開頭。 將 cBytesPerRecord 設定為您用來儲存按鈕數據的數據區塊大小。 將 cButtons 設定為按鈕數目,然後傳回。
下一個 NMTBRESTORE 適用於第一個按鈕。 pCurrent 成員會指向第一個按鈕數據區塊的開頭,而iItem會設定為按鈕索引。 擷取該數據並前進 pCurrent。 將數據載入 TBBUTTON,然後傳回。 若要省略已還原工具列中的按鈕,請將 TBBUTTON 的 idCommand 成員設定為零。 殼層會針對其餘按鈕重複此程式。 除了 NMTBSAVE 和 NMTBRESTORE 訊息之外,您也可以使用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;
}
相關主題