Tamanho da página
Nome completo | E-mail | Telefone | Idade | Conta | |
|---|---|---|---|---|---|
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
|
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.
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.
Tamanho da página
Nome completo | E-mail | Telefone | Idade | Conta | |
|---|---|---|---|---|---|
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
|
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.
<MainGrid tableName="contact" allowEdit>
<GridColumns>
<GridColumn columnName="firstname" />
<GridColumn columnName="lastname" />
<GridColumn columnName="emailaddress1" />
</GridColumns>
</MainGrid><MainGrid TableName="contact" AllowEdit="true">
<GridColumns>
<GridColumn ColumnName="firstname" />
<GridColumn ColumnName="lastname" />
<GridColumn ColumnName="emailaddress1" />
</GridColumns>
</MainGrid>Por que uma opção de alternar?
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.
<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:
String, Memo, EmailAddress, Phone, Url — TextEdit (com validação apropriada ao formato).Integer, Decimal, Double, BigInt — NumberEdit (análise sintática consciente da localização, mínimo/máximo a partir dos metadados).Boolean — BoolEdit (renderizado como um interruptor, alinhamento centralizado).Picklist — ChoiceEdit (menu suspenso orientado por conjunto de opções, rótulos localizados).MultiSelectPicklist — MultiSelectChoiceEdit.Lookup, Customer, Owner — LookupEdit (orientado por typeahead, respeita listas de permitir a tabela de alvo).DateTime, DateOnly — DateTimeEdit.Money — MoneyEdit (prefixo de símbolo de moeda de transactioncurrencyid).File, Image — FileEdit / ImageEdit (upload arrastando-solta).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.
{/* 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} />
)}
/><!-- 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>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.EditContextValidator para invariantes entre campos (por exemplo, "contato deve ter e-mail ou telefone").Validação em widgets personalizados
Entradas personalizadas renderizadas dentro
ChildContent/EditChildContentque não se estendemBaseEdit(por exemplo, um widget rawFluentTextFieldou 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 doValueChangedwidget antes de chamareditCtx.SetValue.
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.
<MainGrid tableName="contact" allowEdit>
<GridColumns>
<GridColumn columnName="firstname" />
<GridColumn columnName="lastname" />
<GridColumn columnName="emailaddress1" />
</GridColumns>
<GridButtons>
<NewRecordGridButton>
<NewContactForm />
</NewRecordGridButton>
<DeleteRecordGridButton />
</GridButtons>
</MainGrid><MainGrid TableName="contact" AllowEdit="true">
<GridColumns>
<GridColumn ColumnName="firstname" />
<GridColumn ColumnName="lastname" />
<GridColumn ColumnName="emailaddress1" />
</GridColumns>
<Buttons>
<NewRecordGridButton TForm="NewContactForm" />
<DeleteRecordGridButton />
</Buttons>
</MainGrid>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.
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.
<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>
)}
/><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>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.
<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 });
}}
/>
);
}}
/><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
EditChildContentautomaticamente com asEditContextValidatorlinhas e participavam do portão de validação antes do save do framework. OeditCtx.SetValuecaminho 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 doValueChangedcallback antes de chamarSetValue, ou volte ao padrão de editor drop-in.