本文介绍如何使用 API 模型在 Excel、OneNote、PowerPoint、Visio 和 Word 中生成外接程序。 本文介绍核心概念,这些概念是使用基于承诺的 API 的基础。
注意
Outlook 和 Project 客户端不支持此模型。 使用 通用 API 模型 来处理这些应用程序。 有关完整的平台可用性说明,请参阅 Office 客户端应用程序和平台可用性的 Office 加载项组。
提示
本文中的示例使用 Excel JavaScript API,但这些概念也适用于 OneNote、PowerPoint、Visio 和 Word JavaScript API。 有关演示如何在各种 Office 应用程序中使用这些概念和其他概念的完整代码示例,请参阅 Office 外接程序代码示例。
基于承诺的 API 的异步性质
Office 加载项是显示在 Office 应用程序(如 Excel)中的 Web 视图控件内的网站。 此控件嵌入在基于桌面的平台(例如 Windows 上的 Office)上的 Office 应用程序中,并在 Office web 版 中的 HTML iframe 中运行。 出于性能考虑,Office.js API 无法跨所有平台与 Office 应用程序同步交互。 因此,Office.js sync() API 调用返回 Office 请求的读取或写入操作时要解决的"承诺"问题。 此外,还可以将多个操作(例如设置属性或调用方法)排队,并且只要一次呼叫 sync(),就可以作为一批命令运行这些操作,而不是针对每个操作发送单独的请求。 以下各节介绍如何使用 API 和 run()sync()实现此操作。
*.run 函数
Excel.run、 OneNote.run、 PowerPoint.run和 Word.run 执行一个函数,该函数指定要对 Excel、Word 和 OneNote 执行的操作。
*.run 会自动创建可用于与 Office 对象交互的请求上下文。 完成后 *.run ,它会解析承诺,并自动释放在运行时分配的任何对象。
以下示例显示了如何使用 Excel.run。 同一模式也用于 OneNote、PowerPoint、Visio 和 Word。
Excel.run(function (context) {
// Add your Excel JS API calls here that will be batched and sent to the workbook.
console.log('Your code goes here.');
}).catch(function (error) {
// Catch and log any errors that occur within `Excel.run`.
console.log('error: ' + error);
if (error instanceof OfficeExtension.Error) {
console.log('Debug info: ' + JSON.stringify(error.debugInfo));
}
});
请求上下文
Office 应用程序和加载项在不同的进程中运行。 由于它们使用不同的运行时环境,因此外接程序需要一个 RequestContext 对象来连接到 Office 对象,例如工作表、区域、段落和表。 调用 *.run时,将此对象作为参数提供RequestContext。
代理对象
声明并用于基于承诺的 API 的 Office JavaScript 对象是代理对象。 调用的任何方法或在代理对象上设置或加载的属性都只是添加到挂起命令的队列中。 调用请求 sync() (例如 context.sync())上的方法时,排队的命令将调用 Office 应用程序并运行。 这些 API 在根本上以批处理为中心。 您可以根据对请求上下文希望排入多达数个更改,然后调用 sync() 方法,以运行排队命令的批处理。
例如,以下代码片段声明本地 JavaScript Excel.Range 对象 selectedRange引用 Excel 工作簿中的选定区域,然后针对该对象设置一些属性。 对象 selectedRange 代理对象,因此在调用加载项之前,不会在 Excel 文档中反映已设置的属性和在该对象上调用 context.sync()。
const selectedRange = context.workbook.getSelectedRange();
selectedRange.format.fill.color = "#4472C4";
selectedRange.format.font.color = "white";
selectedRange.format.autofitColumns();
性能提示:最小化创建代理对象的数量
避免重复创建同一个代理对象。 如果多个操作需要同一个代理对象,则改为创建一次并将其分配给一个变量,然后在代码中使用该变量。
// BAD: Repeated calls to .getRange() to create the same proxy object.
worksheet.getRange("A1").format.fill.color = "red";
worksheet.getRange("A1").numberFormat = "0.00%";
worksheet.getRange("A1").values = [[1]];
// GOOD: Create the range proxy object once and assign to a variable.
const range = worksheet.getRange("A1");
range.format.fill.color = "red";
range.numberFormat = "0.00%";
range.values = [[1]];
// ALSO GOOD: Use a "set" method to immediately set all the properties
// without even needing to create a variable!
worksheet.getRange("A1").set({
numberFormat: [["0.00%"]],
values: [[1]],
format: {
fill: {
color: "red"
}
}
});
sync()
调用 sync() 上下文的方法可同步 Office 文档中代理对象和对象之间的状态。 该 sync() 在请求上下文中排入队列的任何命令,并检索应在代理对象上加载的任何属性的值。 方法 sync() 异步执行,并返回 承诺,该 sync() 完成时完成。
下面的示例显示一个批处理函数,定义本地 JavaScript 代理对象 (selectedRange),加载该对象的属性,然后使用 JavaScript 形式调用 context.sync() 以在 Excel 文档中的代理对象和对象间同步状态。
await Excel.run(async (context) => {
const selectedRange = context.workbook.getSelectedRange();
selectedRange.load('address');
await context.sync();
console.log('The selected range is: ' + selectedRange.address);
});
在上一示例中,已设置 selectedRange,并且将在调用 context.sync() 时加载其 address 属性。
由于 sync() 是异步操作,因此始终返回 Promise 对象以确保 sync() 操作在脚本继续运行之前完成。 如果使用 TypeScript 或 ES6+ JavaScript, await 调用 context.sync() ,而不是返回承诺。
性能提示:减少同步呼叫数
在 Excel JavaScript API 中,sync() 是唯一的异步操作,在某些情况下可能会很慢,尤其是对于 Excel 网页版。 若要优化性能,在调用之前,通过尽可能多地将更改加入队列来最大程度减少调用 sync() 的次数。 有关使用 sync()优化性能的详细信息,请参阅 避免在循环中使用 context.sync 方法。
load()
必须显式加载属性才能读取代理对象的属性,才能使用 Office 文档中的数据填充代理对象,然后调用 context.sync()。 例如,如果创建代理对象来引用选定的区域,然后希望读取所选区域的 address 属性,需要首先加载 address 属性,然后才可以读取它。 若要请求加载代理对象的属性,调用 load() 的方法并指定要加载的属性。 以下示例显示了为 Range.address 加载的 myRange。
await Excel.run(async (context) => {
const sheetName = 'Sheet1';
const rangeAddress = 'A1:B2';
const myRange = context.workbook.worksheets.getItem(sheetName).getRange(rangeAddress);
myRange.load('address');
await context.sync();
console.log (myRange.address); // ok
//console.log (myRange.values); // not ok as it was not loaded
console.log('done');
});
注意
如果仅对代理对象调用方法或设置属性,则无需调用 load() 方法。 只有在想要读取代理对象上的属性时 load() 代理方法才必需。
与对代理对象设置属性或调用方法的请求一样,对代理对象加载属性的请求将添加到请求上下文的挂起命令队列中,该队列将在下次调用 sync() 方法时运行。 必要时,可以将请求上下文中尽可能多的 load() 调用排入队列。
标量和导航属性
属性分为两种类别:标量和导航。 标量属性是可分配的类型,如字符串、整数和 JSON 结构。 导航属性是分配了字段的只读对象和对象集合,而不是直接分配属性。 例如,Worksheet 对象上的 name和 position 成员是标量属性,而 protection 和 tables 是导航属性。
加载项可使用导航属性作为加载特定标量属性的路径。 以下代码会按照 load 对象使用的字体的名称将向上排队 Excel.Range 命令,而无需加载任何其他信息。
someRange.load("format/font/name")
还可通过遍历路径来设置导航属性的标量属性。 例如,通过使用"另一种" Excel.Range , someRange.format.font.size = 10;。 设置属性前无需加载属性。
对象下的某些属性可能具有与另一个对象相同的名称。 例如, format 是对象下 Excel.Range 属性, format 值本身也是一个对象。 因此,如果进行诸如 的 range.load("format")调用,此调用等效于 range.format.load()) (一个不需要的空 load() 语句。 若要避免此问题,代码应仅加载对象树中的“叶节点”。
从集合加载
使用集合时,请在 load 集合上使用 以加载集合中每个对象的属性。 完全按照该集合中的单个对象的方式使用 load 。
以下示例代码显示 name 正在加载并记录“示例”工作表中每个图表的属性。
await Excel.run(async (context) => {
const sheet = context.workbook.worksheets.getItem("Sample");
const chartCollection = sheet.charts;
// Load the name property on every chart in the chart collection.
chartCollection.load("name");
await context.sync();
chartCollection.items.forEach((chart) => {
console.log(chart.name);
});
});
通常不会在参数中包含itemsload集合的 属性。 如果加载任何项属性,则加载所有项。 但是,如果要循环访问集合中的项,但不需要加载项的任何特定属性,则需要 访问 load 属性 items 。
以下示例代码显示 name 正在为“示例”工作表中的每个图表设置的属性。
await Excel.run(async (context) => {
const sheet = context.workbook.worksheets.getItem("Sample");
const chartCollection = sheet.charts;
// Load the items property from the chart collection to set properties on individual charts.
chartCollection.load("items");
await context.sync();
chartCollection.items.forEach((chart, index) => {
chart.name = `Sample chart ${index}`;
});
});
不带 load (不推荐)的呼叫方
如果在未指定任何参数的情况下对对象或集合调用 load() 方法,则加载对象或集合对象的所有标量属性。 加载不需要的数据会降低加载项的速度。 始终显式指定要加载的属性。
重要
无参数 load 语句返回的数据量可能超过该服务的大小限制。 为了降低旧加载项的风险,该服务不会通过 不显式请求某些属性来返回这些属性 load 。 从此类加载操作中排除以下属性。
Excel.Range.numberFormatCategories
ClientResult
返回基元类型的基于承诺的 API 中的方法遵循类似于范例的 load/sync 模式。 例如, Excel.TableCollection.getCount 检索集合中的表数。
getCount 返回一个 ClientResult<number>,这意味着 value 返回 ClientResult 的 中的 属性是一个数字。 在调用 context.sync() 之前,脚本无法访问此值。
以下代码获取 Excel 工作簿中的表总数,并对此数目的日志记录到控制台。
const tableCount = context.workbook.tables.getCount();
// This sync call implicitly loads tableCount.value.
// Any other ClientResult values are loaded too.
await context.sync();
// Trying to log the value before calling sync would throw an error.
console.log (tableCount.value);
set()
在具有嵌套导航属性的对象上设置属性可能很麻烦。 作为使用导航路径设置单个属性的替代方法,如前所述,可以使用 object.set() 基于承诺的 JavaScript API 中的对象上可用的方法。 通过使用此方法,可以同时设置对象的多个属性,方法是传入同一 Office.js 类型的另一个对象,或者传入一个 JavaScript 对象,这些属性的结构类似于调用方法的对象的属性。
下面的代码示例设置区域的多个格式属性,具体方法是调用 set() 方法,并传入 JavaScript 对象,其中包含可反映 Range 对象中属性结构的属性名称和类型。 此示例假定 B2:E2 范围中有数据。
await Excel.run(async (context) => {
const sheet = context.workbook.worksheets.getItem("Sample");
const range = sheet.getRange("B2:E2");
range.set({
format: {
fill: {
color: '#4472C4'
},
font: {
name: 'Verdana',
color: 'white'
}
}
});
range.format.autofitColumns();
await context.sync();
});
某些属性不能直接设置
尽管可写的属性,但某些属性不能设置。 这些属性是必须将设置为单个对象的父属性的一部分。 这是因为父属性依赖于具有特定逻辑关系的子属性。 必须使用对象文字表示法设置这些父属性来设置整个对象,而不是设置该对象的单个子问题。 PageLayout 中可找到此示例。
zoom必须使用单个 PageLayoutZoomOptions 对象设置属性,如下所示。
// PageLayout.zoom.scale must be set by assigning PageLayout.zoom to a PageLayoutZoomOptions object.
sheet.pageLayout.zoom = { scale: 200 };
在前面的示例中, 无法 直接分配 zoom 值: sheet.pageLayout.zoom.scale = 200;。 该语句引发错误,因为 zoom 未加载。 即使 zoom 已加载,规模集也不会生效。 所有上下文操作 zoom、刷新加载项中的代理对象并覆盖本地设置的值。
此行为与 Range.format 等 导航属性。 可以使用对象导航设置 的属性 format ,如下所示。
// This will set the font size on the range during the next `content.sync()`.
range.format.font.size = 10;
可通过检查其只读修改者,识别不能直接设置其子问题的属性。 所有只读属性可直接设置其非只读子问题。 必须在该级别 PageLayout.zoom 可编写属性,如属性。 摘要:
- 只读属性:可通过导航设置子项目。
- 可写的属性:无法通过导航设置子项目(必须设置为初始父对象分配的一部分)。
*OrNullObject 方法和属性
当所需对象不存在时,某些配件方法和属性将引发异常。 例如,如果尝试通过指定工作簿未包含的工作表名称获取 Excel 工作表,则 getItem() 会引发 ItemNotFound 异常。 特定于应用程序的库为代码提供了一种方法,用于测试文档实体是否存在,而无需异常处理代码。 此方法使用 *OrNullObject 方法和属性的变体。 这些变体返回一个对象,如果指定的项不存在,则其 isNullObject 属性设置为 true ,而不是引发异常。
例如,可以在集合(如 Worksheets)上调用 getItemOrNullObject() 方法,尝试从集合中检索某个项。 方法 getItemOrNullObject() 返回指定项目(如果存在);否则,将返回其属性 isNullObject 为
注意
变体 *OrNullObject 从不返回 JavaScript 值 null。 它们返回普通 Office 代理对象。 如果对象表示的实体不存在,则 isNullObject 对象的 属性设置为 true。 不要测试返回的对象是否为 null 或伪造。 它从来不是 null、 false或 undefined。
以下代码示例尝试使用以下方法检索名为"数据"的 Excel getItemOrNullObject()。 如果不存在具有该名称的工作表,则代码将创建一个新工作表。 请注意,代码不会加载 isNullObject 属性。 调用 时 context.sync ,Office 会自动加载此属性,因此你无需使用 类似 dataSheet.load('isNullObject')的内容显式加载它。
await Excel.run(async (context) => {
let dataSheet = context.workbook.worksheets.getItemOrNullObject("Data");
await context.sync();
if (dataSheet.isNullObject) {
dataSheet = context.workbook.worksheets.add("Data");
}
// Set `dataSheet` to be the second worksheet in the workbook.
dataSheet.position = 1;
});
撤消支持
特定于应用程序的 Office JavaScript API 部分支持撤消。 这意味着用户可以通过撤消命令还原加载项所做的更改。 如果特定 API 不支持撤消,则会清除应用程序的撤消堆栈。 这意味着在调用该 API 之前,将无法撤消该 API 或任何内容的影响。
对撤消的 API 支持正在继续扩展,但目前还不完整。 我们建议不要以依赖于撤消支持的方式来设计加载项。