複数のユーザーとアドインが同じ Excel ブックで動作する場合、あるユーザーによって行われた変更によって、別のユーザーのアドイン インスタンスで予期しない動作が発生する可能性があります。 共同編集者がブックを変更すると、アドインのキャッシュされた値が古くなる可能性があります。 この古いデータにより、アドインで正しくないデータが表示されたり、古い情報に基づいて意思決定を行ったり、マージ競合が発生したりします。
アドイン開発者が共同編集を処理する必要がある理由
Excel では、OneDrive、OneDrive for Business、または SharePoint Online に格納されているブックでの共同編集がサポートされています。 自動保存が有効になっている場合、変更はリアルタイムで同期されます。 アドイン コードは、共同編集者がブックを変更するタイミングを自動的に認識しません。
アドインの場合は、共同編集を処理する必要があります。
- JavaScript 変数にブック値をキャッシュします (古いデータのリスク)。
- 非表示のワークシートに状態を格納します (同期が失われるリスク)。
-
TableRowCollection.addを使用してテーブルに行を追加します (マージ競合のリスク)。 - データの変更に応じて UI を表示します (すべてのユーザーに対して予期しないダイアログが発生するリスク)。
アドインが起動時に 1 回だけデータを読み取るか、共有ブックでほとんど実行されない場合は、共同編集のサポートの優先順位は低くなりますが、引き続き監視する必要があります。
重要
Excel for Microsoft 365では、自動保存によって変更がリアルタイムで同期されます。 自動保存を有効にすると、共同編集の問題が頻繁に発生し、目立つようになります。 自動保存が有効になっているアドインをテストして、潜在的な問題を特定します。 ユーザーは、Excel ウィンドウの左上にあるスイッチを使用して自動保存を切り替えることができます。
Excel は、アドインのメモリではなくブックのコンテンツを同期します
Excel では、ブックの内容 (セル値、書式設定、テーブル データなど) がすべての共同編集者間で自動的に同期されます。 ただし、 Excel では、アドインの JavaScript 変数、オブジェクト、またはメモリ内の状態は同期されません。 各ユーザーは、個別のメモリを使用してアドインの独自のインスタンスを実行します。
問題: キャッシュされた変数からの古いデータ
次のコードでは、ユーザー A の cachedValue 変数が自動的に更新されることはありません。 アドイン ロジックが計算、表示、または決定に cachedValue を使用する場合は、古い情報を使用しています。
// User A's add-in reads a value.
const range = context.workbook.worksheets.getActiveWorksheet().getRange("A1");
range.load("values");
await context.sync();
const cachedValue = range.values[0][0]; // Stores "Contoso".
console.log(cachedValue); // "Contoso"
// Meanwhile, User B (coauthor) changes A1 to "Fabrikam".
// User B's change synchronizes to the workbook.
// User A's add-in still has the old value.
console.log(cachedValue); // Still "Contoso" - STALE!
// The workbook has the new value.
range.load("values");
await context.sync();
console.log(range.values[0][0]); // "Fabrikam" - CURRENT
各共同編集者には、独自の個別のアドイン インスタンスがあります。 ブックの値を JavaScript 変数にコピーしても、それらのコピーはブックと同期されません。 値を明示的に更新するか、イベントを使用して変更を検出する必要があります。
解決策: イベントを使用して共同編集の変更を検出する
共同編集者がブックを変更したときにアドインの状態を同期するには、Excel イベントを使用します。 イベントは、ブックのコンテンツが変更されたときにアドインに通知されるため、キャッシュされたデータを更新したり、UI を更新したりできます。
| シナリオ | 使用するイベント | 理由 |
|---|---|---|
| 非表示のワークシートには設定が格納されます | BindingDataChanged |
共同編集者が構成を変更するタイミングを検出する |
| ダッシュボードにセル値が表示される | BindingDataChanged |
ブックと同期して表示を維持する |
| 変更の特定の範囲を監視する | WorksheetChanged |
複雑な変更検出の柔軟性 |
| ワークシートの変更を追跡する | WorksheetChanged |
より広範な変更認識 |
例: ダッシュボードの同期を維持する
シナリオ: アドインには、セル A1:C10 からのデータを示すダッシュボードが表示されます。 イベント処理を行わないと、共同編集者がそれらのセルを更新すると、ダッシュボードに古いデータが表示されます。
次のコードでは、 BindingDataChanged イベント (BindingDataChanged) を使用します。 このイベントは、任意のユーザー (ローカルまたは共同編集者) がバインドされた範囲を変更するたびにアクティブになります。 イベント ハンドラーは、キャッシュされたデータを更新して、すべてのユーザーに現在の情報が表示されるようにします。
let cachedData = null;
// Initial load.
async function loadDashboard() {
await Excel.run(async (context) => {
const range = context.workbook.worksheets.getActiveWorksheet().getRange("A1:C10");
range.load("values");
await context.sync();
cachedData = range.values;
updateDashboardDisplay(cachedData);
});
}
// Set up event to detect changes from coauthors.
async function setupCoauthoringSupport() {
await Excel.run(async (context) => {
const sheet = context.workbook.worksheets.getActiveWorksheet();
const range = sheet.getRange("A1:C10");
// Create a binding to enable change detection.
const binding = context.workbook.bindings.add(range, Excel.BindingType.range, "DashboardData");
await context.sync();
// Register event handler for data changes.
binding.onDataChanged.add(handleDataChange);
await context.sync();
});
}
// This activates when coauthors change the bound range.
async function handleDataChange(event) {
await Excel.run(async (context) => {
const binding = context.workbook.bindings.getItem("DashboardData");
const range = binding.getRange();
range.load("values");
await context.sync();
// Update cached data and refresh display.
cachedData = range.values;
updateDashboardDisplay(cachedData);
});
}
function updateDashboardDisplay(data) {
// Update your UI with the current data.
console.log("Dashboard refreshed with current data");
}
イベント ハンドラーに UI を表示しない
共同編集がアクティブな場合、ユーザーが変更を行うと、すべてのユーザーに対してイベント ハンドラーが実行されます。 この動作により、重要な設計制約が作成されます。
❌ 次の操作を行わないでください。
binding.onDataChanged.add(async (event) => {
// This is a bad pattern. It shows a dialog to all users when any user changes data.
Office.context.ui.displayDialogAsync("https://contoso.com/validation.html");
});
ユーザー B がセルを変更すると、ユーザー A が変更を加えていなくても、ユーザー A に検証ダイアログが予期せず表示されます。 このエクスペリエンスは混乱し、混乱を招きます。
✅ 代わりに次の操作を行います。
let cachedData = null;
binding.onDataChanged.add(async (event) => {
await Excel.run(async (context) => {
const range = event.binding.getRange();
range.load("values");
await context.sync();
// Update internal state silently.
cachedData = range.values;
// Update displayed values without dialogs or alerts.
document.getElementById("dashboard").textContent = JSON.stringify(cachedData);
});
});
// Only show UI in response to explicit user actions.
document.getElementById("showData").onclick = async () => {
// Now it's OK to show UI - user clicked a button.
Office.context.ui.displayDialogAsync("https://contoso.com/validation.html");
};
イベントを使用して、アドインの内部状態とパッシブディスプレイを更新します。 ボタンのクリックやメニューの選択など、明示的なユーザー アクションに応答するダイアログ、アラート、またはモーダル UI のみを表示します。
共同編集シナリオでテーブル行の競合を回避する
共同編集者が同じテーブルまたは近くのセルを編集しているときにアドインでTableRowCollection.addを使用すると、Excel はマージの競合を検出します。 ユーザーに更新を求める黄色のバーが表示され、最近の変更が失われる可能性があります。
TableRowCollection.add API は、同時編集と競合する方法でテーブル構造を変更します。 ユーザー B がセル B5 を編集しているときにユーザー A のアドインが行を追加した場合、Excel では両方の変更を安全にマージできません。
Range.values を使用して行を追加する
Table API を使用する代わりに、テーブルのすぐ下の範囲に値を設定します。 Excel では、競合を作成せずにテーブルが自動的に展開されます。
❌ これを行わないでください (競合の原因になります)。
const table = context.workbook.tables.getItem("SalesData");
table.rows.add(null, [["Product", 100, "=B2*1.2"]]);
// This is a bad pattern. This code causes coauthoring conflicts.
✅ 次の方法を使用します。
await Excel.run(async (context) => {
const table = context.workbook.tables.getItem("SalesData");
const tableRange = table.getRange();
tableRange.load("rowCount, address");
await context.sync();
// Get the range directly below the table.
const sheet = context.workbook.worksheets.getActiveWorksheet();
const newRowRange = table.getDataBodyRange().getRowsBelow(1);
// Set values - table automatically expands without conflicts.
newRowRange.values = [["Product", 100, "=B2*1.2"]];
await context.sync();
});
追加要件
Range.valuesアプローチを確実に機能させるには:
テーブルの下にデータ検証ルールがない: テーブルの下のセルから データ検証ルール を削除するか、特定のセル範囲ではなく列全体に検証を適用します。
テーブルの下にある既存のデータを処理する: ユーザーがテーブルの下にデータを持っている場合は、最初に空白の行を挿入します。
// Insert empty row to push existing data down. let insertRange = table.getDataBodyRange().getRowsBelow(1); insertRange.insert(Excel.InsertShiftDirection.down); await context.sync(); // Now set your data. insertRange = table.getDataBodyRange().getRowsBelow(1); insertRange.values = [["Product", 100, "=B2*1.2"]];本当に空の行を追加できません:テーブルは、実際のデータを設定すると自動的に展開されます。 空の行が必要な場合は、回避策を使用します。
- 非表示列に一時データ (空白文字など) を配置します。
- ユーザーがクリアできるプレースホルダー データを使用します。
共同編集に関する一般的な問題のトラブルシューティング
| 現象 | 考えられる原因 | 修正プログラム |
|---|---|---|
| アドインに古い値が表示される | JavaScript 変数にキャッシュされた値 | 変更時に更新するイベント ハンドラーを実装する |
| 黄色の "更新" バーが頻繁に表示される | 使用 TableRowCollection.add |
行を追加するための Range.values に切り替える |
| ダイアログが予期せずポップアップする | イベント ハンドラーでの UI の表示 | ユーザーが開始したアクションからの UI のみを表示する |
| 設定がユーザー間で同期されない | 非表示のワークシートで変更が監視されない | 設定範囲 BindingDataChanged イベントを追加する |
| 共同編集中に失われた変更 | テーブルの変更からのマージ競合 | テーブル行のベスト プラクティスに従う |
関連項目
Office Add-ins