在 Office 加载项中创建自定义上下文选项卡

上下文选项卡是 Office 功能区中隐藏的选项卡控件,当 Office 文档中发生指定事件时,它将显示在选项卡行中。 例如,选择表格时 Excel 功能区上显示的“表格 设计 ”选项卡。 在 Office 外接程序中包含自定义上下文选项卡,并通过创建更改可见性的事件处理程序来指定它们何时可见或隐藏。 (但是,自定义上下文选项卡不会响应焦点更改。)

注意

本文假定你熟悉以下文档。 如果你最近未使用加载项命令(自定义菜单项和功能区按钮),请查看该文档。

重要

自定义上下文选项卡目前仅在 Excel 上受支持,并且仅在这些平台和版本中受支持。

  • Windows 上的 Excel:版本 2102 (内部版本 13801.20294) 或更高版本。
  • Mac 上的 Excel:版本 16.53.806.0 或更高版本。
  • Excel 网页版

注意

自定义上下文选项卡仅适用于支持以下要求集的平台。 有关要求集及其使用方式的详细信息,请参阅 指定 Office 应用程序和 API 要求

可以使用代码中的运行时检查来测试用户的主机和平台组合是否支持这些要求集,如 运行时检查方法和要求集支持中所述。 (在清单中指定要求集的技术(本文中也进行了介绍)目前不适用于 RibbonApi 1.2.) 或者,当 不支持自定义上下文选项卡时,可以实现备用 UI 体验

自定义上下文选项卡的行为

自定义上下文选项卡的用户体验遵循内置 Office 上下文选项卡的模式。 下面是放置自定义上下文选项卡的基本原则。

  • 自定义上下文选项卡可见时,它将显示在功能区的右端。
  • 如果同时显示一个或多个内置上下文选项卡和加载项中的一个或多个自定义上下文选项卡,则自定义上下文选项卡始终位于所有内置上下文选项卡的右侧。
  • 如果外接程序具有多个上下文选项卡,并且存在多个可见的上下文,则它们按外接程序中的定义顺序显示。 (方向与 Office 语言的方向相同;也就是说, 在从左到右的语言中为从左到右,但在从右到左的语言中为从右到左。) 请参阅 定义显示在选项卡上的组和控件 ,详细了解如何定义组和控件。
  • 如果多个加载项具有在特定上下文中可见的上下文选项卡,则它们将按加载项的启动顺序显示。
  • 与自定义核心选项卡不同,自定义 上下文 选项卡不会永久添加到 Office 应用程序的功能区。 它们仅存在于运行加载项的 Office 文档中。

在加载项中包含上下文选项卡的主要步骤

下面是在外接程序中包含自定义上下文选项卡的主要步骤。

  1. 将外接程序配置为使用共享运行时。
  2. 定义选项卡及其上显示的组和控件。
  3. 将上下文选项卡注册到 Office。
  4. 指定选项卡可见的情况。

将加载项配置为使用共享运行时

添加自定义上下文选项卡需要外接程序使用 共享运行时。 有关详细信息,请参阅 配置外接程序以使用共享运行时

定义显示在选项卡上的组和控件

与在清单中使用 XML 定义的自定义核心选项卡不同,自定义上下文选项卡是在运行时使用 JSON Blob 定义的。 代码将 Blob 分析为 JavaScript 对象,然后将该对象传递给 Office.ribbon.requestCreateControls 方法。 自定义上下文选项卡仅存在于当前运行加载项的文档中。 这与自定义核心选项卡不同,后者在安装加载项时添加到 Office 应用程序功能区,并在打开其他文档时保持存在。 此外, requestCreateControls 方法只能在加载项的会话中运行一次。 如果再次调用,则会引发错误。

注意

JSON Blob 的属性和子属性的结构 (键名称) 大致与清单 XML 中 CustomTab 元素及其后代元素的结构并行。

我们将逐步构造上下文选项卡 JSON Blob 的示例。 上下文选项卡 JSON 的完整架构位于 dynamic-ribbon.schema.json。 如果使用的是 Visual Studio Code,可以使用此文件获取 IntelliSense 并验证 JSON。 有关详细信息,请参阅使用Visual Studio Code编辑 JSON - JSON 架构和设置

  1. 首先创建包含两个名为 和 tabs的数组属性的 actions JSON 字符串。 数组 actions 是上下文选项卡上的控件可以执行的所有函数的规范。数组 tabs 定义一个或多个上下文选项卡, 最多 20 个

    '{
      "actions": [
    
      ],
      "tabs": [
    
      ]
    }'
    
  2. 上下文选项卡的这个简单示例将只有一个按钮,因此只有一个操作。 添加以下内容作为数组的唯一 actions 成员。 关于此标记,请注意:

    • idtype 属性是必需的。
    • 的值 type 可以是“ExecuteFunction”或“ShowTaskpane”。
    • functionName仅当 的typeExecuteFunction值为 时,才使用 属性。 它是 FunctionFile 中定义的函数的名称。 有关 FunctionFile 的详细信息,请参阅 外接程序命令的基本概念
    • 在后面的步骤中,你将此操作映射到上下文选项卡上的按钮。
    {
      "id": "executeWriteData",
      "type": "ExecuteFunction",
      "functionName": "writeData"
    }
    
  3. 添加以下内容作为数组的唯一 tabs 成员。 关于此标记,请注意:

    • id 属性是必需的。 使用在加载项中的所有上下文选项卡中唯一的简短描述性 ID。
    • label 属性是必需的。 它是一个用户友好的字符串,用作上下文选项卡的标签。
    • groups 属性是必需的。 它定义将在选项卡上显示的控件组。它必须至少有一个成员 且不超过 20 个。 (自定义上下文选项卡上的控件数量也有限制,并且也会限制你拥有的组数。有关详细信息,请参阅下一步。)

    注意

    选项卡对象还可以具有可选 visible 属性,该属性指定在加载项启动时是否立即显示选项卡。 由于上下文选项卡通常是隐藏的,直到用户事件触发其可见性 (例如用户在文档中选择某种类型的实体) ,因此当不存在时, visible 属性默认为 false 。 在后面的部分中,我们将演示如何将 属性 true 设置为 以响应事件。

    {
      "id": "CtxTab1",
      "label": "Contoso Data",
      "groups": [
    
      ]
    }
    
  4. 在简单的持续示例中,上下文选项卡只有一个组。 添加以下内容作为数组的唯一 groups 成员。 关于此标记,请注意:

    • 所有属性都是必需的。
    • 属性 id 在清单中的所有组中必须是唯一的。 使用最多 125 个字符的简短描述性 ID。
    • label是一个用户友好的字符串,用作组的标签。
    • 属性 icon 的值是对象数组,这些对象指定组将在功能区上具有的图标,具体取决于功能区和 Office 应用程序窗口的大小。
    • 属性 controls 的值是对象数组,这些对象指定组中的按钮和菜单。 必须至少有一个。

    重要

    整个选项卡上的控件总数不能超过 20 个。 例如,可以有 3 个组,每个组有 6 个控件,第四个组有 2 个控件,但不能有 4 个组,每个组有 6 个控件。

    {
        "id": "CustomGroup111",
        "label": "Insertion",
        "icon": [
    
        ],
        "controls": [
    
        ]
    }
    
  5. 每个组必须至少有两个大小(32x32 像素和 80x80 像素)的图标。 (可选)还可以具有大小为 16x16 像素、20x20 像素、24x24 像素、40x40 像素、48x48 像素和 64x64 像素的图标。 Office 根据功能区和 Office 应用程序窗口的大小决定要使用的图标。 将以下对象添加到图标数组。 (如果窗口和功能区大小足够大,以便至少显示组中的一个控件,则根本不显示组图标。例如,在收缩和展开Word窗口时,watch Word功能区上的“样式”组。) 关于此标记,请注意:

    • 这两个属性都是必需的。
    • 度量 size 的属性单位为像素。 图标始终是方形的,因此数字是高度和宽度。
    • 属性 sourceLocation 指定图标的完整 URL。

    重要

    正如从开发迁移到生产 ((例如将域从 localhost 更改为 contoso.com) )时,通常必须更改加载项清单中的 URL 一样,还必须更改上下文选项卡 JSON 中的 URL。

    {
        "size": 32,
        "sourceLocation": "https://cdn.contoso.com/addins/datainsertion/Images/Group32x32.png"
    },
    {
        "size": 80,
        "sourceLocation": "https://cdn.contoso.com/addins/datainsertion/Images/Group80x80.png"
    }
    
  6. 在我们的简单持续示例中,组只有一个按钮。 将以下 对象添加为数组的唯一 controls 成员。 关于此标记,请注意:

    • 除 之外 enabled的所有属性都是必需的。
    • type 指定控件的类型。 值可以是“Button”、“Menu”或“MobileButton”。
    • id 最多可以有 125 个字符。
    • actionId 必须是数组中定义的操作的 actions ID。 (请参阅本部分的步骤 1。)
    • label 是一个用户友好的字符串,用作按钮的标签。
    • superTip 表示工具提示的丰富形式。 titledescription 属性都是必需的。
    • icon 指定按钮的图标。 前面有关组图标的注释也适用于此处。
    • enabled (可选) 指定在上下文选项卡启动时是否启用按钮。 如果不存在,则默认值为 true
    {
        "type": "Button",
        "id": "CtxBt112",
        "actionId": "executeWriteData",
        "enabled": false,
        "label": "Write Data",
        "superTip": {
            "title": "Data Insertion",
            "description": "Use this button to insert data into the document."
        },
        "icon": [
            {
                "size": 32,
                "sourceLocation": "https://cdn.contoso.com/addins/datainsertion/Images/WriteDataButton32x32.png"
            },
            {
                "size": 80,
                "sourceLocation": "https://cdn.contoso.com/addins/datainsertion/Images/WriteDataButton80x80.png"
            }
        ]
    }
    

下面是 JSON Blob 的完整示例。

`{
  "actions": [
    {
      "id": "executeWriteData",
      "type": "ExecuteFunction",
      "functionName": "writeData"
    }
  ],
  "tabs": [
    {
      "id": "CtxTab1",
      "label": "Contoso Data",
      "groups": [
        {
          "id": "CustomGroup111",
          "label": "Insertion",
          "icon": [
            {
                "size": 32,
                "sourceLocation": "https://cdn.contoso.com/addins/datainsertion/Images/Group32x32.png"
            },
            {
                "size": 80,
                "sourceLocation": "https://cdn.contoso.com/addins/datainsertion/Images/Group80x80.png"
            }
          ],
          "controls": [
            {
                "type": "Button",
                "id": "CtxBt112",
                "actionId": "executeWriteData",
                "enabled": false,
                "label": "Write Data",
                "superTip": {
                    "title": "Data Insertion",
                    "description": "Use this button to insert data into the document."
                },
                "icon": [
                    {
                        "size": 32,
                        "sourceLocation": "https://cdn.contoso.com/addins/datainsertion/Images/WriteDataButton32x32.png"
                    },
                    {
                        "size": 80,
                        "sourceLocation": "https://cdn.contoso.com/addins/datainsertion/Images/WriteDataButton80x80.png"
                    }
                ]
            }
          ]
        }
      ]
    }
  ]
}`

使用 requestCreateControls 向 Office 注册上下文选项卡

上下文选项卡通过调用 Office.ribbon.requestCreateControls 方法注册到 Office。 这通常在分配给 Office.initialize 的函数中完成,或使用 函数 Office.onReady 完成。 有关这些函数和初始化加载项的详细信息,请参阅 初始化 Office 外接程序。 但是,可以在初始化后随时调用 方法。

重要

requestCreateControls 加载项的给定会话中,只能调用一次 方法。 如果再次调用,将引发错误。

示例如下。 请注意,必须先使用 方法将 JSON 字符串转换为 JavaScript 对象, JSON.parse 然后才能将其传递给 JavaScript 函数。

Office.onReady(async () => {
    const contextualTabJSON = ` ... `; // Assign the JSON string such as the one at the end of the preceding section.
    const contextualTab = JSON.parse(contextualTabJSON);
    await Office.ribbon.requestCreateControls(contextualTab);
});

使用 requestUpdate 指定选项卡何时可见的上下文

通常,当用户发起的事件更改加载项上下文时,应显示自定义上下文选项卡。 假设在 Excel 工作簿的默认工作表上激活图表 (时(且仅当)激活时,选项卡才可见) 。

首先分配处理程序。 这通常在 函数中 Office.onReady 完成,如以下示例中所示,该函数将处理程序 (在后面的步骤中创建) 分配给 onActivated 工作表中所有图表的 和 onDeactivated 事件。

Office.onReady(async () => {
    const contextualTabJSON = ` ... `; // Assign the JSON string.
    const contextualTab = JSON.parse(contextualTabJSON);
    await Office.ribbon.requestCreateControls(contextualTab);

    await Excel.run(context => {
        const charts = context.workbook.worksheets
            .getActiveWorksheet()
            .charts;
        charts.onActivated.add(showDataTab);
        charts.onDeactivated.add(hideDataTab);
        return context.sync();
    });
});

接下来,定义处理程序。 下面是 的一个 showDataTab简单示例,但请参阅本文后面的 处理 HostRestartNeeded 错误 ,以获取更可靠的函数版本。 关于此代码,请注意以下几点:

  • Office 控制何时更新功能区的状态。 Office.ribbon.requestUpdate 方法将更新请求排队。 方法将在请求排队后立即解析 Promise 对象,而不是在功能区实际更新时解析。
  • 方法的参数 requestUpdate一个 RibbonUpdaterData 对象, (1 个) 按其 ID 完全 按照 JSON 中指定的 ID 指定选项卡, (2) 指定选项卡的可见性。
  • 如果有多个应在同一上下文中可见的自定义上下文选项卡,只需向数组添加其他选项卡对象 tabs 即可。
async function showDataTab() {
    await Office.ribbon.requestUpdate({
        tabs: [
            {
                id: "CtxTab1",
                visible: true
            }
        ]});
}

隐藏选项卡的处理程序几乎相同,只不过它将 属性设置visible回 。false

Office JavaScript 库还提供了多个接口, (类型) ,以便更轻松地构造RibbonUpdateData 对象。 下面是 showDataTab TypeScript 中的 函数,它利用这些类型。

const showDataTab = async () => {
    const myContextualTab: Office.Tab = {id: "CtxTab1", visible: true};
    const ribbonUpdater: Office.RibbonUpdaterData = { tabs: [ myContextualTab ]};
    await Office.ribbon.requestUpdate(ribbonUpdater);
}

同时切换选项卡可见性和按钮的启用状态

方法 requestUpdate 还用于切换自定义上下文选项卡或自定义核心选项卡上自定义按钮的启用或禁用状态。有关此内容的详细信息,请参阅 启用和禁用外接程序命令。 在某些情况下,你可能希望同时更改选项卡的可见性和按钮的启用状态。 为此,只需调用 requestUpdate一次 。 下面是一个示例,其中在启用核心选项卡上的按钮的同时,上下文选项卡可见。

function myContextChanges() {
    Office.ribbon.requestUpdate({
        tabs: [
            {
                id: "CtxTab1",
                visible: true
            },
            {
                id: "OfficeAppTab1",
                groups: [
                    {
                        id: "CustomGroup111",
                        controls: [
                            {
                                id: "MyButton",
                                enabled: true
                            }
                        ]
                    }
                ]
            ]}
        ]
    });
}

在以下示例中,启用的按钮位于可见的上下文选项卡上。

function myContextChanges() {
    Office.ribbon.requestUpdate({
        tabs: [
            {
                id: "CtxTab1",
                visible: true,
                groups: [
                    {
                        id: "CustomGroup111",
                        controls: [
                            {
                                id: "MyButton",
                                enabled: true
                           }
                       ]
                   }
               ]
            }
        ]
    });
}

从上下文选项卡打开任务窗格

若要从自定义上下文选项卡上的按钮打开任务窗格,请在 JSON type 中创建包含 的 ShowTaskpane操作。 然后定义一个按钮,并将 actionId 属性设置为 id 操作的 。 这会打开清单中的 Runtime> 元素指定的<默认任务窗格。

`{
  "actions": [
    {
      "id": "openChartsTaskpane",
      "type": "ShowTaskpane",
      "title": "Work with Charts",
      "supportPinning": false
    }
  ],
  "tabs": [
    {
      // some tab properties omitted
      "groups": [
        {
          // some group properties omitted
          "controls": [
            {
                "type": "Button",
                "id": "CtxBt112",
                "actionId": "openChartsTaskpane",
                "enabled": false,
                "label": "Open Charts Taskpane",
                // some control properties omitted
            }
          ]
        }
      ]
    }
  ]
}`

若要打开任何不是默认任务窗格的任务窗格,请在操作的定义中指定属性 sourceLocation 。 在以下示例中,将从其他按钮打开第二个任务窗格。

重要

  • sourceLocation为操作指定时,任务窗格不使用共享运行时。 它在新的单独运行时中运行。
  • 不能有一个任务窗格可以使用共享运行时,因此,类型为 ShowTaskpane 的多个操作不能省略 属性 sourceLocation
`{
  "actions": [
    {
      "id": "openChartsTaskpane",
      "type": "ShowTaskpane",
      "title": "Work with Charts",
      "supportPinning": false
    },
    {
      "id": "openTablesTaskpane",
      "type": "ShowTaskpane",
      "title": "Work with Tables",
      "supportPinning": false
      "sourceLocation": "https://MyDomain.com/myPage.html"
    }
  ],
  "tabs": [
    {
      // some tab properties omitted
      "groups": [
        {
          // some group properties omitted
          "controls": [
            {
                "type": "Button",
                "id": "CtxBt112",
                "actionId": "openChartsTaskpane",
                "enabled": false,
                "label": "Open Charts Taskpane",
                // some control properties omitted
            },
            {
                "type": "Button",
                "id": "CtxBt113",
                "actionId": "openTablesTaskpane",
                "enabled": false,
                "label": "Open Tables Taskpane",
                // some control properties omitted
            }
          ]
        }
      ]
    }
  ]
}`

本地化 JSON 文本

传递给 requestCreateControls 的 JSON Blob 的本地化方式与本地化自定义核心选项卡的清单标记的方式不同, (从 清单) 控制本地化 中所述。 相反,必须在运行时使用每个区域设置的不同 JSON Blob 进行本地化。 建议使用测试 switchOffice.context.displayLanguage 属性的语句。 示例如下。

function GetContextualTabsJsonSupportedLocale () {
    const displayLanguage = Office.context.displayLanguage;

        switch (displayLanguage) {
            case 'en-US':
                return `{
                    "actions": [
                        // actions omitted
                     ],
                    "tabs": [
                        {
                          "id": "CtxTab1",
                          "label": "Contoso Data",
                          "groups": [
                              // groups omitted
                          ]
                        }
                    ]
                }`;

            case 'fr-FR':
                return `{
                    "actions": [
                        // actions omitted 
                    ],
                    "tabs": [
                        {
                          "id": "CtxTab1",
                          "label": "Contoso Données",
                          "groups": [
                              // groups omitted
                          ]
                       }
                    ]
               }`;

            // Other cases omitted
       }
}

然后,代码调用 函数以获取传递给 requestCreateControls的本地化 Blob,如以下示例所示。

const contextualTabJSON = GetContextualTabsJsonSupportedLocale();

自定义上下文选项卡的最佳做法

在不支持自定义上下文选项卡时实现备用 UI 体验

平台、Office 应用程序和 Office 内部版本的某些组合不支持 requestCreateControls。 外接程序应设计为为在这些组合之一上运行加载项的用户提供替代体验。 以下部分介绍提供回退体验的两种方法。

使用非文本选项卡或控件

有一个清单元素 OverriddenByRibbonApi,旨在创建外接程序中的回退体验,当外接程序在不支持自定义上下文选项卡的应用程序或平台上运行时,该外接程序实现自定义上下文选项卡。

使用此元素的最简单策略是定义自定义核心选项卡 (即, 非文本自定义 选项卡) 在清单中复制外接程序中自定义上下文选项卡的功能区自定义项。 但添加 <OverriddenByRibbonApi>true</OverriddenByRibbonApi> 为自定义核心选项卡上重复的 GroupControl 和 menu <Item> 元素的第一个子元素。 这样做的效果如下:

  • 如果外接程序在支持自定义上下文选项卡的应用程序和平台上运行,则自定义核心组和控件不会出现在功能区上。 相反,当外接程序调用 requestCreateControls 方法时,将创建自定义上下文选项卡。
  • 如果外接程序在 不支持requestCreateControls的应用程序或平台上运行,则元素会显示在自定义核心选项卡上。

示例如下。 请注意,仅当不支持自定义上下文选项卡时,“MyButton”才会显示在自定义核心选项卡上。 但是,无论是否支持自定义上下文选项卡,都会显示父组和自定义核心选项卡。

<OfficeApp ...>
  ...
  <VersionOverrides ...>
    ...
    <Hosts>
      <Host ...>
        ...
        <DesktopFormFactor>
          <ExtensionPoint ...>
            <CustomTab ...>              
              ...
              <Group ...>
                ...
                <Control ... id="Contoso.MyButton1">
                  <OverriddenByRibbonApi>true</OverriddenByRibbonApi>
                  ...
                  <Action ...>
...
</OfficeApp>

有关更多示例,请参阅 OverriddenByRibbonApi

当父组或菜单标记为 <OverriddenByRibbonApi>true</OverriddenByRibbonApi>时,它不可见,并且当不支持自定义上下文选项卡时,将忽略其所有子标记。 因此,这些子元素中是否有任何一个具有 <OverriddenByRibbonApi> 元素或其值并不重要。 这意味着,如果菜单项或控件必须在所有上下文中可见,则不仅不应使用 <OverriddenByRibbonApi>true</OverriddenByRibbonApi>标记,而且 其上级菜单和组也不得以这种方式标记

重要

不要使用 <OverriddenByRibbonApi>true</OverriddenByRibbonApi>标记组或菜单的所有子元素。 如果父元素因上一段中给出的原因而标记为 <OverriddenByRibbonApi>true</OverriddenByRibbonApi> ,则这是毫无意义的。 此外,如果在父 (上省略 <OverriddenByRibbonApi> 或将其设置为 false) ,则无论是否支持自定义上下文选项卡,父选项卡都将显示,但当支持自定义上下文选项卡时,它们都将为空。 因此,如果支持自定义上下文选项卡时不应显示所有子元素,请使用 标记父级 <OverriddenByRibbonApi>true</OverriddenByRibbonApi>

使用在指定上下文中显示或隐藏任务窗格的 API

作为 OverriddenByRibbonApi> 的<替代方法,加载项可以使用 UI 控件定义任务窗格,这些控件在自定义上下文选项卡上复制控件的功能。然后,使用 Office.addin.showAsTaskpaneOffice.addin.hide 方法显示任务窗格(如果支持上下文选项卡)。 有关如何使用这些方法的详细信息,请参阅 显示或隐藏 Office 外接程序的任务窗格

处理 HostRestartNeeded 错误

在某些情况下,Office 无法更新功能区,并将返回错误。 例如,如果升级了加载项,并且升级后的加载项具有一组不同的自定义加载项命令,则必须关闭并重新打开 Office 应用程序。 在此之前,requestUpdate 方法将返回错误 HostRestartNeeded。 代码应处理此错误。 下面是操作方法的示例。 在此示例中,reportError 方法向用户显示错误。

function showDataTab() {
    try {
        Office.ribbon.requestUpdate({
            tabs: [
                {
                    id: "CtxTab1",
                    visible: true
                }
            ]});
    }
    catch(error) {
        if (error.code == "HostRestartNeeded"){
            reportError("Contoso Awesome Add-in has been upgraded. Please save your work, then close and reopen the Office application.");
        }
    }
}

资源