如何自定义工具栏

大多数基于 Windows 的应用程序都使用工具栏控件,以方便用户使用程序的功能。 但是,静态工具栏也有一些缺点,比如空间太小,无法有效显示所有可用的工具。 解决这一问题的方法就是让用户可以自定义应用程序的工具栏。 然后,用户可以选择只显示他们需要的工具,并以适合个人工作风格的方式来整理这些工具。

注意

无法自定义对话框中的工具栏。

 

若要启用自定义,请在创建工具栏控件时包括 CCS_ADJUSTABLE 公共控件样式标志。 有两种基本的自定义方法:

  • 自定义对话框。 这个系统提供的对话框是最简单的方法。 它为用户提供图形用户界面,允许用户添加、删除或移动图标。
  • 拖放工具。 实现拖放功能允许用户将工具移动到工具栏上的另一个位置,或者通过将工具拖出工具栏来删除工具。 它为用户提供一种简单快捷的方法来组织工具栏,但不允许添加工具。

可以根据应用程序的需求实现任一方法或两种方法。 这两种自定义方法都未提供内置机制,如“取消”或“撤消”按钮,以将工具栏返回到其以前的状态。 必须显式使用工具栏控件 API 来存储工具栏的预自定义状态。 如有必要,稍后可以使用存储的这一信息将工具栏还原到其原始状态。

需要了解的事项

技术

先决条件

  • C/C++
  • Windows 用户界面编程

说明

自定义对话框

自定义对话框由工具栏控件提供,为用户提供一种添加、移动或删除工具的简单方法。 用户可以双击启动工具栏。 应用程序可以通过向工具栏控件发送 TB_CUSTOMIZE 消息以编程方式启动自定义对话框。

下图显示工具栏自定义对话框的示例。

screen shot of a window with a three-item toolbar, and a dialog box with lists of the available and current toolbar buttons

右侧列表框中的工具是当前位于工具栏上的工具。 最初,此列表将包含创建工具栏时指定的工具。 左侧的列表框包含可用于添加到工具栏的工具。 应用程序负责填充该列表(而不是使用自动显示的分隔符)。

工具栏控件通知应用程序正在启动自定义对话框,方法是向其父窗口发送 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 消息,并将 lParam 设置为 TRUE。 此消息的 lParam 值指定是请求保存操作还是还原操作。 发送消息后,可通过两种方式处理保存/还原操作:

  • 使用公共控件版本 4.72 及更早版本,必须实现 TBN_GETBUTTONINFO 处理程序。 工具栏控件发送此通知代码,以请求还原每个按钮的相关信息。
  • 版本 5.80 包含保存/还原选项。 该过程开始时,保存或还原每个按钮时,应用程序将收到 TBN_SAVETBN_RESTORE 通知代码。 若要使用此选项,必须实现通知处理程序,以提供成功保存或还原工具栏状态所需的位图和状态信息。

工具栏状态保存在数据流中,该数据流由 Shell 定义的数据块组成,这些块与应用程序定义的数据块交替使用。 为每个按钮存储每个类型的一个数据块,以及应用程序可以放置在流开头的可选全局数据块。 在保存过程中,TBN_SAVE 处理程序将应用程序定义的块添加到数据流。 在还原过程中,TBN_RESTORE 处理程序读取每个块,并向 Shell 提供重构工具栏所需的信息。

如何处理 TBN_SAVE 通知

第一个 TBN_SAVE 通知代码在保存过程开始时发送。 保存任何按钮之前,将设置 NMTBSAVE 结构的成员,如下表所示。

成员 设置
iItem –1
cbData Shell 定义的数据所需的内存量。
cButtons 按钮数。
pData 应用程序定义的数据所需计算的内存量。 通常,包括一些全局数据,以及每个按钮的数据。 将该值添加到 cbData,并将足够的内存分配给 pData 以全部保存。
pCurrent 数据流中第一个未使用的字节。 如果不需要全局工具栏信息,请设置 pCurrent = pData,使其指向数据流的开头。 如果确实需要全局工具栏信息,请将其存储在 pData 上,并将 pCurrent 设置为数据流未使用部分的开头,然后返回。

 

如果要添加一些全局工具栏信息,请将其放在数据流的开头。 将 pCurrent 推进到全局数据的末尾,以便指向数据流未使用部分的开头,然后返回。

返回后,Shell 将开始保存按钮信息。 它将第一个按钮的 Shell 定义的数据添加到 pCurrent,然后将 pCurrent 推进到未使用部分的开头。

保存每个按钮后,将发送 TBN_SAVE 通知代码,返回 NMTBSAVE,并按如下所示设置这些成员。

成员 设置
iItem 按钮编号从零开始的索引。
pCurrent 指向数据流中第一个未使用的字节的指针。 如果要存储有关该按钮的其他信息,请将它存储在 pCurrent 指向的位置,并更新 pCurrent 以指向之后数据流第一个未使用的部分。
TBBUTTON 描述正在保存的按钮的 TBBUTTON 结构。

 

收到通知代码时,应从 TBBUTTON 提取所需的任何特定于按钮的信息。 请记住,添加按钮时,可以使用 TBBUTTONdwData 成员保存特定于应用程序的数据。 将数据加载到 pCurrent 的数据流中。 将 pCurrent 推进到数据末尾,再次指向流未使用部分的开头,然后返回。

然后,Shell 转到下一个按钮,将其信息添加到 pData,推进 pCurrent,加载 TBBUTTON,并发送另一个 TBN_SAVE 通知代码。 此过程将继续执行,直到保存所有按钮。

还原保存的工具栏

还原过程基本上会反转保存过程。 一开始,应用程序将收到 TBN_RESTORE 通知代码,其中 NMTBRESTORE 结构的 iItem成员设置为 –1。 cbData 成员设置为 pData 的大小,cButtons 设置为按钮数。

通知处理程序应提取保存期间放置在 pData 开头的全局信息,并将 pCurrent 推进到 Shell 定义的数据的第一个块的开头。 将 cBytesPerRecord 设置为用于保存按钮数据的数据块的大小。 将 cButtons 设置为按钮数,然后返回。

下一个 NMTBRESTORE 用于第一个按钮。 pCurrent 成员指向第一个按钮数据块的开头,iItem 设置为按钮索引。 提取该数据并推进 pCurrent。 将数据加载到 TBBUTTON,然后返回。 若要省略已还原工具栏中的按钮,请将 TBBUTTONidCommand 成员设置为零。 Shell 将重复剩余按钮的过程。 除了 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)