Configurado AllowEdit="true" em MainGrid / SubGrid e uma opção de alternância rotulada como Editável aparece no menu de engrenagens. Ligar a coluna muda toda coluna declarada cujo renderizador de célula não é sobreposto para um editor inline — despachado automaticamente a partir dos metadados Dataverse da coluna — enquanto um GridTemplateColumn's EditChildContent assume seu modo ChildContentde leitura. As edições se acumulam como mudanças pendentes de linha no buffer transacional da grade e só aparecem no Dataverse quando o usuário salva.
Demo ao vivo
Um MainGrid sobre a contact mesa com AllowEdit="true". Clique no ícone de engrenagem no overflow da barra de ferramentas e ative Editável . A coluna Nome Completo troca seu modelo de leitura inicial-avatar por um editor personalizado de campo único que divide a entrada do usuário em firstname + lastname. As colunas nuas de E-mail, Telefone, Idade e Conta acendem, com o editor correspondendo ao tipo de metadado — TextEdit para as strings, NumberEdit para o inteiro, LookupEdit para a referência do cliente — sem qualquer fiação por coluna.
Exemplo de reação
Exemplo de Blazor
Qualquer pessoa pode ver os contatos na grade abaixo. Faça login para criar, visualizar e atualizar seus próprios contatos.
SalvarAtualizar
Tamanho da página
102050100
Editable
Nome completo
E-mail
Telefone
Idade
Conta
Metz, Barrows and Nicolas
Schmeler and Sons
Dooley - Nitzsche
Waelchi, Murazik and Considine
Bashirian - Weber
Adams - Torphy
Krajcik Group
Sanford - Littel
Kilback - Towne
Jacobson - Klein
React TypeScript
Razor
Habilitando a edição
Ajustado AllowEdit="true" na grade. A edição é opt-in por grade — a opção só aparece quando o parâmetro está definido, e a opção só é visível quando pelo menos uma coluna é editável. Enquanto a alavanca está desligada, a grade se comporta como uma visualização somente de leitura; Enquanto está ligado, as células renderizam seus editores inline e as alterações não salvas são rastreadas por linha.
A edição em linha altera a renderização das células para cada linha visível, o que é uma possibilidade de leitura diferente de uma grade estática. O toggle permite que os usuários optem pela opção de edição quando precisarem (digitando um valor, separando células, etc.) e mantendo a opção de leitura limpa pelo restante do tempo. A grade lembra o estado de alternância da sessão.
Despacho automático do editor
<GridColumn ColumnName="..." /> Declarações sem ChildContent despacho automático do editor inline direito enquanto a grade está em modo de edição. O framework lê os metadados da coluna e escolhe a subclasse editor:
Suprima ChildContent em um GridColumn para assumir tanto a exibição do modo de leitura quanto do editor do modo de edição para aquela coluna. Uma vez ChildContent definido, o framework para de despachar automaticamente o editor orientado por metadados — renderize qualquer editor que você quiser dentro do template e bloqueie sua visibilidade pela bandeira da Editable grade. Editores padrão (TextEdit, NumberEdit, ColumnEdit) foram inseridos no template que se conectam automaticamente ao dirty-tracking e validação via o contexto da linha em cascata.
React
{/* Célula de exibição personalizada MAIS editor personalizado. React divide os dois
se divide em cellRenderer (somente leitura) e editRenderer (modo edit) —
Use <GridTemplateColumn> para que ambos os renderizadores possam coexistir para o</GridTemplateColumn>
Mesma coluna lógica. Os <TextEdit> registradores automáticos drop-in para</TextEdit>
rastreamento sujo e validação via contexto de linha em cascata. */}
<GridTemplateColumn
displayName="E-mail"
dependsOn={['emailaddress1']}
sortBy="emailaddress1"
cellRenderer={({ record }) => {
const email = (record.properties?.emailaddress1 as { value?: string })?.value;
return <span>{email}</span>;
}}
editRenderer={() => (
<TextEdit columnName="emailaddress1" displayLabelWhenAvailable={false} />
)}
/>Blazor
<!-- Célula de exibição personalizada MAIS editor personalizado. Uma vez que o ChildContent é
fornecido, a estrutura não despacha mais automaticamente um inline
editor desta coluna, então faça um se quiser
A célula editável. O editor drop-in se autoregistra para
rastreamento sujo e validação via contexto de linha em cascata. -->
<GridColumn ColumnName="emailaddress1">
<ChildContent>
@{
var email = context.PrimaryRecord
.GetValueOrDefault<StringValue>("emailaddress1")?.Value;
}
@if (this.Editable)
{
<TextEdit ColumnName="emailaddress1"
DisplayLabelWhenAvailable="false"
DisplayTooltipWhenAvailable="false" />
}
else
{
<span>@email</span>
}
</ChildContent>
</GridColumn>
Validação
Cada linha mantém seu EditContextValidatorpróprio . Editores padrão registram com ele no suporte e exibem seus erros em linha (sublinhado vermelho + dica de ferramenta na célula). O botão de salvar da grade está desativado enquanto qualquer linha apresenta um erro de validação não resolvido. As regras de validação vêm dos metadados da coluna:
RequiredLevel — a coluna é necessária, e um valor vazio bloqueia o salvamento.
Format nas colunas string — e-mail, telefone e formatos de URL impõem a forma sobre o tipo.
MinValue / MaxValue em colunas numéricas — valores fora de faixa são sinalizados antes do salvamento.
Validação personalizada — registrar regras adicionais por forma via API EditContextValidator para invariantes entre campos (por exemplo, "contato deve ter e-mail ou telefone").
Validação em widgets personalizados
Entradas personalizadas renderizadas dentro ChildContent / EditChildContent que não se estendem BaseEdit (por exemplo, um widget raw FluentTextField ou de terceiros) não se registram automaticamente com o validador da linha. Se precisar que eles sejam validados, ou descarte um editor padrão em vez do widget bruto, ou execute a validação manualmente dentro do callback do ValueChanged widget antes de chamar editCtx.SetValue.
Salvar e Cancelar
Edições se acumulam como atualizações pendentes na linha — células modificadas recebem um pequeno indicador "sujo", linhas excluídas são riscadas, e linhas de criação pendente aparecem como novas. A barra de ferramentas da grade mostra os botões Salvar e Cancelar sempre que há alterações pendentes: Salvar faz commit do lote em uma única passagem transacional; Cancelar descarta o buffer e reverte cada linha para o estado do servidor. Pareie com NewRecordGridButton e DeleteRecordGridButton no Buttons fragmento para adicionar criação e exclusão de linhas à mesma transação de edição.
Combine o modo ChildContent de leitura de a GridTemplateColumncom um EditChildContent para tornar uma coluna sintética/composta editável. O modelo de edição recebe — GridTemplateColumnEditContext o mesmo contexto de linha do template de leitura (editCtx.Row.PrimaryRecord, o opcional editCtx.Row.ParentRecord em um SubGrid, editCtx.DependsOnMetadata) mais um auxiliar imperativoeditCtx.SetValue(columnName, value).
EditChildContent dispara apenas quando a grade é editável (AllowEdit="true" E a opção de Editável está ativada) e a linha não está pendente-deletação. Colunas de modelo que omitem EditChildContent permanecem somente leitura mesmo no modo de edição — úteis para colunas de ações que sempre devem renderizar os mesmos botões.
Padrão 1 — Editores padrão disponíveis
O caso mais comum: a célula é composta por múltiplas colunas vinculadas, e você quer deixar o usuário editar cada uma. Coloque os editores correspondentes <TextEdit> / <ColumnEdit><NumberEdit> / dentro EditChildContent — o framework encadeia o registro primário e a correspondência EditContextValidator da linha nesse escopo, para que os editores se registrem para rastreamento e validação sujos. Não é necessário fiação extra.
React
<GridTemplateColumn
displayName="Nome completo"
dependsOn={['firstname', 'lastname']}
sortBy="lastname"
cellRenderer={({ record }) => {
const first = (record.properties?.firstname as { value?: string })?.value ?? '';
const last = (record.properties?.lastname as { value?: string })?.value ?? '';
return <strong>{first} {last}</strong>;
}}
// Editores drop-in — TextEdit liga automaticamente para o rastreamento sujo
// e validação via contexto de linha em cascata.
editRenderer={() => (
<div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
<TextEdit columnName="firstname" displayLabelWhenAvailable={false} />
<TextEdit columnName="lastname" displayLabelWhenAvailable={false} />
</div>
)}
/>Blazor
<GridTemplateColumn Title="Nome completo"
DependsOn="@(new[] { "firstname", "lastname" })"
SortBy="lastname">
<ChildContent Context="ctx">
<strong>
@(ctx.Row.PrimaryRecord.GetValueOrDefault<StringValue>("firstname")?.Value)
@(ctx.Row.PrimaryRecord.GetValueOrDefault<StringValue>("lastname")?.Value)
</strong>
</ChildContent>
<!-- Editores drop-in — TextEdit liga automaticamente para o rastreamento sujo
e validação via contexto de linha em cascata. -->
<EditChildContent Context="editCtx">
<FluentStack Orientation="Orientation.Horizontal" HorizontalGap="6"
VerticalAlignment="VerticalAlignment.Center">
<TextEdit ColumnName="firstname"
DisplayLabelWhenAvailable="false"
DisplayTooltipWhenAvailable="false" />
<TextEdit ColumnName="lastname"
DisplayLabelWhenAvailable="false"
DisplayTooltipWhenAvailable="false" />
</FluentStack>
</EditChildContent>
</GridTemplateColumn>
Padrão 2 — Widget personalizado com SetValue
Quando a superfície do editor não mapeia 1:1 para uma coluna do Dataverse — uma única caixa de texto "Primeiro Último" que se divide em duas colunas, um controle deslizante vinculado a múltiplos campos percentuais, um seletor personalizado por intervalo de datas escrevendo início e fim — renderize sua própria entrada e chame editCtx.SetValue(columnName, value) para cada coluna afetada. O framework roteia a escrita para o correto AliasedTableRecord e dispara ValueChanged para que o dirty-tracking capture a alteração.
React
<GridTemplateColumn
displayName="Nome completo"
dependsOn={['firstname', 'lastname']}
sortBy="lastname"
cellRenderer={({ record }) => {
const first = (record.properties?.firstname as { value?: string })?.value ?? '';
const last = (record.properties?.lastname as { value?: string })?.value ?? '';
return <strong>{first} {last}</strong>;
}}
editRenderer={({ record, setValue }) => {
const first = (record.properties?.firstname as { value?: string })?.value ?? '';
const last = (record.properties?.lastname as { value?: string })?.value ?? '';
const combined = [first, last].filter(Boolean).join(' ');
// Widget personalizado — dividido no espaço; Opiniões do consumidor
// validação já que <Input> não se registra sozinha.
return (
<Input
value={combined}
onChange={(_, data) => {
const next = data.value ?? '';
const idx = next.indexOf(' ');
const head = idx === -1 ? next : next.slice(0, idx);
const tail = idx === -1 ? '' : next.slice(idx + 1);
setValue('firstname', { $type: 'StringValue', value: head });
setValue('lastname', { $type: 'StringValue', value: tail || null });
}}
/>
);
}}
/>Blazor
<GridTemplateColumn Title="Nome completo"
DependsOn="@(new[] { "firstname", "lastname" })"
SortBy="lastname">
<ChildContent Context="ctx">
@{
var first = ctx.Row.PrimaryRecord.GetValueOrDefault<StringValue>("firstname")?.Value;
var last = ctx.Row.PrimaryRecord.GetValueOrDefault<StringValue>("lastname")?.Value;
}
<strong>@first @last</strong>
</ChildContent>
<EditChildContent Context="editCtx">
@{
var combined =
(editCtx.Row.PrimaryRecord.GetValueOrDefault<StringValue>("firstname")?.Value ?? "")
+ " "
+ (editCtx.Row.PrimaryRecord.GetValueOrDefault<StringValue>("lastname")?.Value ?? "");
}
<!-- Widget personalizado — dividido no espaço; Opiniões do consumidor
validação porque o FluentTextField não se registra sozinho. -->
<FluentTextField Value="@combined.Trim()" ValueChanged="@(next =>
{
var parts = next.Split(' ', 2);
editCtx.SetValue("firstname", new StringValue { Value = parts[0] });
editCtx.SetValue("lastname", new StringValue { Value = parts.Length > 1 ? parts[1] : null });
})" />
</EditChildContent>
</GridTemplateColumn>
Validação no caminho imperativo
Editores padrão se registravam EditChildContent automaticamente com as EditContextValidator linhas e participavam do portão de validação antes do save do framework. O editCtx.SetValue caminho contorna esse pipeline — valores que não vieram de um editor registrado não são validados. Se seu widget personalizado precisar impor restrições, execute-as dentro do ValueChanged callback antes de chamar SetValue, ou volte ao padrão de editor drop-in.
Apply começa com um filtro nas colunas seguintes:
Conta E-mail Telefone
Apply começa com um filtro nas colunas seguintes:
Conta E-mail Telefone
Utiliza comparações numéricas nas colunas a seguir: