Botões de grade
Botões da barra de ferramentas de grade controlam como os usuários interagem com registros em um MainGrid ou SubGrid. Botões são colocados dentro do Buttons fragmento de renderização e aparecem automaticamente na barra de ferramentas da grade.
Categorias de Botões
Existem três categorias de botões de grade baseadas em como eles lidam com as operações de registro:
- Botões de Diálogo — Abra um formulário em um diálogo para criar ou editar registros em linha sem sair da página.
- Botões de Navegação — Navegue até uma URL de página separada para criar ou editar registros.
- Botões de Ação — Realize operações como excluir, vincular ou desvincular registros diretamente.
Escolhendo a abordagem certa
Escolha o formato do botão com base no contexto que o usuário precisa e na quantidade de novo estado envolvido. Os três baldes abaixo cobrem os casos típicos.
- Diálogo (inline) — Use
NewRecordGridButton/OpenRecordGridButtonquando o formulário se encaixa em um único diálogo e o usuário se beneficia ao permanecer na página atual (por exemplo, adicionando um contato a um registro da conta sem perder as outras edições não salvas da conta). Combine comBehavior="GridActionBehavior.WithGridContext"(o padrão) para integrar o save do novo registro no commit transacional do contexto pai. - Navegar (página inteira) — Use
NavigateNewRecordGridButton/NavigateOpenRecordGridButtonquando a criação ou edição precisa de uma superfície mais rica do que um diálogo pode oferecer confortavelmente: muitos campos, múltiplas abas, registros relacionados próprios, anexos ou URLs compartilháveis. A rota possui seu próprioRecordContext, então a grade principal não precisa estar ciente da complexidade do formulário. - Assistente (diálogo em várias etapas) — Use
NewRecordGridButton FormType="FormType.WizardForm"quando o formulário tem campos suficientes para que uma tela pareça lotada, mas os comandos são interdependentes o suficiente para que dividir entre as páginas valha o custo de navegação. Forma comum: a página 1 captura identidade/classificação, a página 2 captura detalhes que dependem das escolhas da página 1.
Salve modos de fluxo e comportamento
Todo botão de grade baseado em diálogo (NewRecordGridButton, OpenRecordGridButton, o par de ligação / desvinculação M2M) aceita um Behavior parâmetro que controla quando a chamada subjacente do Dataverse é disparada. Os dois valores correspondem a sequências de solicitação materialmente diferentes:
Comportamento = Imediatamente
O botão de salvar do diálogo emite a solicitação de criar / atualizar / associar / desassociar direto para o Dataverse via ExecuteMultipleAsync, atualiza a grade e fecha o diálogo. O ambiente MainContext / RecordContext (se houver) não percebe a mudança — já está comprometido.
- O usuário clica em Salvar no diálogo.
- O diálogo valida e depois envia um ou mais
OrganizationRequests atravésExecuteMultipleAsyncde . - Retornos de servidor; a grade se atualiza; O diálogo se encerra.
Escolha isso quando: o diálogo for independente (sem botão de salvar ao nível da página), ou o usuário realmente quiser que cada ação em nível de linha seja um commit próprio. Cada save aqui é independente — a conclusão parcial de um fluxo de trabalho multi-linha permanece no servidor.
Comportamento = ComContextoGradeGrid (padrão)
O botão Salvar do diálogo ativa a solicitação na fila pendente da grade (_rowsToCreate / _rowsToUpdate em Blazor, o equivalente React em useGridContext()) e fecha o diálogo. A chamada real do Dataverse só é acionada quando o botão de salvar do / RecordContextao redor MainContext é clicado.
- O usuário clica em Salvar no diálogo → registro (ou atualizar/associar/dissociar) fica em fila na grade.
- Diálogo encerrado. O contexto pai da página muda para
IsDirty=true; o botão Salvar em nível de página ativa. - O usuário clica no Save em nível de página → cada alteração na grade em fila + a atualização do registro pai + as requisições pendentes de todos os outros descendentes são agrupadas em um único
ExecuteMultipleAsync. - Retornos de servidor; a página atualiza; filas liberadas;
IsDirtyvira para trás.
Escolha isso quando: a página tem um pai RecordContext com seu próprio botão de salvar — o usuário espera uma semântica de "salvar a página inteira", e um commit parcial (pai salvo, linhas filhos não) seria um resultado pior do que reverter tudo junto. Esse é o padrão por esse motivo.
O padrão é WithGridContext
WithGridContexté o padrão para todo botão baseado em diálogo — Imediatamente é o opt-in. Se sua grade estiver fora de um contexto pai (uma página de listagem independente sem registro acima), a fila não tem para onde drenar e o diálogo automaticamente volta para a semântica Imediatamente.
NovoRegistroGradeBotão
Abre um formulário de diálogo para criar um novo registro. Requer um TForm parâmetro de tipo que especifique o componente Razor para ser renderizado como a forma.
Use Location para controlar onde o diálogo aparece: DialogLocation.Center (padrão) ou DialogLocation.Right (painel lateral).
Use FormType para escolher entre uma forma padrão (FormType.Form) ou um assistente de múltiplos passos (FormType.WizardForm).
Use Behavior para controlar quando o registro é criado: GridActionBehavior.Immediately salva no Dataverse imediatamente, enquanto GridActionBehavior.WithGridContext (padrão) adia o save até que o contexto pai seja confirmado.
{/* Diálogo centralizado com formato padrão */}
<NewRecordGridButton>
<NewContactForm />
</NewRecordGridButton>
{/* Painel lateral com formato padrão */}
<NewRecordGridButton location="panel">
<NewContactForm />
</NewRecordGridButton>
{/* Diálogo central com a forma de bruxo — as crianças são <WizardRecordPage>
(não é necessário um prop FormType; o botão detecta automaticamente). */}
<NewRecordGridButton location="center">
<WizardRecordPage>
<TextEdit columnName="firstname" />
<TextEdit columnName="lastname" />
</WizardRecordPage>
<WizardRecordPage>
<MoneyEdit columnName="annualincome" />
</WizardRecordPage>
</NewRecordGridButton>
{/* Salve imediatamente em vez de adiar */}
<NewRecordGridButton behavior="immediately">
<NewContactForm />
</NewRecordGridButton><!-- Diálogo centralizado com formato padrão -->
<NewRecordGridButton TForm="NewContactForm" />
<!-- Painel lateral com formato padrão -->
<NewRecordGridButton TForm="NewContactForm"
Location="DialogLocation.Right" />
<!-- Diálogo central com a forma do assistente -->
<NewRecordGridButton TForm="NewContactForm"
Location="DialogLocation.Center"
FormType="FormType.WizardForm" />
<!-- Salve imediatamente em vez de adiar -->
<NewRecordGridButton TForm="NewContactForm"
Behavior="GridActionBehavior.Immediately" />Exemplo de Formulário Padrão
O TForm parâmetro de tipo especifica um componente Razor que define o layout do formulário. Um formulário padrão é simplesmente um componente Razor contendo componentes do editor. Pode incluir abas (abs), seções ou qualquer layout que você precisar.
// EditContactForm.tsx
<TabList>
<Tab value="general">Geral</Tab>
<Tab value="other">Outros</Tab>
</TabList>
{activeTab === 'general' && (
<>
<TextEdit columnName="firstname" />
<TextEdit columnName="middlename" />
<TextEdit columnName="lastname" />
</>
)}
{activeTab === 'other' && (
<>
<MoneyEdit columnName="annualincome" />
<TextEdit columnName="telephone1" type="tel" />
</>
)}<!-- EditContactForm.razor -->
<FluentTabs Style="width: 100%">
<FluentTab Label="Geral">
<TextEdit ColumnName="firstname" />
<TextEdit ColumnName="middlename" />
<TextEdit ColumnName="lastname" />
</FluentTab>
<FluentTab Label="Outros">
<MoneyEdit ColumnName="annualincome" />
<TextEdit ColumnName="telephone1"
TextFieldType="TextFieldType.Tel" />
</FluentTab>
</FluentTabs>Exemplo de Formulário de Wizard
Um formulário de assistente divide o processo de criação em várias etapas. Defina cada etapa usando WizardRecordPage componentes. Use FormType="FormType.WizardForm" o botão para ativar o modo de mago.
// NewContactForm.tsx
<WizardRecordPage>
<TextEdit columnName="firstname" />
<TextEdit columnName="middlename" />
<TextEdit columnName="lastname" />
</WizardRecordPage>
<WizardRecordPage>
<MoneyEdit columnName="annualincome" />
<TextEdit columnName="telephone1" type="tel" />
</WizardRecordPage><!-- NewContactForm.razor -->
<WizardRecordPage>
<TextEdit ColumnName="firstname" />
<TextEdit ColumnName="middlename" />
<TextEdit ColumnName="lastname" />
</WizardRecordPage>
<WizardRecordPage>
<MoneyEdit ColumnName="annualincome" />
<TextEdit ColumnName="telephone1"
TextFieldType="TextFieldType.Tel" />
</WizardRecordPage>Nota
Ao usar uma forma de assistente, defina
FormType="FormType.WizardForm"sobre oNewRecordGridButton. O assistente exibe a navegação Voltar e Próximo e valida cada página antes de avançar.
Validação por página
Cada um WizardRecordPage tem por padrão para ForceSuccessfulValidationBeforeSave="true": o botão Next do assistente executa os validadores da página ativa antes de avançar e cancela a transição se algum campo necessário estiver vazio ou inválido. Defina false páginas que só tenham campos opcionais para que o usuário possa pulá-los. O botão Terminar da página final sempre valida independentemente dessa flag — a rejeição do lado do servidor é a única forma de superá-la.
<WizardRecordPage>
{/* Campos obrigatórios aqui — o usuário não pode avançar até que estejam preenchidos */}
<TextEdit columnName="firstname" />
<TextEdit columnName="lastname" />
</WizardRecordPage>
<WizardRecordPage forceSuccessfulValidationBeforeSave={false}>
{/* Campos opcionais — o usuário pode avançar mesmo com erros de validação */}
<MoneyEdit columnName="annualincome" />
</WizardRecordPage><WizardRecordPage>
<!-- Campos obrigatórios aqui — o usuário não pode avançar até que estejam preenchidos -->
<TextEdit ColumnName="firstname" />
<TextEdit ColumnName="lastname" />
</WizardRecordPage>
<WizardRecordPage ForceSuccessfulValidationBeforeSave="false">
<!-- Campos opcionais — o usuário pode avançar mesmo com erros de validação -->
<MoneyEdit ColumnName="annualincome" />
</WizardRecordPage>Registro compartilhado entre páginas
Todos WizardRecordPage os componentes dentro de um NewRecordGridButton compartilham o mesmo objeto subjacente TableRecord — a edição firstname na página 1 e annualincome na página 2 termina em um único payload de criação no final. S por página RecordContextse vinculam à mesma instância de registro, então a navegação entre etapas preserva as edições em andamento.
OpenRecordGridButton
Abre um formulário de diálogo para editar o(s) registro(s) selecionado(s). Por exemplo NewRecordGridButton, requer um TForm parâmetro de tipo. Quando múltiplos registros são selecionados, o formulário mostra campos compartilhados e aplica alterações a todos os registros selecionados.
O botão de editar é ativado automaticamente quando uma linha é feita com duplo clique na grade. Coloque AllowNavigateOnRowDoubleClick="false" na grade para suprimir o manipulador de duplo clique.
Por padrão, a coluna de nome primário da tabela também é renderizada como um hiperlink em cada linha, e clicar no link aciona a mesma ação de edição do duplo clique. Defina AllowNavigateOnPrimaryNameClick="false" na grade para suprimir o hiperlink e renderizar a célula do nome primário como texto simples. O hiperlink só aparece quando um OpenRecordGridButton ou NavigateOpenRecordGridButton está registrado, então grades sem botão de edição não são afetadas.
{/* Diálogo central (padrão) */}
<OpenRecordGridButton>
<EditContactForm />
</OpenRecordGridButton>
{/* Painel lateral */}
<OpenRecordGridButton location="panel">
<EditContactForm />
</OpenRecordGridButton>
{/* Salve imediatamente */}
<OpenRecordGridButton behavior="immediately">
<EditContactForm />
</OpenRecordGridButton>
{/* Suprimir o hiperlink do nome primário (clique duplo na linha ainda ativa a edição) */}
<MainGrid tableName="contact" allowNavigateOnPrimaryNameClick={false}>
<GridButtons>
<OpenRecordGridButton>
<EditContactForm />
</OpenRecordGridButton>
</GridButtons>
</MainGrid>
{/* Suprimir o manipulador de clique duplo da linha (o hiperlink do nome primário ainda funciona) */}
<MainGrid tableName="contact" allowOpenOnRowDoubleClick={false}>
<GridButtons>
<OpenRecordGridButton>
<EditContactForm />
</OpenRecordGridButton>
</GridButtons>
</MainGrid><!-- Diálogo central (padrão) -->
<OpenRecordGridButton TForm="EditContactForm" />
<!-- Painel lateral -->
<OpenRecordGridButton TForm="EditContactForm"
Location="DialogLocation.Right" />
<!-- Salve imediatamente -->
<OpenRecordGridButton TForm="EditContactForm"
Behavior="GridActionBehavior.Immediately" />
<!-- Suprimir o hiperlink do nome primário (clique duplo na linha ainda ativa a edição) -->
<MainGrid TableName="contact"
AllowNavigateOnPrimaryNameClick="false">
<Buttons>
<OpenRecordGridButton TForm="EditContactForm" />
</Buttons>
</MainGrid>
<!-- Suprimir o manipulador de clique duplo da linha (o hiperlink do nome primário ainda funciona) -->
<MainGrid TableName="contact"
AllowNavigateOnRowDoubleClick="false">
<Buttons>
<OpenRecordGridButton TForm="EditContactForm" />
</Buttons>
</MainGrid>NavegarNovoRegistroGridBotão
Navega até uma URL para criar um novo registro. Defina o Url parâmetro para a página de destino. Quando usado em um SubGrid, o contexto de relacionamento do registro pai é automaticamente adicionado como parâmetros de string de consulta.
Use o OnClick callback para definir dinamicamente a URL com base no contexto da grade. Isso é útil para grades multi-tabela onde a URL depende da tabela da visualização selecionada.
{/* URL estática */}
<NavigateNewRecordGridButton url="/contacts/new" />
{/* URL dinâmica baseada na visão selecionada */}
<NavigateNewRecordGridButton
url="/contacts/new"
onClick={(ctx) => {
switch (ctx.gridContext.selectedView?.tableName) {
case 'contact': ctx.url = '/contacts/new'; break;
case 'account': ctx.url = '/accounts/new'; break;
default: throw new Error('Tabela desconhecida');
}
}}
/><!-- URL estática -->
<NavigateNewRecordGridButton Url="/contacts/new" />
<!-- URL dinâmica baseada na visão selecionada -->
<NavigateNewRecordGridButton OnClick="OnNewClick" />
@code {
private async Task OnNewClick(NavigateGridButtonContext ctx)
{
ctx.Url = ctx.GridContext.SelectedView.TableName switch
{
"contact" => "/contacts/new",
"account" => "/accounts/new",
_ => throw new Exception("Tabela desconhecida"),
};
}
}NavegarAbroRegistroGridButtonNavegue
Navega até uma URL para editar o registro selecionado. O Url parâmetro suporta {0} como um marcador de posição para o ID do registro selecionado.
{/* Construa a URL por registro via urlFor */}
<NavigateOpenRecordGridButton
urlFor={(record) => `/contacts/edit?contactId=${record.id}`}
/>
{/* URL dinâmica baseada na visualização selecionada */}
<NavigateOpenRecordGridButton
urlFor={(record, ctx) => {
switch (ctx.selectedView?.tableName) {
case 'contact': return `/contacts/edit?contactId=${record.id}`;
case 'account': return `/accounts/edit?accountId=${record.id}`;
default: throw new Error('Tabela desconhecida');
}
}}
/><!-- {0} é substituído pelo ID do registro selecionado -->
<NavigateOpenRecordGridButton Url="/contacts/edit?contactId={0}" />
<!-- URL dinâmica -->
<NavigateOpenRecordGridButton OnClick="OnEditClick" />
@code {
private async Task OnEditClick(NavigateGridButtonContext ctx)
{
ctx.Url = ctx.GridContext.SelectedView.TableName switch
{
"contact" => "/contacts/edit?contactId={0}",
"account" => "/accounts/edit?accountId={0}",
_ => throw new Exception("Tabela desconhecida"),
};
}
}NavegarRegistroGridBotão
Um botão de navegação de uso geral com um rótulo, ícone e URL personalizados. Use isso para ações de navegação personalizadas que não se encaixem no novo padrão ou no padrão.
<NavigateRecordGridButton
title="Ver Detalhes"
url="/records/details?id={0}"
buttonEnabledBehavior={GridButtonBehavior.OnlyOneSelected}
/><NavigateRecordGridButton Title="Ver Detalhes"
Url="/records/details?id={0}"
ButtonEnabledBehavior="GridButtonBehavior.WhenOneSelected" />ExcluirRegistroGradeBotão
Exclui os registros selecionados após solicitar confirmação. Use Mode para controlar se os registros são deletados BulkOperationMode.Individually (um a um com o progresso) ou em um único lote.
{/* Apague um por um com o progresso */}
<DeleteRecordGridButton mode="individually" />
{/* Exclua em um único lote */}
<DeleteRecordGridButton mode="bulk" />
{/* Exclua imediatamente sem adiar */}
<DeleteRecordGridButton behavior="immediately" /><!-- Apague um por um com o progresso -->
<DeleteRecordGridButton Mode="BulkOperationMode.Individually" />
<!-- Exclua em um único lote -->
<DeleteRecordGridButton Mode="BulkOperationMode.Batch" />
<!-- Exclua imediatamente sem adiar -->
<DeleteRecordGridButton Behavior="GridActionBehavior.Immediately" />LinkExistingRecordGridButton
Abre uma caixa de diálogo de busca para encontrar e associar registros existentes por meio de uma relação muitos-para-muitos. Só se aplica em SubGrid relacionamentos N:N.
<LinkExistingRecordGridButton />
{/* Associe imediatamente */}
<LinkExistingRecordGridButton behavior="immediately" /><LinkExistingRecordGridButton />
<!-- Associe imediatamente -->
<LinkExistingRecordGridButton Behavior="GridActionBehavior.Immediately" />DesvincularExistindoRegistroGridBotão
Dissocia os registros selecionados de um relacionamento muitos-para-muitos após solicitação de confirmação. Só se aplica em SubGrid relacionamentos N:N.
<UnlinkExistingRecordGridButton />
{/* Desassocie-se imediatamente */}
<UnlinkExistingRecordGridButton behavior="immediately" /><UnlinkExistingRecordGridButton />
<!-- Desassocie-se imediatamente -->
<UnlinkExistingRecordGridButton Behavior="GridActionBehavior.Immediately" />GridButton
Um botão totalmente personalizado com um OnClick callback que recebe o arquivo atual GridContext. Use isso para implementar ações personalizadas na barra de ferramentas.
<SubGrid relationshipName="contact_customer_accounts">
<GridButtons>
<NewRecordGridButton>
<NewContactForm />
</NewRecordGridButton>
<OpenRecordGridButton>
<EditContactForm />
</OpenRecordGridButton>
<DeleteRecordGridButton />
{/* Botão personalizado */}
<GridButton
label="Exportação"
icon={<ArrowDownload20Regular />}
enabledBehavior={GridButtonBehavior.OneOrMoreSelected}
onClick={async (ctx) => {
const selectedRecords = ctx.selectedRecords;
// Lógica personalizada — exportar, imprimir, enviar e-mail, etc.
}}
/>
</GridButtons>
</SubGrid><SubGrid RelationshipName="contact_customer_accounts">
<Buttons>
<NewRecordGridButton TForm="NewContactForm" />
<OpenRecordGridButton TForm="EditContactForm" />
<DeleteRecordGridButton />
<!-- Botão personalizado -->
<GridButton Label="Exportação"
Icon="@(new Icons.Regular.Size20.ArrowDownload())"
IsButtonEnabled="DefaultGridButtonBehavior.GetBehavior(GridButtonBehavior.WhenOneOrMoreSelected)"
OnClick="OnExportClick" />
</Buttons>
</SubGrid>
@code {
private async Task OnExportClick(GridContext context)
{
var selectedRecords = context.SelectedRecords;
// Lógica personalizada — exportar, imprimir, enviar e-mail, etc.
}
}Parâmetros Comuns
Behavior— Controla se a operação é executada imediatamente (GridActionBehavior.Immediately) ou adiada até que o contexto pai salve (GridActionBehavior.WithGridContext).Mode— Para botões de exclusão/link/desvinculação, controla se operações em massa são executadas individualmente com feedback de progresso ou em uma única requisição por lote.IsButtonEnabled/IsButtonVisible— Predica que controla o estado do botão com base na seleção atual da linha.
GridButton Classe
Parâmetros
Nome | Tipo | Padrão | Descrição |
|---|---|---|---|
Appearance | Appearance? | Stealth | Aparência visual do botão da barra de ferramentas. Padrão para Appearance.Stealth Então o botão parece transparente em repouso e só pinta um fundo no hover — correspondendo aos botões de Atualizar e Configurações da grade. O CSS com escopo da grade (Appearance.Accent para um chamado à ação primário). |
Enabled | bool? | True | Anula o estado ativado do botão independentemente do GridButton.IsButtonEnabled resultado do predicado. |
Icon | Icon? | Um ícone opcional exibido no botão. | |
IsButtonEnabled | Func<IEnumerable<GridRowContext>, bool> | Um predicado avaliado em relação à seleção de linha atual para determinar se o botão está ativado. | |
IsButtonVisible | Func<IEnumerable<GridRowContext>, bool> | Um predicado avaliado em relação à seleção de linha atual para determinar se o botão é visível. | |
IsOpenRecordButton | bool | False | Especifica que esse é o botão 'Editar' para a grade. Apenas um botão deve ter essa propriedade definida como 'verdadeiro' em uma grade. O gerenciador de eventos desse botão é chamado quando uma linha é 'dupla clique' na grade. |
Label | string? | A etiqueta de texto exibida no botão. | |
Tooltip | string? | Uma dica opcional é mostrada quando o usuário paira sobre o botão. |
AppearanceAppearance.Stealth Então o botão parece transparente em repouso e só pinta um fundo no hover — correspondendo aos botões de Atualizar e Configurações da grade. O CSS com escopo da grade (Appearance.Accent para um chamado à ação primário).EnabledGridButton.IsButtonEnabled resultado do predicado.IconIsButtonEnabledIsButtonVisibleIsOpenRecordButtonLabelTooltipEventos
Nome | Tipo | Descrição |
|---|---|---|
OnClick | EventCallback<GridContext> | Dispara quando o botão é clicado, fornecendo a corrente Components.GridContext para o manipulador. |
OnClickComponents.GridContext para o manipulador.