教程:创建 Excel 任务窗格加载项

在本教程中,将创建 Excel 任务窗格加载项,该加载项将:

  • 创建表格
  • 筛选和排序表格
  • 创建图表
  • 冻结表格标题
  • 保护工作表
  • 打开对话框

提示

如果已使用 Yeoman 生成器完成了“创建 Excel 任务窗格加载项”快速入门,并希望使用此项目作为该教程的起点,直接转至“创建表”以开始此教程。

如果需要本教程的完整版本,请转到 GitHub 上的 Office 外接程序示例存储库

先决条件

  • Node.js(最新LTS 版本)。 访问 Node.js 站点 ,下载并安装适合你的操作系统的版本。

  • 最新版本的 Yeoman 和适用于 Office 加载项的 Yeoman 生成器。若要全局安装这些工具,请从命令提示符处运行以下命令。

    npm install -g yo generator-office
    

    注意

    即便先前已安装了 Yeoman 生成器,我们还是建议你通过 npm 将包更新为最新版本。

  • 已连接到 Microsoft 365 订阅的 Office (包括 Office 网页版)。

    注意

    如果你还没有 Office,你可能有资格通过 Microsoft 365 开发人员计划获得Microsoft 365 E5开发人员订阅;有关详细信息,请参阅常见问题解答。 或者,可以 注册 1 个月的免费试用版购买 Microsoft 365 计划

创建加载项项目

运行以下命令,使用 Yeoman 生成器创建加载项项目。 包含项目的文件夹将添加到当前目录。

yo office

注意

运行该yo office命令时,可能会收到有关 Yeoman 和 Office 加载项 CLI 工具的数据收集策略的提示。 根据你的需要,使用提供的信息来响应提示。

出现提示时,请提供以下信息以创建加载项项目。

  • 选择项目类型:Office Add-in Task Pane project
  • 选择脚本类型:JavaScript
  • 要为外接程序命名什么名称?My Office Add-in
  • 你希望支持哪个 Office 客户端应用程序?Excel

Yeoman Office 外接程序生成器命令行界面。

完成此向导后,生成器会创建项目,并安装支持的 Node 组件。 如果在初始设置过程中出现问题,可能需要在项目的根文件夹中手动运行 npm install

注意

如果使用 Node.js 20.0.0 或更高版本,则当生成器运行安装时,可能会看到一条警告,指出你的引擎不受支持。 我们正在努力解决此问题。 同时,警告不会影响生成的生成器或项目,因此可以忽略它。

提示

创建加载项项目后,可忽略 Yeoman 生成器提供的后续步骤指南。 本文中的分步说明提供了完成本教程所需的全部指南。

创建表

本教程的这一步是,以编程方式测试加载项是否支持用户的当前版本 Excel,向工作表中添加表格,使用数据填充表格,并设置格式。

编码加载项

  1. 在代码编辑器中打开项目。

  2. 打开 ./src/taskpane/taskpane.html 文件。 此文件含有任务窗格的 HTML 标记。

  3. 找到 <main> 元素并删除在开始 <main> 标记后和关闭 </main> 标记前出现的所有行。

  4. 打开 <main> 标记后立即添加下列标记:

    <button class="ms-Button" id="create-table">Create Table</button><br/><br/>
    
  5. 打开 ./src/taskpane/taskpane.js 文件。 此文件包含用于加快任务窗格与 Office 客户端应用程序之间的交互的 Office JavaScript API 代码。

  6. 执行以下操作,删除对 run 按钮和 run() 函数的所有引用:

    • 查找并删除行 document.getElementById("run").onclick = run;

    • 查找并删除整个 run() 函数。

  7. Office.onReady 函数调用中,找到行 if (info.host === Office.HostType.Excel) { 并紧跟该行添加下列代码。 注意:

    • 此代码为 create-table 按钮添加事件处理程序。
    • 函数 createTable 包装在调用 tryCatch 中, (将在下一步) 添加这两个函数。 这允许独立于服务代码处理 Office JavaScript 层生成的任何错误。
    // Assign event handlers and other initialization logic.
    document.getElementById("create-table").onclick = () => tryCatch(createTable);
    
  8. 将以下函数添加到文件末尾。 注意:

    • Excel.js 业务逻辑将添加到传递给 Excel.run 的函数。 此逻辑不立即执行。 相反,它会被添加到挂起的命令队列中。

    • context.sync 方法将所有已排入队列的命令发送到 Excel 以供执行。

    • 通过 tryCatch 任务窗格与工作簿交互的所有函数都将使用该函数。 以这种方式捕获 Office JavaScript 错误是一种通用处理任何未捕获的错误的便捷方法。

    注意

    以下代码使用 ES6 JavaScript,与 旧版 Office 不兼容,后者使用 Trident (Internet Explorer 11) 浏览器引擎。 有关如何在生产环境中支持这些平台的信息,请参阅 支持较旧的 Microsoft Webviews 和 Office 版本。 你可能有资格获得具有最新 Office 应用程序的Microsoft 365 E5开发人员订阅,以便通过 Microsoft 365 开发人员计划进行开发;有关详细信息,请参阅常见问题解答。 或者,可以 注册 1 个月的免费试用版购买 Microsoft 365 计划

    async function createTable() {
        await Excel.run(async (context) => {
    
            // TODO1: Queue table creation logic here.
    
            // TODO2: Queue commands to populate the table with data.
    
            // TODO3: Queue commands to format the table.
    
            await context.sync();
        });
    }
    
    /** Default helper for invoking an action and handling errors. */
    async function tryCatch(callback) {
        try {
            await callback();
        } catch (error) {
            // Note: In a production add-in, you'd want to notify the user through your add-in's UI.
            console.error(error);
        }
    }
    
  9. createTable() 函数中,将 TODO1 替换为以下代码。 注意:

    • 该代码使用 add 工作表的表集合的 方法创建一个表,即使它为空,该集合也始终存在。 这是创建 Excel.js 对象的标准方式。 没有类构造函数 API,切勿使用 new 运算符创建 Excel 对象。 相反,请添加到父集合对象。

    • add 方法的第一个参数仅是表格最上面一行的范围,而不是表格最终使用的整个范围。 这是因为当加载项填充数据行时(在下一步中),它将新行添加到表中,而不是将值写入现有行的单元格。 这是一种常见模式,因为在创建表时,表将具有的行数通常未知。

    • 表名称必须在整个工作簿中都是唯一的,而不仅仅是在工作表一级。

    const currentWorksheet = context.workbook.worksheets.getActiveWorksheet();
    const expensesTable = currentWorksheet.tables.add("A1:D1", true /*hasHeaders*/);
    expensesTable.name = "ExpensesTable";
    
  10. createTable() 函数中,将 TODO2 替换为以下代码。 注意:

    • 范围的单元格值是通过一组数组进行设置。

    • 表格中的新行是通过调用表格的行集合的 add 方法进行创建。 通过在作为第二个参数传递的父数组中添加多个单元格值数组,可以在一次 add 调用中添加多个行。

    expensesTable.getHeaderRowRange().values =
        [["Date", "Merchant", "Category", "Amount"]];
    
    expensesTable.rows.add(null /*add at the end*/, [
        ["1/1/2017", "The Phone Company", "Communications", "120"],
        ["1/2/2017", "Northwind Electric Cars", "Transportation", "142.33"],
        ["1/5/2017", "Best For You Organics Company", "Groceries", "27.9"],
        ["1/10/2017", "Coho Vineyard", "Restaurant", "33"],
        ["1/11/2017", "Bellows College", "Education", "350.1"],
        ["1/15/2017", "Trey Research", "Other", "135"],
        ["1/15/2017", "Best For You Organics Company", "Groceries", "97.88"]
    ]);
    
  11. createTable() 函数中,将 TODO3 替换为以下代码。 注意:

    • 此代码将从零开始编制的索引传递给表格的列集合的 getItemAt 方法,以获取对“金额”列的引用。

      注意

      Excel.js 集合对象(如 TableCollectionWorksheetCollectionTableColumnCollection)有 items 属性,此属性是子对象类型的数组(如 TableWorksheetTableColumn),但 *Collection 对象本身并不是数组。

    • 然后,此代码将“金额”列的范围格式化为欧元(精确到小数点后两位)。 在数字格式代码一文中详细了解 Excel 数字格式语法/

    • 最后,它确保了列宽和行高足以容纳最长(或最高)的数据项。 请注意,此代码必须获取要格式化的 Range 对象。 TableColumnTableRow 对象没有格式属性。

    expensesTable.columns.getItemAt(3).getRange().numberFormat = [['\u20AC#,##0.00']];
    expensesTable.getRange().format.autofitColumns();
    expensesTable.getRange().format.autofitRows();
    
  12. 验证是否已保存了对项目所做的所有更改。

测试加载项

  1. 完成以下步骤,以启动本地 Web 服务器并旁加载你的加载项。

    注意

    即使在开发过程中,Office 外接程序也应使用 HTTPS,而不是 HTTP。 如果在运行以下命令之一后系统提示安装证书,请接受安装 Yeoman 生成器提供的证书的提示。 你可能还必须以管理员身份运行命令提示符或终端才能进行更改。

    提示

    如果在 Mac 上测试加载项,请先运行项目根目录中的以下命令,然后再继续。 运行此命令时,本地 Web 服务器将启动。

    npm run dev-server
    
    • 若要在 Excel 中测试加载项,请在项目的根目录中运行以下命令。 这将启动本地的 Web 服务器 (如果尚未运行的话), 并使用加载的加载项打开 Excel。

      npm start
      
    • 若要在 Excel 网页版中测试加载项,请在项目的根目录中运行以下命令。 运行此命令时,本地 Web 服务器将启动。 将 "{url}" 替换为你拥有权限的 OneDrive 或 SharePoint 库上 Excel 文档的 URL。

      注意

      如果在 Mac 上进行开发,请将 括 {url} 在单引号中。 请勿在 Windows 上执行此操作。

      npm run start:web -- --document {url}
      

      示例如下。

      • npm run start:web -- --document https://contoso.sharepoint.com/:t:/g/EZGxP7ksiE5DuxvY638G798BpuhwluxCMfF1WZQj3VYhYQ?e=F4QM1R
      • npm run start:web -- --document https://1drv.ms/x/s!jkcH7spkM4EGgcZUgqthk4IK3NOypVw?e=Z6G1qp
      • npm run start:web -- --document https://contoso-my.sharepoint-df.com/:t:/p/user/EQda453DNTpFnl1bFPhOVR0BwlrzetbXvnaRYii2lDr_oQ?e=RSccmNP

      如果外接程序未在文档中旁加载,请按照手动旁加载加载项中的说明手动旁加载到Office web 版

  2. 在 Excel 中,选择“ 开始 ”选项卡,然后选择功能区上的“ 显示任务窗格 ”按钮以打开加载项任务窗格。

    Excel 主页菜单,突出显示了“显示任务窗格”按钮。

  3. 在任务窗格中,选择“创建表”按钮。

    Excel 显示包含“创建表格”按钮的外接程序任务窗格,工作表中填充了日期、商家、类别和金额数据的表格。

筛选和排序表格

本教程的这一步是,筛选并排序之前创建的表。

筛选表格

  1. 打开 ./src/taskpane/taskpane.html 文件。

  2. 查找create-table按钮的<button>元素,并在行后添加下列标记。

    <button class="ms-Button" id="filter-table">Filter Table</button><br/><br/>
    
  3. 打开 ./src/taskpane/taskpane.js 文件。

  4. Office.onReady 函数调用中,定位将单击处理程序分配到 create-table 按钮的行,并在该行后添加以下代码。

    document.getElementById("filter-table").onclick = () => tryCatch(filterTable);
    
  5. 将以下函数添加到文件结尾。

    async function filterTable() {
        await Excel.run(async (context) => {
    
            // TODO1: Queue commands to filter out all expense categories except
            //        Groceries and Education.
    
            await context.sync();
        });
    }
    
  6. filterTable() 函数中,将 TODO1 替换为以下代码。 注意:

    • 代码先将列名称传递给 getItem 方法(而不是像 getItemAt 方法一样将列索引传递给 createTable 方法),获取对需要筛选的列的引用。 由于用户可以移动表格列,因此给定索引处的列可能会在表格创建后更改。 所以,更安全的做法是,使用列名称获取对列的引用。 上一教程安全地使用了 getItemAt,因为是在与创建表格完全相同的方法中使用了它,所以用户没有机会移动列。

    • applyValuesFilter 方法是对 Filter 对象执行的多种筛选方法之一。

    const currentWorksheet = context.workbook.worksheets.getActiveWorksheet();
    const expensesTable = currentWorksheet.tables.getItem('ExpensesTable');
    const categoryFilter = expensesTable.columns.getItem('Category').filter;
    categoryFilter.applyValuesFilter(['Education', 'Groceries']);
    

排序表格

  1. 打开 ./src/taskpane/taskpane.html 文件。

  2. 查找filter-table按钮的<button>元素,并在行后添加下列标记。

    <button class="ms-Button" id="sort-table">Sort Table</button><br/><br/>
    
  3. 打开 ./src/taskpane/taskpane.js 文件。

  4. Office.onReady 函数调用中,定位将单击处理程序分配到 filter-table 按钮的行,并在该行后添加以下代码。

    document.getElementById("sort-table").onclick = () => tryCatch(sortTable);
    
  5. 将以下函数添加到文件结尾。

    async function sortTable() {
        await Excel.run(async (context) => {
    
            // TODO1: Queue commands to sort the table by Merchant name.
    
            await context.sync();
        });
    }
    
  6. sortTable() 函数中,将 TODO1 替换为以下代码。 注意:

    • 此代码创建一组 SortField 对象,其中只有一个成员,因为加载项只对“商家”列进行了排序。

    • SortField 对象的 key 属性是用于排序的列的从零开始编制索引。 表中的行按照所引用列中的值进行排序。

    • Tablesort 成员是 TableSort 对象,并不是方法。 SortField 传递到 TableSort 对象的 apply 方法。

    const currentWorksheet = context.workbook.worksheets.getActiveWorksheet();
    const expensesTable = currentWorksheet.tables.getItem('ExpensesTable');
    const sortFields = [
        {
            key: 1,            // Merchant column
            ascending: false,
        }
    ];
    
    expensesTable.sort.apply(sortFields);
    
  7. 验证是否已保存了对项目所做的所有更改。

测试加载项

  1. 如果本地 Web 服务器已在运行,并且加载项已加载到 Excel 中,请继续执行步骤 2。 否则,启动本地 Web 服务器并旁加载你的加载项:

    • 若要在 Excel 中测试加载项,请在项目的根目录中运行以下命令。 这将启动本地的 Web 服务器 (如果尚未运行的话), 并使用加载的加载项打开 Excel。

      npm start
      
    • 若要在 Excel 网页版中测试加载项,请在项目的根目录中运行以下命令。 运行此命令时,本地 Web 服务器将启动。 将 "{url}" 替换为你拥有权限的 OneDrive 或 SharePoint 库上 Excel 文档的 URL。

      注意

      如果在 Mac 上进行开发,请将 括 {url} 在单引号中。 请勿在 Windows 上执行此操作。

      npm run start:web -- --document {url}
      

      示例如下。

      • npm run start:web -- --document https://contoso.sharepoint.com/:t:/g/EZGxP7ksiE5DuxvY638G798BpuhwluxCMfF1WZQj3VYhYQ?e=F4QM1R
      • npm run start:web -- --document https://1drv.ms/x/s!jkcH7spkM4EGgcZUgqthk4IK3NOypVw?e=Z6G1qp
      • npm run start:web -- --document https://contoso-my.sharepoint-df.com/:t:/p/user/EQda453DNTpFnl1bFPhOVR0BwlrzetbXvnaRYii2lDr_oQ?e=RSccmNP

      如果外接程序未在文档中旁加载,请按照手动旁加载加载项中的说明手动旁加载到Office web 版

  2. 如果加载项任务窗格尚未在 Excel 中打开,请转到“ 开始 ”选项卡,然后选择功能区上的“ 显示任务窗格 ”按钮将其打开。

  3. 如果之前在此教程中添加的表格未在打开的工作表中出现,选择任务窗格中的“创建表”按钮。

  4. 选择“筛选表”按钮和“排序表”按钮(按顺序和倒序中的任一顺序排序皆可)。

    Excel,其中包含加载项任务窗格中可见的“筛选表”和“排序表”按钮。

创建图表

本教程的这一步是,使用先前创建的表中的数据创建图表,再设置图表格式。

使用表格数据绘制图表

  1. 打开 ./src/taskpane/taskpane.html 文件。

  2. 查找sort-table按钮的<button>元素,并在行后添加下列标记。

    <button class="ms-Button" id="create-chart">Create Chart</button><br/><br/>
    
  3. 打开 ./src/taskpane/taskpane.js 文件。

  4. Office.onReady 函数调用中,定位将单击处理程序分配到 sort-table 按钮的行,并在该行后添加以下代码。

    document.getElementById("create-chart").onclick = () => tryCatch(createChart);
    
  5. 将以下函数添加到文件结尾。

    async function createChart() {
        await Excel.run(async (context) => {
    
            // TODO1: Queue commands to get the range of data to be charted.
    
            // TODO2: Queue command to create the chart and define its type.
    
            // TODO3: Queue commands to position and format the chart.
    
            await context.sync();
        });
    }
    
  6. createChart() 函数中,将 TODO1 替换为以下代码。 请注意,为了排除标题行,此代码使用 Table.getDataBodyRange 方法(而不是 getRange 方法),获取要绘制成图表的数据的范围。

    const currentWorksheet = context.workbook.worksheets.getActiveWorksheet();
    const expensesTable = currentWorksheet.tables.getItem('ExpensesTable');
    const dataRange = expensesTable.getDataBodyRange();
    
  7. createChart() 函数中,将 TODO2 替换为以下代码。 请注意以下参数。

    • add 方法的第一个参数指定图表类型。 有几十种类型。

    • 第二个参数指定要在图表中添加的数据的范围。

    • 第三个参数确定是按行方向还是按列方向绘制表格中的一系列数据点。 选项 auto 指示 Excel 确定最佳方法。

    const chart = currentWorksheet.charts.add('ColumnClustered', dataRange, 'Auto');
    
  8. createChart() 函数中,将 TODO3 替换为以下代码。 此代码的大部分内容非常直观明了。 请注意几下几点:

    • setPosition 方法的参数指定应包含图表的工作表区域的左上角和右下角单元格。 Excel 可以调整行宽等设置,以便图表能够适应所提供的空间。

    • “系列”是指表格列中的一组数据点。 因为表格中只有一个非字符串列,所以 Excel 推断此列就是要绘制成图表的唯一一列数据点。 它将其他列解释为图表标签。 因此,图表中只有一个系列,它的索引为 0。 这是要标记为“金额(欧元)”的系列。

    chart.setPosition("A15", "F30");
    chart.title.text = "Expenses";
    chart.legend.position = "Right";
    chart.legend.format.fill.setSolidColor("white");
    chart.dataLabels.format.font.size = 15;
    chart.dataLabels.format.font.color = "black";
    chart.series.getItemAt(0).name = 'Value in \u20AC';
    
  9. 验证是否已保存了对项目所做的所有更改。

测试加载项

  1. 如果本地 Web 服务器已在运行,并且加载项已加载到 Excel 中,请继续执行步骤 2。 否则,启动本地 Web 服务器并旁加载你的加载项:

    • 若要在 Excel 中测试加载项,请在项目的根目录中运行以下命令。 这将启动本地的 Web 服务器 (如果尚未运行的话), 并使用加载的加载项打开 Excel。

      npm start
      
    • 若要在 Excel 网页版中测试加载项,请在项目的根目录中运行以下命令。 运行此命令时,本地 Web 服务器将启动。 将 "{url}" 替换为你拥有权限的 OneDrive 或 SharePoint 库上 Excel 文档的 URL。

      注意

      如果在 Mac 上进行开发,请将 括 {url} 在单引号中。 请勿在 Windows 上执行此操作。

      npm run start:web -- --document {url}
      

      示例如下。

      • npm run start:web -- --document https://contoso.sharepoint.com/:t:/g/EZGxP7ksiE5DuxvY638G798BpuhwluxCMfF1WZQj3VYhYQ?e=F4QM1R
      • npm run start:web -- --document https://1drv.ms/x/s!jkcH7spkM4EGgcZUgqthk4IK3NOypVw?e=Z6G1qp
      • npm run start:web -- --document https://contoso-my.sharepoint-df.com/:t:/p/user/EQda453DNTpFnl1bFPhOVR0BwlrzetbXvnaRYii2lDr_oQ?e=RSccmNP

      如果外接程序未在文档中旁加载,请按照手动旁加载加载项中的说明手动旁加载到Office web 版

  2. 如果加载项任务窗格尚未在 Excel 中打开,请转到“ 开始 ”选项卡,然后选择功能区上的“ 显示任务窗格 ”按钮将其打开。

  3. 如果之前在此教程中添加的表格未在打开的工作表中出现,选择任务窗格中的“创建表”按钮,随后选择“筛选表”按钮和“排序表”按钮(按顺序和倒序中的任一顺序排序皆可)。

  4. 选择“创建图表”**** 按钮。 此时,图表创建完成,其中仅包含筛选出的行中的数据。 底部数据点上的标签按图表的排序顺序进行排序,即按商家名称的字母倒序排序。

    Excel 具有加载项任务窗格中可见的“创建图表”按钮,工作表中显示杂货和教育费用数据的图表。

冻结表格标题

如果表格很长,导致用户必须滚动才能看到一些行,那么标题行可能会在滚动时不可见。 本教程的这一步是,冻结以前创建的表格的标题行,让它在用户向下滚动工作表时依然可见。

冻结表格的标题行

  1. 打开 ./src/taskpane/taskpane.html 文件。

  2. 查找create-chart按钮的<button>元素,并在行后添加下列标记。

    <button class="ms-Button" id="freeze-header">Freeze Header</button><br/><br/>
    
  3. 打开 ./src/taskpane/taskpane.js 文件。

  4. Office.onReady 函数调用中,定位将单击处理程序分配到 create-chart 按钮的行,并在该行后添加以下代码。

    document.getElementById("freeze-header").onclick = () => tryCatch(freezeHeader);
    
  5. 将以下函数添加到文件结尾。

    async function freezeHeader() {
        await Excel.run(async (context) => {
    
            // TODO1: Queue commands to keep the header visible when the user scrolls.
    
            await context.sync();
        });
    }
    
  6. freezeHeader() 函数中,将 TODO1 替换为以下代码。 注意:

    • Worksheet.freezePanes 集合是工作表中的一组窗格,在工作表滚动时就地固定或冻结。

    • freezeRows 方法需要使用要就地固定的行数(自顶部算起)作为参数。 传递 1 以就地固定第一行。

    const currentWorksheet = context.workbook.worksheets.getActiveWorksheet();
    currentWorksheet.freezePanes.freezeRows(1);
    
  7. 验证是否已保存了对项目所做的所有更改。

测试加载项

  1. 如果本地 Web 服务器已在运行,并且加载项已加载到 Excel 中,请继续执行步骤 2。 否则,启动本地 Web 服务器并旁加载你的加载项:

    • 若要在 Excel 中测试加载项,请在项目的根目录中运行以下命令。 这将启动本地的 Web 服务器 (如果尚未运行的话), 并使用加载的加载项打开 Excel。

      npm start
      
    • 若要在 Excel 网页版中测试加载项,请在项目的根目录中运行以下命令。 运行此命令时,本地 Web 服务器将启动。 将 "{url}" 替换为你拥有权限的 OneDrive 或 SharePoint 库上 Excel 文档的 URL。

      注意

      如果在 Mac 上进行开发,请将 括 {url} 在单引号中。 请勿在 Windows 上执行此操作。

      npm run start:web -- --document {url}
      

      示例如下。

      • npm run start:web -- --document https://contoso.sharepoint.com/:t:/g/EZGxP7ksiE5DuxvY638G798BpuhwluxCMfF1WZQj3VYhYQ?e=F4QM1R
      • npm run start:web -- --document https://1drv.ms/x/s!jkcH7spkM4EGgcZUgqthk4IK3NOypVw?e=Z6G1qp
      • npm run start:web -- --document https://contoso-my.sharepoint-df.com/:t:/p/user/EQda453DNTpFnl1bFPhOVR0BwlrzetbXvnaRYii2lDr_oQ?e=RSccmNP

      如果外接程序未在文档中旁加载,请按照手动旁加载加载项中的说明手动旁加载到Office web 版

  2. 如果加载项任务窗格尚未在 Excel 中打开,请转到“ 开始 ”选项卡,然后选择功能区上的“ 显示任务窗格 ”按钮将其打开。

  3. 如果之前在此教程中添加的表格出现在此工作表中,将其删除。

  4. 在任务窗格中,选择“创建表”按钮。

  5. 在任务窗格中,选择“冻结标题”按钮。

  6. 尽量向下滚动工作表,直到在上面的行不可见时表格标题在顶部依然可见。

    具有冻结表格标题的 Excel 工作表。

保护工作表

在此教程的此步骤中,将向功能区添加一个按钮,以打开和关闭工作表保护。

将清单配置为添加第二个功能区按钮

  1. 打开清单文件 ./manifest.xml

  2. 找到 <Control> 元素。 此元素定义了“主页”功能区上一直用于启动加载项的“显示任务窗格”按钮。 将向“主页”功能区上的相同组添加第二个按钮。 在结束 </Control> 标记和结束 </Group> 标记之间,添加以下标记。

    <Control xsi:type="Button" id="<!--TODO1: Unique (in manifest) name for button -->">
        <Label resid="<!--TODO2: Button label -->" />
        <Supertip>
            <Title resid="<!-- TODO3: Button tool tip title -->" />
            <Description resid="<!-- TODO4: Button tool tip description -->" />
        </Supertip>
        <Icon>
            <bt:Image size="16" resid="Icon.16x16"/>
            <bt:Image size="32" resid="Icon.32x32"/>
            <bt:Image size="80" resid="Icon.80x80"/>
        </Icon>
        <Action xsi:type="<!-- TODO5: Specify the type of action-->">
            <!-- TODO6: Identify the function.-->
        </Action>
    </Control>
    
  3. 在刚添加至清单文件的 XML 内,将 TODO1 替换为字符串,以便向按钮提供在此清单文件内唯一的 ID。 由于按钮将启用和禁用工作表保护,因此请使用“ToggleProtection”。 完成时,Control 元素的开始标记如下所示:

    <Control xsi:type="Button" id="ToggleProtection">
    
  4. 接下来的三个 TODO 设置资源 ID,或 resid。 资源是字符串(最大长度为 32 个字符),这三个字符串将在后续步骤中创建。 现在,需要向资源提供 ID。 虽然按钮标签应名为“切换保护”,但此字符串的 ID 应为“ProtectionButtonLabel”。因此 Label 元素的样式如下:

    <Label resid="ProtectionButtonLabel" />
    
  5. SuperTip 元素定义了按钮的工具提示。 由于工具提示标题应与按钮标签相同,因此使用完全相同的资源 ID,即“ProtectionButtonLabel”。 工具提示说明为“单击即可启用和禁用工作表保护”。 不过,resid 应为“ProtectionButtonToolTip”。 完成后,SuperTip 元素如下所示:

    <Supertip>
        <Title resid="ProtectionButtonLabel" />
        <Description resid="ProtectionButtonToolTip" />
    </Supertip>
    

    注意

    在生产加载项中,不建议对两个不同的按钮使用相同的图标;但为了简单起见,本教程将采用这样的做法。 因此,新 Icon 中的 Control 标记直接就是现有 IconControl 元素的副本。

  6. 虽然原始 Control 元素内的 Action 元素的类型设置为 ShowTaskpane,但新按钮不会要打开任务窗格,而是要运行在后续步骤中创建的自定义函数。 因此,将 TODO5 替换为 ExecuteFunction,即触发自定义函数的按钮的操作类型。 Action 元素的开始标记如下所示:

    <Action xsi:type="ExecuteFunction">
    
  7. 原始 Action 元素的子元素指定任务窗格 ID,以及应当在任务窗格中打开的页面 URL。 不过,Action 类型的 ExecuteFunction 元素只有一个子元素,用于命名控件执行的函数。 此函数(名为 toggleProtection)将在后续步骤中创建。 因此,请将 替换为 TODO6 以下标记。

    <FunctionName>toggleProtection</FunctionName>
    

    此时,整个 Control 标记应如下所示:

    <Control xsi:type="Button" id="ToggleProtection">
        <Label resid="ProtectionButtonLabel" />
        <Supertip>
            <Title resid="ProtectionButtonLabel" />
            <Description resid="ProtectionButtonToolTip" />
        </Supertip>
        <Icon>
            <bt:Image size="16" resid="Icon.16x16"/>
            <bt:Image size="32" resid="Icon.32x32"/>
            <bt:Image size="80" resid="Icon.80x80"/>
        </Icon>
        <Action xsi:type="ExecuteFunction">
           <FunctionName>toggleProtection</FunctionName>
        </Action>
    </Control>
    
  8. 向下滚动到清单的 Resources 部分。

  9. 将下列标记添加为 bt:ShortStrings 元素的子级。

    <bt:String id="ProtectionButtonLabel" DefaultValue="Toggle Worksheet Protection" />
    
  10. 将下列标记添加为 bt:LongStrings 元素的子级。

    <bt:String id="ProtectionButtonToolTip" DefaultValue="Click to protect or unprotect the current worksheet." />
    
  11. 保存文件。

创建工作表保护函数

  1. 打开文件 .\commands\commands.js

  2. 紧接着 action 函数添加下列函数。 注意,我们向函数和函数调用 args.completed 的最后一行指定了 args 参数。 ExecuteFunction 类型的所有加载项命令都必须满足这项要求。 它会指示 Office 客户端应用程序,函数已完成,且 UI 可以再次变得可响应。

    async function toggleProtection(args) {
        try {
            await Excel.run(async (context) => {
    
                // TODO1: Queue commands to reverse the protection status of the current worksheet.
    
                await context.sync();
            });
        } catch (error) {
            // Note: In a production add-in, you'd want to notify the user through your add-in's UI.
            console.error(error);
        }
    
        args.completed();
    }
    
  3. 紧跟在 函数后面添加以下行以将其注册。

    Office.actions.associate("toggleProtection", toggleProtection);
    
  4. toggleProtection 函数中,将 TODO1 替换为以下代码。 此代码使用处于标准切换模式的工作表对象 protection 属性。 TODO2 将在下一部分中进行介绍。

    const sheet = context.workbook.worksheets.getActiveWorksheet();
    
    // TODO2: Queue command to load the sheet's "protection.protected" property from
    //        the document and re-synchronize the document and task pane.
    
    if (sheet.protection.protected) {
        sheet.protection.unprotect();
    } else {
        sheet.protection.protect();
    }
    

添加代码以将文档属性提取到任务窗格的脚本对象

在此教程中创建的各函数内,通过对命令进行排队来写入 Office 文档。 每个函数结束时都会调用 context.sync() 方法,从而将排入队列的命令发送到文档,以供执行。 但是在上一步中添加的代码调用的是 sheet.protection.protected property , 这与之前编写的函数明显不同,因为 sheet 对象只是任务窗格脚本中的代理对象。 代理对象并不了解文档的实际保护状态,因此它的 protection.protected 属性无法有实值。 为避免出现异常错误,必须首先从文档中获取保护状态并使用它来设置值 sheet.protection.protected。 此获取过程分为三步。

  1. 将命令排入队列,以加载(即提取)代码需要读取的属性。

  2. 调用上下文对象的 sync方法,从而向文档发送已排入队列的命令以供执行,并返回请求获取的信息。

  3. 由于 sync 是异步方法,因此请先确保它已完成,然后代码才能调用已提取的属性。

只要代码需要从 Office 文档读取信息,就必须完成这些步骤。

  1. toggleProtection 函数中,将 TODO2 替换为以下代码。 注意:

    • 每个 Excel 对象都有 load 方法。 对于要在参数中读取的对象属性,将它们指定为逗号分隔名称字符串。 在此示例中,需要读取的属性为 protection 属性的子属性。 引用子属性的方法与在代码中的其他任何地方引用属性几乎完全一样,不同之处在于使用的是正斜杠(“/”)字符,而不是“.”字符。

    • 要确保切换逻辑(显示为 sheet.protection.protected)在 sync 完成且已为 sheet.protection.protected 分配从文档中提取的正确值之后才会运行,它必须在 await 运算符确保 sync 完成后运行。

    sheet.load('protection/protected');
    await context.sync();
    

    完成后,整个函数应如下所示:

    async function toggleProtection(args) {
        try {
            await Excel.run(async (context) => {
                const sheet = context.workbook.worksheets.getActiveWorksheet();
    
                sheet.load('protection/protected');
                await context.sync();
    
                if (sheet.protection.protected) {
                    sheet.protection.unprotect();
                } else {
                    sheet.protection.protect();
                }
    
                await context.sync();
            });
        } catch (error) {
            // Note: In a production add-in, you'd want to notify the user through your add-in's UI.
            console.error(error);
        }
    
        args.completed();
    }
    
  2. 验证是否已保存了对项目所做的所有更改。

测试加载项

  1. 关闭所有 Office 应用程序(包括 Excel ()或关闭浏览器选项卡(如果使用Excel web 版) )。

  2. 清除 Office 缓存。 这对于从客户端应用程序中完全清除旧版本的外接程序是必需的。 清除 Office 缓存一文中提供了此过程的说明。

  3. 如果本地 Web 服务器已在运行,请在命令提示符下输入以下命令来停止它。 这应关闭节点命令窗口。

    npm stop
    
  4. 因为清单文件已更新,必须使用已更新的清单文件,重新旁加载加载项。 启动本地 Web 服务器并旁加载你的加载项。

    • 若要在 Excel 中测试加载项,请在项目的根目录中运行以下命令。 这将启动本地的 Web 服务器 (如果尚未运行的话), 并使用加载的加载项打开 Excel。

      npm start
      
    • 若要在 Excel 网页版中测试加载项,请在项目的根目录中运行以下命令。 运行此命令时,本地 Web 服务器将启动。 将 "{url}" 替换为你拥有权限的 OneDrive 或 SharePoint 库上 Excel 文档的 URL。

      注意

      如果在 Mac 上进行开发,请将 括 {url} 在单引号中。 请勿在 Windows 上执行此操作。

      npm run start:web -- --document {url}
      

      示例如下。

      • npm run start:web -- --document https://contoso.sharepoint.com/:t:/g/EZGxP7ksiE5DuxvY638G798BpuhwluxCMfF1WZQj3VYhYQ?e=F4QM1R
      • npm run start:web -- --document https://1drv.ms/x/s!jkcH7spkM4EGgcZUgqthk4IK3NOypVw?e=Z6G1qp
      • npm run start:web -- --document https://contoso-my.sharepoint-df.com/:t:/p/user/EQda453DNTpFnl1bFPhOVR0BwlrzetbXvnaRYii2lDr_oQ?e=RSccmNP

      如果外接程序未在文档中旁加载,请按照手动旁加载加载项中的说明手动旁加载到Office web 版

  5. 在 Excel 的 Home 选项卡上,选择“切换工作表保护”按钮。 请注意,功能区上的大部分控件都处于禁用状态(灰显),如下面的屏幕截图所示。

    突出显示并启用了“切换工作表保护”按钮的 Excel 功能区。其他大多数按钮显示为灰色且处于禁用状态。

  6. 选择单元格并尝试编辑其内容。 Excel 将显示一条错误消息,指示工作表受保护。

  7. 再次选择“切换工作表保护”按钮,此时控件重新启用,可以再次更改单元格值。

打开对话框

本教程的最后一步是,在加载项中打开对话框,将消息从对话框进程传递到任务窗格进程,再关闭对话框。 Office 外接程序对话框是 非模式的:用户可以继续与 Office 应用程序中的文档和任务窗格中的主机页面进行交互。

创建对话框页面

  1. 在项目根目录下的 ./src 文件夹中,新建文件夹“dialogs”。

  2. ./src/dialogs 文件夹中,新建文件“popup.html”。

  3. 将下面的标记添加到 popup.html 中。 注意:

    • 此页面包含可供用户输入用户名的 <input> 字段,并包含将此名称发送到将在其中显示的任务窗格的按钮。

    • 此标记加载在后续步骤中创建的 popup.js 脚本。

    • 此标记还加载 Office.JS 库,因为 popup.js 将使用它们。

    <!DOCTYPE html>
    <html>
        <head lang="en">
            <title>Dialog for My Office Add-in</title>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1">
    
            <!-- For more information on Fluent UI, visit https://developer.microsoft.com/fluentui. -->
            <link rel="stylesheet" href="https://static2.sharepointonline.com/files/fabric/office-ui-fabric-core/9.6.1/css/fabric.min.css"/>
    
            <script type="text/javascript" src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js"></script>
            <script type="text/javascript" src="popup.js"></script>
    
        </head>
        <body style="display:flex;flex-direction:column;align-items:center;justify-content:center">
            <p class="ms-font-xl">ENTER YOUR NAME</p>
            <input id="name-box" type="text"/><br/><br/>
            <button id="ok-button" class="ms-Button">OK</button>
        </body>
    </html>
    
  4. ./src/dialogs 文件夹中,新建文件“popup.js”。

  5. 将下面的代码添加到 popup.js 中。 对于此代码,请注意以下事项。

    • 每个调用 Office.JS 库中的 API 的页面均必须首先确保该库已完成初始化。 执行此操作的最佳方法是调用 Office.onReady() 函数。 必须在调用 Office.js 之前运行 Office.onReady() 调用;因此,作业位于由页面加载的脚本文件中,如同本示例中一样。
    Office.onReady((info) => {
        // TODO1: Assign handler to the OK button.
    });
    
    // TODO2: Create the OK button handler.
    
  6. TODO1 替换为下面的代码。 将在下一步中创建 sendStringToParentPage 函数。

    document.getElementById("ok-button").onclick = () => tryCatch(sendStringToParentPage);
    
  7. TODO2 替换为以下代码。 messageParent 方法将它的参数传递到父页面(在此示例中,为任务窗格中的页面)。 参数必须是字符串,其中包括任何可以序列化为字符串的内容(例如 XML 或 JSON),或者任何可以转换为字符串的类型。 这还会添加 taskpane.js 中使用的相同tryCatch方法,用于错误处理。

    function sendStringToParentPage() {
        const userName = document.getElementById("name-box").value;
        Office.context.ui.messageParent(userName);
    }
    
    /** Default helper for invoking an action and handling errors. */
    async function tryCatch(callback) {
        try {
            await callback();
        } catch (error) {
            // Note: In a production add-in, you'd want to notify the user through your add-in's UI.
            console.error(error);
        }
    }
    

注意

popup.html 文件及其加载的 popup.js 文件在完全独立于加载项任务窗格的浏览器进程中运行。 如果将 popup.js 转换为与 app.js 文件相同的 bundle.js 文件,加载项必须加载 bundle.js 文件的两个副本,这就违背了绑定目的。 所以此加载项根本不会转换 popup.js

更新 webpack 配置设置

打开项目根目录中的 webpack.config.js 文件,并完成以下步骤。

  1. config 对象内找到 entry 对象并为 popup 添加新条目。

    popup: "./src/dialogs/popup.js"
    

    完成此操作之后,新的 entry 对象将与此类似。

    entry: {
      polyfill: "@babel/polyfill",
      taskpane: "./src/taskpane/taskpane.js",
      commands: "./src/commands/commands.js",
      popup: "./src/dialogs/popup.js"
    },
    
  2. config 对象中找到 plugins 数组,并添加下列对象至数组的结尾。

    new HtmlWebpackPlugin({
      filename: "popup.html",
      template: "./src/dialogs/popup.html",
      chunks: ["polyfill", "popup"]
    })
    

    完成此操作之后,新的 plugins 数组将与此类似。

    plugins: [
      new CleanWebpackPlugin(),
      new HtmlWebpackPlugin({
        filename: "taskpane.html",
        template: "./src/taskpane/taskpane.html",
        chunks: ['polyfill', 'taskpane']
      }),
      new CopyWebpackPlugin([
      {
        to: "taskpane.css",
        from: "./src/taskpane/taskpane.css"
      }
      ]),
      new HtmlWebpackPlugin({
        filename: "commands.html",
        template: "./src/commands/commands.html",
        chunks: ["polyfill", "commands"]
      }),
      new HtmlWebpackPlugin({
        filename: "popup.html",
        template: "./src/dialogs/popup.html",
        chunks: ["polyfill", "popup"]
      })
    ],
    
  3. 如果本地 Web 服务器正在运行,请在命令提示符下输入以下命令来停止它。 这应关闭节点命令窗口。

    npm stop
    
  4. 运行以下命令以重建项目。

    npm run build
    

从任务窗格打开对话框

  1. 打开 ./src/taskpane/taskpane.html 文件。

  2. 查找freeze-header按钮的<button>元素,并在行后添加下列标记。

    <button class="ms-Button" id="open-dialog">Open Dialog</button><br/><br/>
    
  3. 对话框会提示用户输入用户名,并将用户名传递到任务窗格。 任务窗格将在标签中显示用户名。 紧接着刚添加的 button,添加下列标记。

    <label id="user-name"></label><br/><br/>
    
  4. 打开 ./src/taskpane/taskpane.js 文件。

  5. Office.onReady 函数调用中,定位将单击处理程序分配到 freeze-header 按钮的行,并在该行后添加以下代码。 将在后续步骤中创建 openDialog 方法。

    document.getElementById("open-dialog").onclick = openDialog;
    
  6. 添加下列声明至文件结尾。 此变量用于保留父页面执行上下文中的对象,以用作对话框页面执行上下文的中间对象。

    let dialog = null;
    
  7. 添加下列函数至文件结尾(dialog 声明后)。 关于此代码,请务必注意它包含的内容,即不含 Excel.run 调用。 这是因为用于打开对话框的 API 跨所有 Office 应用程序共享,所以它属于 Office JavaScript 通用 API,而非 Excel 专用 API。

    function openDialog() {
        // TODO1: Call the Office Common API that opens a dialog.
    }
    
  8. TODO1 替换为下面的代码。 注意:

    • displayDialogAsync 方法在屏幕中央打开对话框。

    • 第一个参数是要打开的页面 URL。

    • 第二个参数用于传递选项。 heightwidth 是 Office 应用程序窗口大小的百分比。

    Office.context.ui.displayDialogAsync(
        'https://localhost:3000/popup.html',
        {height: 45, width: 55},
    
        // TODO2: Add callback parameter.
    );
    

处理对话框发送的消息并关闭对话框

  1. ./src/taskpane/taskpane.js 文件的 openDialog 函数内,将 TODO2 替换为下列代码。 注意:

    • 回调在对话框成功打开后和用户在对话框中执行任何操作前立即执行。

    • result.value 对象用作父页面执行上下文和对话框页面执行上下文的中介。

    • processMessage 函数将在后续步骤中创建。 此处理程序将处理通过 messageParent 函数调用从对话框页面发送的任何值。

    function (result) {
        dialog = result.value;
        dialog.addEventHandler(Office.EventType.DialogMessageReceived, processMessage);
    }
    
  2. openDialog 函数后面添加以下函数。

    function processMessage(arg) {
        document.getElementById("user-name").innerHTML = arg.message;
        dialog.close();
    }
    
  3. 验证是否已保存了对项目所做的所有更改。

测试加载项

  1. 如果本地 Web 服务器已在运行,并且加载项已加载到 Excel 中,请继续执行步骤 2。 否则,启动本地 Web 服务器并旁加载你的加载项:

    • 若要在 Excel 中测试加载项,请在项目的根目录中运行以下命令。 这将启动本地的 Web 服务器 (如果尚未运行的话), 并使用加载的加载项打开 Excel。

      npm start
      
    • 若要在 Excel 网页版中测试加载项,请在项目的根目录中运行以下命令。 运行此命令时,本地 Web 服务器将启动。 将 "{url}" 替换为你拥有权限的 OneDrive 或 SharePoint 库上 Excel 文档的 URL。

      注意

      如果在 Mac 上进行开发,请将 括 {url} 在单引号中。 请勿在 Windows 上执行此操作。

      npm run start:web -- --document {url}
      

      示例如下。

      • npm run start:web -- --document https://contoso.sharepoint.com/:t:/g/EZGxP7ksiE5DuxvY638G798BpuhwluxCMfF1WZQj3VYhYQ?e=F4QM1R
      • npm run start:web -- --document https://1drv.ms/x/s!jkcH7spkM4EGgcZUgqthk4IK3NOypVw?e=Z6G1qp
      • npm run start:web -- --document https://contoso-my.sharepoint-df.com/:t:/p/user/EQda453DNTpFnl1bFPhOVR0BwlrzetbXvnaRYii2lDr_oQ?e=RSccmNP

      如果外接程序未在文档中旁加载,请按照手动旁加载加载项中的说明手动旁加载到Office web 版

  2. 如果加载项任务窗格尚未在 Excel 中打开,请转到“ 开始 ”选项卡,然后选择功能区上的“ 显示任务窗格 ”按钮将其打开。

  3. 选择任务窗格中的“打开对话框”按钮。

  4. 对话框打开后,拖动它并重设大小。 请注意,你可以与工作表进行交互并按任务窗格上的其他按钮,但无法从同一任务窗格页面启动第二个对话框。

  5. 在对话框中,输入名称并选择“确定”。 此时,用户名显示在任务窗格上,且对话框关闭。

  6. (可选)在 ./src/taskpane/taskpane.js 文件中,注释掉 函数中的processMessagedialog.close();。 然后,重复执行此部分的步骤。 这样一来,对话框便会继续处于打开状态,可供用户更改用户名。 按右上角的“X”按钮,可手动关闭对话框。

    Excel 在加载项任务窗格中显示“打开对话框”按钮,并在工作表上显示一个对话框。

后续步骤

在本教程中,你已创建与 Excel 工作簿中的表格、图表、工作表和对话框进行交互的 Excel 任务窗格加载项。 若要了解有关构建 Excel 加载项的详细信息,请继续阅读以下文章。

代码示例

另请参阅