Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Quando vários utilizadores e suplementos trabalham no mesmo livro do Excel, as alterações efetuadas por um utilizador podem criar um comportamento inesperado na instância do suplemento de outro utilizador. Os valores em cache do suplemento podem ficar obsoletos quando os cocriadores modificam o livro. Estes dados obsoletos resultam na apresentação de dados incorretos no suplemento, na tomada de decisões com base em informações desatualizadas ou na criação de conflitos de intercalação.
Por que motivo os programadores de suplementos precisam de lidar com a cocriação
O Excel suporta a cocriação em livros armazenados no OneDrive, no OneDrive for Business ou no SharePoint Online. Quando a funcionalidade Guardar Automaticamente está ativada, as alterações são sincronizadas em tempo real. O código do suplemento não sabe automaticamente quando os cocriadores modificam o livro.
Tem de lidar com a cocriação se o seu suplemento:
- Coloca em cache valores de livros em variáveis javaScript (risco de dados obsoletos).
- Armazena o estado em folhas de cálculo ocultas (risco de perda de sincronização).
- Adiciona linhas a tabelas com
TableRowCollection.add(risco de conflitos de intercalação). - Mostra a IU em resposta a alterações de dados (risco de caixas de diálogo inesperadas para todos os utilizadores).
Se o suplemento só ler dados uma vez no arranque ou raramente for executado em livros partilhados, o suporte de cocriação é de menor prioridade, mas deve continuar a ser monitorizado.
Importante
No Excel para Microsoft 365, a funcionalidade Guardar Automaticamente sincroniza as alterações em tempo real. Quando ativa a funcionalidade Guardar Automaticamente, os problemas de cocriação tornam-se mais frequentes e visíveis. Teste o suplemento com a Funcionalidade Guardar Automaticamente ativada para identificar potenciais problemas. Os utilizadores podem ativar/desativar a funcionalidade Guardar Automaticamente através do comutador no canto superior esquerdo da janela do Excel.
O Excel sincroniza o conteúdo do livro e não a memória do suplemento
O Excel sincroniza automaticamente o conteúdo do livro (como valores de células, formatação, dados de tabela) em todos os cocriadores. No entanto, o Excel não sincroniza as variáveis, objetos ou estado dentro da memória do suplemento. Cada utilizador executa a sua própria instância do seu suplemento com memória separada.
Problema: Dados obsoletos de variáveis em cache
No código seguinte, a variável do cachedValue Utilizador A nunca é atualizada automaticamente. Se a lógica do suplemento utilizar cachedValue para cálculos, apresentações ou decisões, está a trabalhar com informações desatualizadas.
// 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
Cada cocriador tem a sua própria instância de suplemento separada. Quando copia valores do livro para variáveis javaScript, essas cópias não permanecem sincronizadas com o livro. Tem de atualizar explicitamente os valores ou utilizar eventos para detetar alterações.
Solução: Utilizar eventos para detetar alterações de cocriação
Para manter o estado do suplemento sincronizado quando os cocriadores modificam o livro, utilize eventos do Excel. Os eventos notificam o suplemento quando o conteúdo do livro é alterado, para que possa atualizar os dados em cache ou atualizar a IU.
| Cenário | Evento a utilizar | Reason |
|---|---|---|
| A folha de cálculo oculta armazena as definições | BindingDataChanged |
Detetar quando os cocriadores alteram a configuração |
| O dashboard apresenta os valores das células | BindingDataChanged |
Manter o ecrã sincronizado com o livro |
| Monitorizar o intervalo específico de alterações | WorksheetChanged |
Mais flexível para deteção de alterações complexa |
| Controlar qualquer modificação da folha de cálculo | WorksheetChanged |
Maior consciencialização sobre as mudanças |
Exemplo: Manter uma dashboard sincronizada
Cenário: o suplemento apresenta uma dashboard a mostrar dados das células A1:C10. Sem processamento de eventos, o dashboard mostra dados obsoletos quando os cocriadores atualizam essas células.
O código seguinte utiliza o BindingDataChanged evento (BindingDataChanged). Este evento é ativado sempre que qualquer utilizador (local ou cocriador) modificar o intervalo vinculado. O processador de eventos atualiza os dados em cache, para que todos os utilizadores vejam as informações atuais.
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");
}
Não mostrar a IU nos processadores de eventos
Quando a cocriação está ativa, os processadores de eventos são executados para todos os utilizadores quando um utilizador efetua uma alteração. Este comportamento cria uma restrição de design crítica.
❌ Não faça isto:
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");
});
Quando o Utilizador B altera uma célula, o Utilizador A vê inesperadamente uma caixa de diálogo de validação, apesar de o Utilizador A não ter efetuado alterações. Esta experiência é confusa e disruptiva.
✅ Em alternativa, faça o seguinte:
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");
};
Utilize eventos para atualizar o estado interno e os ecrãs passivos do suplemento. Mostrar apenas caixas de diálogo, alertas ou IU modal em resposta a ações explícitas do utilizador, como cliques nos botões ou seleções de menus.
Evitar conflitos de linhas de tabela em cenários de cocriação
Quando o suplemento é utilizado TableRowCollection.add enquanto os cocriadores estão a editar a mesma tabela ou células próximas, o Excel deteta uma conflito de merge. Os utilizadores veem uma barra amarela a pedir-lhes para serem atualizados e as alterações recentes podem perder-se.
A TableRowCollection.add API altera a estrutura da tabela de uma forma que entra em conflito com as edições simultâneas. Quando o suplemento do Utilizador A adiciona uma linha enquanto o Utilizador B está a editar a célula B5, o Excel não consegue intercalar ambas as alterações com segurança.
Utilizar Range.values para adicionar linhas
Em vez de utilizar a API de Tabela, defina valores no intervalo diretamente abaixo da tabela. O Excel expande automaticamente a tabela sem criar conflitos.
❌ Não faça isto (causa conflitos):
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.
✅ Utilize esta abordagem:
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();
});
Requisitos adicionais
Para que a Range.values abordagem funcione de forma fiável:
Nenhuma regra de validação de dados abaixo da tabela: remova as regras de validação de dados das células abaixo da tabela ou aplique a validação a colunas inteiras em vez de intervalos de células específicos.
Processar dados existentes abaixo da tabela: se os utilizadores tiverem dados abaixo da tabela, insira primeiro uma linha em branco.
// 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"]];Não é possível adicionar linhas verdadeiramente vazias: as tabelas só se expandem automaticamente quando define dados reais. Se precisar de uma linha vazia, utilize uma solução.
- Coloque dados temporários (como um caráter de espaço) numa coluna oculta.
- Utilize dados de marcador de posição que os utilizadores podem limpar.
Resolver problemas comuns de cocriação
| Sintoma | Causa provável | Correção |
|---|---|---|
| O suplemento apresenta valores desatualizados | Valores colocados em cache em variáveis de JavaScript | Implementar processadores de eventos para atualizar em alterações |
| A barra de "atualização" amarela é apresentada com frequência | Utilizar TableRowCollection.add |
Mudar para Range.values para para adicionar linhas |
| As caixas de diálogo são apresentadas inesperadamente | Mostrar a IU nos processadores de eventos | Mostrar apenas a IU a partir de ações iniciadas pelo utilizador |
| As definições não são sincronizadas entre utilizadores | Folha de cálculo oculta não monitorizada relativamente a alterações | Adicionar BindingDataChanged evento no intervalo de definições |
| Alterações perdidas durante a cocriação | Intercalar conflitos a partir de modificações de tabelas | Seguir as melhores práticas da linha da tabela |