教程:创建 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
完成此向导后,生成器会创建项目,并安装支持的 Node 组件。 如果在初始设置过程中出现问题,可能需要在项目的根文件夹中手动运行 npm install
。
创建表
本教程的这一步是,以编程方式测试加载项是否支持用户的当前版本 Excel,向工作表中添加表格,使用数据填充表格,并设置格式。
编码加载项
在代码编辑器中打开项目。
打开 ./src/taskpane/taskpane.html 文件。 此文件含有任务窗格的 HTML 标记。
找到
<main>
元素并删除在开始<main>
标记后和关闭</main>
标记前出现的所有行。打开
<main>
标记后立即添加下列标记:<button class="ms-Button" id="create-table">Create Table</button><br/><br/>
打开 ./src/taskpane/taskpane.js 文件。 此文件包含用于加快任务窗格与 Office 客户端应用程序之间的交互的 Office JavaScript API 代码。
执行以下操作,删除对
run
按钮和run()
函数的所有引用:查找并删除行
document.getElementById("run").onclick = run;
。查找并删除整个
run()
函数。
在
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);
- 此代码为
将以下函数添加到文件末尾。 注意:
Excel.js 业务逻辑将添加到传递给
Excel.run
的函数。 此逻辑不立即执行。 相反,它会被添加到挂起的命令队列中。context.sync
方法将所有已排入队列的命令发送到 Excel 以供执行。通过
tryCatch
任务窗格与工作簿交互的所有函数都将使用该函数。 以这种方式捕获 Office JavaScript 错误是一种通用处理任何未捕获的错误的便捷方法。
注意
以下代码使用 ES6 JavaScript,与 旧版 Office 不兼容,后者使用 Trident (Internet Explorer 11) 浏览器引擎。 有关如何在生产环境中支持这些平台的信息,请参阅 支持较旧的Microsoft Web 视图和 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); } }
在
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";
在
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"] ]);
在
createTable()
函数中,将TODO3
替换为以下代码。 注意:此代码将从零开始编制的索引传递给表格的列集合的
getItemAt
方法,以获取对“金额”列的引用。注意
Excel.js 集合对象(如
TableCollection
、WorksheetCollection
和TableColumnCollection
)有items
属性,此属性是子对象类型的数组(如Table
、Worksheet
或TableColumn
),但*Collection
对象本身并不是数组。然后,此代码将“金额”列的范围格式化为欧元(精确到小数点后两位)。 在数字格式代码一文中详细了解 Excel 数字格式语法/
最后,它确保了列宽和行高足以容纳最长(或最高)的数据项。 请注意,此代码必须获取要格式化的
Range
对象。TableColumn
和TableRow
对象没有格式属性。
expensesTable.columns.getItemAt(3).getRange().numberFormat = [['\u20AC#,##0.00']]; expensesTable.getRange().format.autofitColumns(); expensesTable.getRange().format.autofitRows();
验证是否已保存了对项目所做的所有更改。
测试加载项
完成以下步骤,以启动本地 Web 服务器并旁加载你的加载项。
注意
即使在开发过程中,Office 外接程序也应使用 HTTPS,而不是 HTTP。 如果在运行以下命令之一后系统提示安装证书,请接受安装 Yeoman 生成器提供的证书的提示。 你可能还必须以管理员身份运行命令提示符或终端才能进行更改。
如果这是你第一次在计算机上开发 Office 加载项,则命令行中可能会提示你授予Microsoft Edge WebView 环回豁免 (“允许 Microsoft Edge WebView 的 localhost 环回?”) 。 出现提示时,输入
Y
以允许豁免。 请注意,需要管理员权限才能允许豁免。 一旦允许,在将来 (旁加载 Office 加载项时,系统就不会提示你获得豁免,除非从计算机) 中删除该豁免。 若要了解详细信息,请参阅加载 Office 外接程序或使用 Fiddler 时,“我们无法从 localhost 打开此外接程序”。
提示
如果在 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 版。
在 Excel 中,选择“ 开始 ”选项卡,然后选择功能区上的“ 显示任务窗格 ”按钮以打开加载项任务窗格。
在任务窗格中,选择“创建表”按钮。
如果要停止本地 Web 服务器并卸载加载项,请按照适用的说明操作:
若要停止服务器,请运行以下命令。 如果使用
npm start
,则以下命令也会卸载加载项。npm stop
如果手动旁加载加载项,请参阅 删除旁加载加载项。
筛选和排序表格
本教程的这一步是,筛选并排序之前创建的表。
筛选表格
打开 ./src/taskpane/taskpane.html 文件。
查找
create-table
按钮的<button>
元素,并在行后添加下列标记。<button class="ms-Button" id="filter-table">Filter Table</button><br/><br/>
打开 ./src/taskpane/taskpane.js 文件。
在
Office.onReady
函数调用中,定位将单击处理程序分配到create-table
按钮的行,并在该行后添加以下代码。document.getElementById("filter-table").onclick = () => tryCatch(filterTable);
将以下函数添加到文件结尾。
async function filterTable() { await Excel.run(async (context) => { // TODO1: Queue commands to filter out all expense categories except // Groceries and Education. await context.sync(); }); }
在
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']);
排序表格
打开 ./src/taskpane/taskpane.html 文件。
查找
filter-table
按钮的<button>
元素,并在行后添加下列标记。<button class="ms-Button" id="sort-table">Sort Table</button><br/><br/>
打开 ./src/taskpane/taskpane.js 文件。
在
Office.onReady
函数调用中,定位将单击处理程序分配到filter-table
按钮的行,并在该行后添加以下代码。document.getElementById("sort-table").onclick = () => tryCatch(sortTable);
将以下函数添加到文件结尾。
async function sortTable() { await Excel.run(async (context) => { // TODO1: Queue commands to sort the table by Merchant name. await context.sync(); }); }
在
sortTable()
函数中,将TODO1
替换为以下代码。 注意:此代码创建一组
SortField
对象,其中只有一个成员,因为加载项只对“商家”列进行了排序。SortField
对象的key
属性是用于排序的列的从零开始编制索引。 表中的行按照所引用列中的值进行排序。Table
的sort
成员是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);
验证是否已保存了对项目所做的所有更改。
测试加载项
如果本地 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 版。
如果加载项任务窗格尚未在 Excel 中打开,请转到“ 开始 ”选项卡,然后选择功能区上的“ 显示任务窗格 ”按钮将其打开。
如果之前在此教程中添加的表格未在打开的工作表中出现,选择任务窗格中的“创建表”按钮。
选择“筛选表”按钮和“排序表”按钮(按顺序和倒序中的任一顺序排序皆可)。
创建图表
本教程的这一步是,使用先前创建的表中的数据创建图表,再设置图表格式。
使用表格数据绘制图表
打开 ./src/taskpane/taskpane.html 文件。
查找
sort-table
按钮的<button>
元素,并在行后添加下列标记。<button class="ms-Button" id="create-chart">Create Chart</button><br/><br/>
打开 ./src/taskpane/taskpane.js 文件。
在
Office.onReady
函数调用中,定位将单击处理程序分配到sort-table
按钮的行,并在该行后添加以下代码。document.getElementById("create-chart").onclick = () => tryCatch(createChart);
将以下函数添加到文件结尾。
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(); }); }
在
createChart()
函数中,将TODO1
替换为以下代码。 请注意,为了排除标题行,此代码使用Table.getDataBodyRange
方法(而不是getRange
方法),获取要绘制成图表的数据的范围。const currentWorksheet = context.workbook.worksheets.getActiveWorksheet(); const expensesTable = currentWorksheet.tables.getItem('ExpensesTable'); const dataRange = expensesTable.getDataBodyRange();
在
createChart()
函数中,将TODO2
替换为以下代码。 请注意以下参数。add
方法的第一个参数指定图表类型。 有几十种类型。第二个参数指定要在图表中添加的数据的范围。
第三个参数确定是按行方向还是按列方向绘制表格中的一系列数据点。 选项
auto
指示 Excel 确定最佳方法。
const chart = currentWorksheet.charts.add('ColumnClustered', dataRange, 'Auto');
在
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';
验证是否已保存了对项目所做的所有更改。
测试加载项
如果本地 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 版。
如果加载项任务窗格尚未在 Excel 中打开,请转到“ 开始 ”选项卡,然后选择功能区上的“ 显示任务窗格 ”按钮将其打开。
如果之前在此教程中添加的表格未在打开的工作表中出现,选择任务窗格中的“创建表”按钮,随后选择“筛选表”按钮和“排序表”按钮(按顺序和倒序中的任一顺序排序皆可)。
选择“创建图表”**** 按钮。 此时,图表创建完成,其中仅包含筛选出的行中的数据。 底部数据点上的标签按图表的排序顺序进行排序,即按商家名称的字母倒序排序。
冻结表格标题
如果表格很长,导致用户必须滚动才能看到一些行,那么标题行可能会在滚动时不可见。 本教程的这一步是,冻结以前创建的表格的标题行,让它在用户向下滚动工作表时依然可见。
冻结表格的标题行
打开 ./src/taskpane/taskpane.html 文件。
查找
create-chart
按钮的<button>
元素,并在行后添加下列标记。<button class="ms-Button" id="freeze-header">Freeze Header</button><br/><br/>
打开 ./src/taskpane/taskpane.js 文件。
在
Office.onReady
函数调用中,定位将单击处理程序分配到create-chart
按钮的行,并在该行后添加以下代码。document.getElementById("freeze-header").onclick = () => tryCatch(freezeHeader);
将以下函数添加到文件结尾。
async function freezeHeader() { await Excel.run(async (context) => { // TODO1: Queue commands to keep the header visible when the user scrolls. await context.sync(); }); }
在
freezeHeader()
函数中,将TODO1
替换为以下代码。 注意:Worksheet.freezePanes
集合是工作表中的一组窗格,在工作表滚动时就地固定或冻结。freezeRows
方法需要使用要就地固定的行数(自顶部算起)作为参数。 传递1
以就地固定第一行。
const currentWorksheet = context.workbook.worksheets.getActiveWorksheet(); currentWorksheet.freezePanes.freezeRows(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 版。
如果加载项任务窗格尚未在 Excel 中打开,请转到“ 开始 ”选项卡,然后选择功能区上的“ 显示任务窗格 ”按钮将其打开。
如果之前在此教程中添加的表格出现在此工作表中,将其删除。
在任务窗格中,选择“创建表”按钮。
在任务窗格中,选择“冻结标题”按钮。
尽量向下滚动工作表,直到在上面的行不可见时表格标题在顶部依然可见。
保护工作表
在此教程的此步骤中,将向功能区添加一个按钮,以打开和关闭工作表保护。
将清单配置为添加第二个功能区按钮
打开清单文件 ./manifest.xml。
找到 <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>
在刚添加至清单文件的 XML 内,将
TODO1
替换为字符串,以便向按钮提供在此清单文件内唯一的 ID。 由于按钮将启用和禁用工作表保护,因此请使用“ToggleProtection”。 完成时,Control
元素的开始标记如下所示:<Control xsi:type="Button" id="ToggleProtection">
接下来的三个
TODO
设置资源 ID,或resid
。 资源是字符串(最大长度为 32 个字符),这三个字符串将在后续步骤中创建。 现在,需要向资源提供 ID。 虽然按钮标签应名为“切换保护”,但此字符串的 ID 应为“ProtectionButtonLabel”。因此Label
元素的样式如下:<Label resid="ProtectionButtonLabel" />
SuperTip
元素定义了按钮的工具提示。 由于工具提示标题应与按钮标签相同,因此使用完全相同的资源 ID,即“ProtectionButtonLabel”。 工具提示说明为“单击即可启用和禁用工作表保护”。 不过,resid
应为“ProtectionButtonToolTip”。 完成后,SuperTip
元素如下所示:<Supertip> <Title resid="ProtectionButtonLabel" /> <Description resid="ProtectionButtonToolTip" /> </Supertip>
注意
在生产加载项中,不建议对两个不同的按钮使用相同的图标;但为了简单起见,本教程将采用这样的做法。 因此,新
Icon
中的Control
标记直接就是现有Icon
中Control
元素的副本。虽然原始
Control
元素内的Action
元素的类型设置为ShowTaskpane
,但新按钮不会要打开任务窗格,而是要运行在后续步骤中创建的自定义函数。 因此,将TODO5
替换为ExecuteFunction
,即触发自定义函数的按钮的操作类型。Action
元素的开始标记如下所示:<Action xsi:type="ExecuteFunction">
原始
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>
向下滚动到清单的
Resources
部分。将下列标记添加为
bt:ShortStrings
元素的子级。<bt:String id="ProtectionButtonLabel" DefaultValue="Toggle Worksheet Protection" />
将下列标记添加为
bt:LongStrings
元素的子级。<bt:String id="ProtectionButtonToolTip" DefaultValue="Click to protect or unprotect the current worksheet." />
保存文件。
创建工作表保护函数
打开文件 .\commands\commands.js。
紧接着
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(); }
紧跟在 函数后面添加以下行以将其注册。
Office.actions.associate("toggleProtection", toggleProtection);
在
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
。 此获取过程分为三步。
将命令排入队列,以加载(即提取)代码需要读取的属性。
调用上下文对象的
sync
方法,从而向文档发送已排入队列的命令以供执行,并返回请求获取的信息。由于
sync
是异步方法,因此请先确保它已完成,然后代码才能调用已提取的属性。
只要代码需要从 Office 文档读取信息,就必须完成这些步骤。
在
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(); }
验证是否已保存了对项目所做的所有更改。
测试加载项
关闭所有 Office 应用程序(包括 Excel ()或关闭浏览器选项卡(如果使用Excel web 版) )。
清除 Office 缓存。 这对于从客户端应用程序中完全清除旧版本的外接程序是必需的。 清除 Office 缓存一文中提供了此过程的说明。
如果本地 Web 服务器已在运行,请在命令提示符下输入以下命令来停止它。 这应关闭节点命令窗口。
npm stop
因为清单文件已更新,必须使用已更新的清单文件,重新旁加载加载项。 启动本地 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 版。
在 Excel 的 Home 选项卡上,选择“切换工作表保护”按钮。 请注意,功能区上的大部分控件都处于禁用状态(灰显),如下面的屏幕截图所示。
选择单元格并尝试编辑其内容。 Excel 将显示一条错误消息,指示工作表受保护。
再次选择“切换工作表保护”按钮,此时控件重新启用,可以再次更改单元格值。
打开对话框
本教程的最后一步是,在加载项中打开对话框,将消息从对话框进程传递到任务窗格进程,再关闭对话框。 Office 外接程序对话框是 非模式的:用户可以继续与 Office 应用程序中的文档和任务窗格中的主机页面进行交互。
创建对话框页面
在项目根目录下的 ./src 文件夹中,新建文件夹“dialogs”。
在 ./src/dialogs 文件夹中,新建文件“popup.html”。
将下面的标记添加到 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://res-1.cdn.office.net/files/fabric-cdn-prod_20230815.002/office-ui-fabric-core/11.0.0/css/fabric.min.css"/> <script type="text/javascript" src="https://appsforoffice.microsoft.com/lib/1.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>
在 ./src/dialogs 文件夹中,新建文件“popup.js”。
将下面的代码添加到 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.
- 每个调用 Office.JS 库中的 API 的页面均必须首先确保该库已完成初始化。 执行此操作的最佳方法是调用
将
TODO1
替换为下面的代码。 将在下一步中创建sendStringToParentPage
函数。document.getElementById("ok-button").onclick = () => tryCatch(sendStringToParentPage);
将
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 文件,并完成以下步骤。
在
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" },
在
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"] }) ],
如果本地 Web 服务器正在运行,请在命令提示符下输入以下命令来停止它。 这应关闭节点命令窗口。
npm stop
运行以下命令以重建项目。
npm run build
从任务窗格打开对话框
打开 ./src/taskpane/taskpane.html 文件。
查找
freeze-header
按钮的<button>
元素,并在行后添加下列标记。<button class="ms-Button" id="open-dialog">Open Dialog</button><br/><br/>
对话框会提示用户输入用户名,并将用户名传递到任务窗格。 任务窗格将在标签中显示用户名。 紧接着刚添加的
button
,添加下列标记。<label id="user-name"></label><br/><br/>
打开 ./src/taskpane/taskpane.js 文件。
在
Office.onReady
函数调用中,定位将单击处理程序分配到freeze-header
按钮的行,并在该行后添加以下代码。 将在后续步骤中创建openDialog
方法。document.getElementById("open-dialog").onclick = openDialog;
添加下列声明至文件结尾。 此变量用于保留父页面执行上下文中的对象,以用作对话框页面执行上下文的中间对象。
let dialog = null;
添加下列函数至文件结尾(
dialog
声明后)。 关于此代码,请务必注意它不包含的内容,即不含Excel.run
调用。 这是因为用于打开对话框的 API 跨所有 Office 应用程序共享,所以它属于 Office JavaScript 通用 API,而非 Excel 专用 API。function openDialog() { // TODO1: Call the Office Common API that opens a dialog. }
将
TODO1
替换为下面的代码。 注意:displayDialogAsync
方法在屏幕中央打开对话框。第一个参数是要打开的页面 URL。
第二个参数用于传递选项。
height
和width
是 Office 应用程序窗口大小的百分比。
Office.context.ui.displayDialogAsync( 'https://localhost:3000/popup.html', {height: 45, width: 55}, // TODO2: Add callback parameter. );
处理对话框发送的消息并关闭对话框
在 ./src/taskpane/taskpane.js 文件的
openDialog
函数内,将TODO2
替换为下列代码。 注意:回调在对话框成功打开后和用户在对话框中执行任何操作前立即执行。
result.value
对象用作父页面执行上下文和对话框页面执行上下文的中介。processMessage
函数将在后续步骤中创建。 此处理程序将处理通过messageParent
函数调用从对话框页面发送的任何值。
function (result) { dialog = result.value; dialog.addEventHandler(Office.EventType.DialogMessageReceived, processMessage); }
在
openDialog
函数后面添加以下函数。function processMessage(arg) { document.getElementById("user-name").innerHTML = arg.message; dialog.close(); }
验证是否已保存了对项目所做的所有更改。
测试加载项
如果本地 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 版。
如果加载项任务窗格尚未在 Excel 中打开,请转到“ 开始 ”选项卡,然后选择功能区上的“ 显示任务窗格 ”按钮将其打开。
选择任务窗格中的“打开对话框”按钮。
对话框打开后,拖动它并重设大小。 请注意,你可以与工作表进行交互并按任务窗格上的其他按钮,但无法从同一任务窗格页面启动第二个对话框。
在对话框中,输入名称并选择“确定”。 此时,用户名显示在任务窗格上,且对话框关闭。
(可选)在 ./src/taskpane/taskpane.js 文件中,注释掉 函数中的
processMessage
行dialog.close();
。 然后,重复执行此部分的步骤。 这样一来,对话框便会继续处于打开状态,可供用户更改用户名。 按右上角的“X”按钮,可手动关闭对话框。
后续步骤
在本教程中,你已创建与 Excel 工作簿中的表格、图表、工作表和对话框进行交互的 Excel 任务窗格加载项。 若要了解有关构建 Excel 加载项的详细信息,请继续阅读以下文章。
代码示例
- 已完成的 Excel 加载项教程:完成本教程的结果。