Auf / SubGrid gesetzt und ein Schalter AllowEdit="true"MainGrid mit der Beschriftung Editable erscheint im Zahnradmenü-Overflow. Das Einschalten schaltet jede deklarierte Spalte, deren Zellrenderer nicht überschrieben wird, in einen Inline-Editor um – der automatisch aus den Dataverse-Metadaten der Spalte gesendet wird –, während ein GridTemplateColumn's EditChildContent den Lesemodus ChildContentübernimmt. Bearbeitungen sammeln sich als ausstehende Zeilenänderungen im transaktionalen Puffer des Grids auf und landen nur in Dataverse, wenn der Benutzer speichert.
Live-Demo
A MainGrid über dem contact Tisch mit AllowEdit="true". Klicken Sie auf das Zahnrad-Symbol in der Symbolleiste und aktivieren Sie Bearbeitend. Die Spalte Full Name tauscht ihre Initialen-Avatar-Lesevorlage gegen einen benutzerdefinierten Einzelfeld-Editor, der die Benutzereingabe wieder in firstname + lastnameaufteilt. Die bloßen Spalten E-Mail, Telefon, Alter und Konto leuchten jeweils auf, wobei der Editor ihren Metadatentyp – TextEdit für die Zeichenketten, NumberEdit für die Ganzzahl, LookupEdit für die Kundenreferenz – abstimmt, ohne jede Spalte zu verdrahten.
React-Beispiel
Blazor-Beispiel
Jeder kann die Kontakte im untenstehenden Raster einsehen. Melden Sie sich an, um Ihre eigenen Kontakte zu erstellen, anzusehen und zu aktualisieren.
SpeichernAktualisieren
Seitengröße
102050100
Editable
Vollständiger Name
E-Mail
Telefon
Alter
Konto
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
Bearbeiten aktivieren
Auf dem Raster gesetzt AllowEdit="true" . Das Bearbeiten erfolgt pro Raster – der Schalter erscheint nur, wenn der Parameter gesetzt ist, und der Schalter selbst ist nur sichtbar, wenn mindestens eine Spalte bearbeitbar ist. Während der Schalter ausgeschaltet ist, verhält sich das Raster wie eine Nur-Lese-Ansicht; Während sie aktiv ist, werden die Zellen ihre Inline-Editoren gerendert und nicht gespeicherte Änderungen werden pro Zeile verfolgt.
Inline-Edit ändert das Rendering der Zellen für jede sichtbare Zeile, was eine andere Leseaffordanz als ein statisches Raster darstellt. Der Schalter erlaubt es den Nutzern, die Bearbeitungsmöglichkeit zu wählen, wenn sie sie brauchen (einen Wert eingeben, Tabben über Zellen hinweg usw.) und behält die sauberere Lesemöglichkeit für den Rest der Zeit. Das Raster merkt sich den Umschaltstatus der Sitzung.
Automatische Editor-Dispatch
Bare-Deklarationen <GridColumn ColumnName="..." /> ohne ChildContent automatisches Dispatchen des richtigen Inline-Editors, während das Raster im Bearbeitungsmodus ist. Das Framework liest die Metadaten der Spalte und wählt die Editor-Unterklasse aus:
Bereitstellen ChildContent Sie ein GridColumn , um sowohl die Lesemodusanzeige ALS auch den Bearbeitungsmodus-Editor für diese Spalte zu übernehmen. Sobald ChildContent gesetzt ist, hört das Framework auf, den metadatengesteuerten Editor automatisch zu senden – rendert den gewünschten Editor innerhalb der Vorlage und sperrt dessen Sichtbarkeit vom Grid-Flag Editable ab. Standard-Editoren (TextEdit, NumberEdit, ) ColumnEditwurden in die Vorlage automatisch verdrahtet, um über den kaskadierenden Zeilenkontext Dirty Tracking und Validierung durchzuführen.
React
{/* Benutzerdefinierte Anzeigezelle PLUS benutzerdefinierter Editor. React teilt die beiden auf
halbiert in cellRenderer (nur lesbar) und editRenderer (Bearbeitungsmodus) —
verwenden <GridTemplateColumn> , damit beide Renderer für die</GridTemplateColumn>
Gleiche logische Spalte. Die heruntergelassenen <TextEdit> Selbstregistrierungen für</TextEdit>
Dirty-Tracking und Validierung über den kaskadierenden Zeilenkontext. */}
<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
<!-- Benutzerdefinierte Anzeigezelle PLUS benutzerdefinierter Editor. Sobald ChildContent ist
Bereitgestellt wird das Framework nicht mehr automatisch eine Inline gesendet
Redakteur für diese Kolumne, also rendere selbst eine, wenn du möchtest
Die Zelle ist bearbeitbar. Der eingesteckte Editor registriert sich selbst für
Dirty-Tracking und Validierung über den kaskadierenden Zeilenkontext. -->
<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>
Validierung
Jede Reihe behält ihre eigene EditContextValidator. Standard-Editoren registrieren ihn am Mount und weisen ihre Fehler in der Reihe auf (rote Unterstreichung + Tooltip auf der Zelle). Die Speichertaste des Rasters ist deaktiviert, während jede Zeile einen ungelösten Validierungsfehler zeigt. Validierungsregeln stammen aus den Spaltenmetadaten:
RequiredLevel — ist die Spalte erforderlich, und ein leerer Wert blockiert das Speichern.
Format auf String-Spalten – E-Mail, Telefon, URL-Formate erzwingen die Form oben auf dem Typ.
MinValue / MaxValue in numerischen Spalten — Werte außerhalb des Bereichs werden vor dem Speichern markiert.
Benutzerdefinierte Validierung – zusätzliche Regeln pro Form über die EditContextValidator API für feldübergreifende Invarianten registrieren (z. B. "Kontakt muss entweder E-Mail oder Telefon haben").
Validierung bei benutzerdefinierten Widgets
Benutzerdefinierte Eingaben, die innerhalb ChildContent gerendert werden oder EditChildContent nicht erweitert BaseEdit werden (z. B. ein RAW FluentTextField oder ein Drittanbieter-Widget), werden nicht automatisch beim Validator der Zeile registriert. Wenn du sie validieren möchtest, verzichte entweder auf einen Standardeditor statt auf das rohe Widget oder führe die Validierung manuell im Callback des Widgets ValueChanged durch, bevor du aufgerufen editCtx.SetValuewirst.
Speichern & Stornieren
Bearbeitungen sammeln sich als ausstehende Updates in der Zeile an – modifizierte Zellen erhalten einen kleinen "schmutzig"-Indikator, gelöschte Zeilen werden durchgestrichen, und ausstehende Erstellungszeilen werden als neu angezeigt. Die Werkzeugleiste des Rasters zeigt Speichern und Abbrechen-Buttons auf, wenn ausstehende Änderungen vorliegen: Save commit den Batch in einem transaktionalen Durchgang; Cancel verwirft den Puffer und setzt jede Zeile auf ihren Serverzustand zurück. Koppele mit NewRecordGridButton und DeleteRecordGridButton im Fragment Buttons , um Zeilenerstellung und -löschung zur selben Bearbeitungstransaktion hinzuzufügen.
Koppele den Lesemodus ChildContent von 'GridTemplateColumns mit an, EditChildContent um eine synthetische/zusammengesetzte Spalte editierbar zu machen. Die Bearbeitungsvorlage erhält einen GridTemplateColumnEditContext — denselben Zeilenkontext wie die Lesevorlage (editCtx.Row.PrimaryRecord, der optionale editCtx.Row.ParentRecord in einem SubGrid, editCtx.DependsOnMetadata) plus einen imperativen editCtx.SetValue(columnName, value) Helfer.
EditChildContent löst nur aus, wenn das Raster bearbeitbar ist (AllowEdit="true" UND der Wechselbare Schalter aktiviert ist) und die Zeile nicht auf 'Pending-Delete' steht. Vorlagenspalten, die sie weglassen EditChildContent , bleiben auch im Bearbeitungsmodus nur lesbar – nützlich für Aktionsspalten, die immer dieselben Buttons darstellen sollten.
Muster 1 — Standard-Editoren für Drop-in
Der häufigste Fall: Die Zelle besteht aus mehreren gebundenen Spalten, und du möchtest dem Benutzer erlauben, jede einzelne zu bearbeiten. Fügen Sie die Matching <TextEdit> / <NumberEdit> / Editoren <ColumnEdit> hinein EditChildContent – das Framework kaskadierend den primären Datensatz und das Matching EditContextValidator der Reihe in diesen Scope, sodass die Editoren sich selbst registrieren, um Dirty Tracking und Validierung zu überprüfen. Keine zusätzliche Verkabelung erforderlich.
React
<GridTemplateColumn
displayName="Vollständiger Name"
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>;
}}
// Drop-in-Editoren – TextEdit verdrahtet automatisch zu Dirty-Tracking
// und Validierung über den kaskadierenden Zeilenkontext.
editRenderer={() => (
<div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
<TextEdit columnName="firstname" displayLabelWhenAvailable={false} />
<TextEdit columnName="lastname" displayLabelWhenAvailable={false} />
</div>
)}
/>Blazor
Muster 2 — Benutzerdefiniertes Widget mit SetValue
Wenn die Editor-Oberfläche nicht 1:1 einer Dataverse-Spalte zugeordnet ist – also einem einzigen "First Last"-Textfeld, das in zwei Spalten geteilt wird, einem Schieberegler, der an mehrere Prozentfelder gebunden ist, einem benutzerdefinierten Date-Range-Picker, der Start + Ende schreibt – rendere du für jede betroffene Spalte deine eigene Eingabe und den Aufruf editCtx.SetValue(columnName, value) . Das Framework leitet den Schreib an die richtige AliasedTableRecord Stelle und startet ValueChanged , sodass Dirty-Tracking die Änderung erkennt.
React
<GridTemplateColumn
displayName="Vollständiger Name"
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(' ');
// Benutzerdefiniertes Widget – aufgeteilt nach Platz; Verbraucher nehmen Kritik
// Validierung da <Input> registriert sich nicht selbst.
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="Vollständiger Name"
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 ?? "");
}
<!-- Benutzerdefiniertes Widget – aufgeteilt nach Platz; Verbraucher nehmen Kritik
Validierung, da FluentTextField sich nicht selbst registriert. -->
<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>
Validierung auf dem imperativen Weg
Standard-Editoren wurden automatisch in EditChildContent die Register der Zeilen EditContextValidator eingefügt und nehmen am Validate-before-Save-Gate des Frameworks teil. Der Pfad editCtx.SetValueumgeht diese Pipeline – Werte, die nicht von einem registrierten Editor stammen, werden nicht validiert. Wenn dein benutzerdefiniertes Widget Constraints erzwingen muss, führe sie im ValueChanged Callback aus, bevor du aufrufst SetValue, oder greife auf das Drop-in-Editor-Muster zurück.
Apply beginnt mit einem Filter in den folgenden Spalten:
E-Mail Konto Telefon
Apply beginnt mit einem Filter in den folgenden Spalten:
E-Mail Konto Telefon
Verwendet Zahlenvergleiche in den folgenden Spalten: