Add MgGrid system documentation and architecture overview
Added a new section to ARCHITECTURE.md summarizing the MgGrid component system, its hierarchy, and key behaviors, with a link to the new MGGRID.md. Created MGGRID.md with comprehensive documentation on MgGrid design, usage, parameters, lifecycle, events, layout persistence, master-detail, InfoPanel, fullscreen, and public interface. Includes code examples, tables, and diagrams for developer reference.
This commit is contained in:
parent
b80b117a38
commit
6cce23a124
|
|
@ -35,6 +35,31 @@ User → DxGrid → AcSignalRDataSource → SignalR (AcBinary) → Server Hub
|
||||||
User ← DxGrid ← AcSignalRDataSource ← SignalR (AcBinary) ← Server Hub
|
User ← DxGrid ← AcSignalRDataSource ← SignalR (AcBinary) ← Server Hub
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## MgGrid Component System
|
||||||
|
|
||||||
|
The primary UI pattern for data screens. Full documentation: [`MGGRID.md`](MGGRID.md)
|
||||||
|
|
||||||
|
```
|
||||||
|
DxGrid (DevExpress)
|
||||||
|
└── MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClient>
|
||||||
|
└── [Project adapter, e.g. FruitBankGridBase<TDataItem>]
|
||||||
|
└── [Concrete grid, e.g. GridShippingBase]
|
||||||
|
```
|
||||||
|
|
||||||
|
| Component | Role |
|
||||||
|
|---|---|
|
||||||
|
| **MgGridBase** | Abstract base — SignalR CRUD, layout persistence, master-detail, edit state |
|
||||||
|
| **MgGridWithInfoPanel** | `DxSplitter` wrapper — grid + collapsible InfoPanel + fullscreen |
|
||||||
|
| **MgGridToolbarBase** | `DxToolbar` with grid reference and refresh action |
|
||||||
|
| **MgGridDataColumn** | `DxGridDataColumn` with URL template support |
|
||||||
|
| **MgGridInfoPanel** | Default InfoPanel — column-value display with edit mode |
|
||||||
|
|
||||||
|
Key behaviors:
|
||||||
|
- CRUD via **SignalR message tags** (`SignalRCrudTags`) — not REST
|
||||||
|
- **Layout auto-persistence** to `localStorage` (per-user, per-grid, per-master/detail)
|
||||||
|
- **Master-detail** via `CascadingParameter<IMgGridBase>`
|
||||||
|
- **New item IDs**: negative ints (client-side temp) or `Guid.NewGuid()`
|
||||||
|
|
||||||
## Key Design Decisions
|
## Key Design Decisions
|
||||||
|
|
||||||
- **DevExpress 25.1.3** exclusively — no mixing with other component libraries
|
- **DevExpress 25.1.3** exclusively — no mixing with other component libraries
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,399 @@
|
||||||
|
# MgGrid System
|
||||||
|
|
||||||
|
> Comprehensive documentation for the **MgGrid** component family — the primary UI data grid pattern in the AyCode.Blazor framework.
|
||||||
|
> Source: `AyCode.Blazor.Components/Components/Grids/`
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
**MgGridBase** is an abstract generic Blazor component that extends DevExpress `DxGrid` with:
|
||||||
|
- **Automatic SignalR CRUD** — data flows through `AcSignalRDataSource` with integer message tags
|
||||||
|
- **Layout persistence** — column order, widths, sorting, grouping auto-saved to `localStorage`
|
||||||
|
- **Master-detail hierarchy** — nested grids share context via `CascadingParameter`
|
||||||
|
- **InfoPanel integration** — side panel shows focused row details, supports edit mode
|
||||||
|
- **Fullscreen mode** — standalone overlay or via `MgGridWithInfoPanel` wrapper
|
||||||
|
- **Change tracking** — client-side tracking with server sync via `SaveChangesAsync`
|
||||||
|
|
||||||
|
## Component Hierarchy
|
||||||
|
|
||||||
|
```
|
||||||
|
DxGrid (DevExpress)
|
||||||
|
└── MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClient> (AyCode.Blazor — abstract)
|
||||||
|
└── [Project-specific adapter, e.g. FruitBankGridBase<TDataItem>]
|
||||||
|
└── [Concrete grid, e.g. GridShippingBase]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Companion Components
|
||||||
|
|
||||||
|
| Component | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| **MgGridWithInfoPanel** | `DxSplitter` wrapper: grid (left) + InfoPanel (right), fullscreen, splitter size persistence |
|
||||||
|
| **MgGridToolbarBase** | `DxToolbar` base with `Grid` reference, `RefreshClick` callback, `ShowOnlyIcon` toggle |
|
||||||
|
| **MgGridDataColumn** | Extended `DxGridDataColumn` with URL template support (`{PropertyName}` placeholders) |
|
||||||
|
| **MgGridInfoPanel** | Default InfoPanel showing column-value pairs, supports edit mode with save/cancel |
|
||||||
|
|
||||||
|
## Generic Type Parameters
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClient>
|
||||||
|
```
|
||||||
|
|
||||||
|
| Parameter | Constraint | Purpose |
|
||||||
|
|---|---|---|
|
||||||
|
| `TSignalRDataSource` | `: AcSignalRDataSource<TDataItem, TId, AcObservableCollection<TDataItem>>` | SignalR-backed data source with CRUD operations |
|
||||||
|
| `TDataItem` | `: class, IId<TId>` | Entity type displayed in the grid |
|
||||||
|
| `TId` | `: struct` | Primary key type (`int`, `Guid`) |
|
||||||
|
| `TLoggerClient` | `: AcLoggerBase` | Logger for diagnostics |
|
||||||
|
|
||||||
|
### Usage Example (Project-Specific Adapter)
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Project adapter — fixes TSignalRDataSource, TId, TLoggerClient for the entire project
|
||||||
|
public class FruitBankGridBase<TDataItem>
|
||||||
|
: MgGridBase<SignalRDataSourceObservable<TDataItem>, TDataItem, int, LoggerClient>
|
||||||
|
where TDataItem : class, IId<int>
|
||||||
|
{
|
||||||
|
protected override int GetLayoutUserId() => LoggedInModel.CustomerDto?.Id ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Concrete grid — only TDataItem remains open
|
||||||
|
public class GridShippingBase : FruitBankGridBase<Shipping>
|
||||||
|
{
|
||||||
|
public GridShippingBase()
|
||||||
|
{
|
||||||
|
GetAllMessageTag = SignalRTags.GetShippings;
|
||||||
|
AddMessageTag = SignalRTags.AddShipping;
|
||||||
|
UpdateMessageTag = SignalRTags.UpdateShipping;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Component Parameters
|
||||||
|
|
||||||
|
### Required Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `SignalRClient` | `AcSignalRClientBase` | SignalR client for server communication |
|
||||||
|
| `Logger` | `TLoggerClient` | Logger instance |
|
||||||
|
| `GetAllMessageTag` | `int` | SignalR tag for loading all items |
|
||||||
|
|
||||||
|
### CRUD Message Tags
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `GetAllMessageTag` | `int` | Tag for "get all items" request |
|
||||||
|
| `GetItemMessageTag` | `int` | Tag for "get single item" request |
|
||||||
|
| `AddMessageTag` | `int` | Tag for "add item" request |
|
||||||
|
| `UpdateMessageTag` | `int` | Tag for "update item" request |
|
||||||
|
| `RemoveMessageTag` | `int` | Tag for "remove item" request |
|
||||||
|
|
||||||
|
These are bundled into a `SignalRCrudTags` during `OnInitializedAsync`.
|
||||||
|
|
||||||
|
### Data & Context Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `DataSource` | `IList<TDataItem>` | Bind with `AcObservableCollection<TDataItem>` for external data. If not set, grid creates its own. |
|
||||||
|
| `ParentDataItem` | `IId<TId>?` | Parent entity for detail grids. `null` = master grid. |
|
||||||
|
| `KeyFieldNameToParentId` | `string?` | Property name on `TDataItem` that references the parent's ID |
|
||||||
|
| `ContextIds` | `object[]?` | Additional context passed to `AcSignalRDataSource` constructor |
|
||||||
|
| `FilterText` | `string?` | Text filter — propagated to data source, triggers reload |
|
||||||
|
|
||||||
|
### Display & Behavior Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `Caption` | `string` | `typeof(TDataItem).Name` | Grid title (shown in fullscreen header) |
|
||||||
|
| `GridName` | `string` | `"{TDataItem.Name}Grid"` | Name used in log messages |
|
||||||
|
| `AutoSaveLayoutName` | `string?` | `"Grid{TDataItem.Name}"` | Base name for layout storage keys |
|
||||||
|
|
||||||
|
## Event Callbacks
|
||||||
|
|
||||||
|
All grid events are re-exposed with `OnGrid` prefix to avoid collisions with `DxGrid` base events:
|
||||||
|
|
||||||
|
| Event | DxGrid Equivalent | When Fired |
|
||||||
|
|---|---|---|
|
||||||
|
| `OnGridItemDeleting` | `DataItemDeleting` | Before item removal (can cancel via `e.Cancel`) |
|
||||||
|
| `OnGridEditModelSaving` | `EditModelSaving` | Before item save (can cancel via `e.Cancel`) |
|
||||||
|
| `OnGridEditStart` | `EditStart` | When edit mode begins |
|
||||||
|
| `OnGridCustomizeEditModel` | `CustomizeEditModel` | When edit model is being prepared |
|
||||||
|
| `OnGridFocusedRowChanged` | `FocusedRowChanged` | When focused row changes |
|
||||||
|
| `OnDataSourceChanged` | — | After data source is loaded/reloaded |
|
||||||
|
| `OnGridItemChanged` | — | After server confirms a CRUD operation |
|
||||||
|
| `OnGridItemChanging` | — | Before a CRUD operation is sent to server |
|
||||||
|
|
||||||
|
## Lifecycle
|
||||||
|
|
||||||
|
```
|
||||||
|
1. OnInitializedAsync()
|
||||||
|
├── Validate Logger, SignalRClient (throw if null)
|
||||||
|
├── Create SignalRCrudTags from message tag parameters
|
||||||
|
├── Create TSignalRDataSource via Activator.CreateInstance(SignalRClient, crudTags, ContextIds)
|
||||||
|
├── Set DataSource.FilterText
|
||||||
|
├── Bind grid Data to data source inner list
|
||||||
|
└── Subscribe to: OnDataSourceLoaded, OnDataSourceItemChanged, OnSyncingStateChanged
|
||||||
|
|
||||||
|
2. SetParametersAsyncCore() [first time]
|
||||||
|
├── Set KeyFieldName = "Id"
|
||||||
|
├── Wire DxGrid events → internal handlers (OnItemSaving, OnItemDeleting, etc.)
|
||||||
|
├── Add OnCustomizeElement handler (edit row highlighting, detail cell styling)
|
||||||
|
└── Set defaults: TextWrapEnabled=false, AllowSelectRowByClick=true, etc.
|
||||||
|
|
||||||
|
3. OnParametersSet() [first time]
|
||||||
|
├── Set GridName default: "{TDataItem.Name}Grid"
|
||||||
|
├── Set AutoSaveLayoutName default: "Grid{TDataItem.Name}"
|
||||||
|
├── Wire layout auto-loading/saving handlers
|
||||||
|
└── Register with GridWrapper for splitter persistence
|
||||||
|
|
||||||
|
4. OnAfterRenderAsync(firstRender: true)
|
||||||
|
├── If DataSource parameter was provided: LoadDataSource(dataSourceParam, sync, notify)
|
||||||
|
└── Else: LoadDataSourceAsync(notify) — fires SignalR GetAll request
|
||||||
|
```
|
||||||
|
|
||||||
|
### Data Load Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
Grid.OnAfterRenderAsync
|
||||||
|
→ AcSignalRDataSource.LoadDataSourceAsync()
|
||||||
|
→ SignalRClient.SendMessage(GetAllMessageTag)
|
||||||
|
→ [AcBinary over SignalR]
|
||||||
|
→ Server Hub.OnReceiveMessage(tag, bytes, requestId)
|
||||||
|
→ DynamicMethodRegistry.TryFindMethod(tag)
|
||||||
|
→ [Tagged method executes, returns data]
|
||||||
|
→ [AcBinary response]
|
||||||
|
→ AcSignalRDataSource.BinaryToMerge()
|
||||||
|
→ OnDataSourceLoaded event
|
||||||
|
→ Grid.SetGridData() + StateHasChanged
|
||||||
|
```
|
||||||
|
|
||||||
|
## CRUD Operations
|
||||||
|
|
||||||
|
### Adding Items
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Fire-and-forget (add to local list, sync later)
|
||||||
|
await grid.AddDataItem(item);
|
||||||
|
|
||||||
|
// Immediate server sync
|
||||||
|
await grid.AddDataItemAsync(item);
|
||||||
|
```
|
||||||
|
|
||||||
|
### ID Generation for New Items
|
||||||
|
|
||||||
|
New items get **temporary client-side IDs** until the server assigns real ones:
|
||||||
|
|
||||||
|
| TId Type | Strategy | Example |
|
||||||
|
|---|---|---|
|
||||||
|
| `Guid` | `Guid.NewGuid()` | `a1b2c3d4-...` |
|
||||||
|
| `int` | `-1 * AcDomain.NextUniqueInt32` | `-1`, `-2`, `-3`, ... |
|
||||||
|
|
||||||
|
**Convention:** Negative integer IDs indicate unsaved items. The server replaces them with real auto-increment IDs.
|
||||||
|
|
||||||
|
### Edit Flow (Inline)
|
||||||
|
|
||||||
|
```
|
||||||
|
User clicks Edit → OnEditStart → OnCustomizeEditModel
|
||||||
|
├── Set GridEditState = New/Edit
|
||||||
|
├── For new items: assign temp ID, set parent FK if detail grid
|
||||||
|
├── Notify InfoPanel: SetEditMode()
|
||||||
|
└── Fire OnGridCustomizeEditModel callback
|
||||||
|
|
||||||
|
User clicks Save → OnItemSaving
|
||||||
|
├── Fire OnGridEditModelSaving callback (can cancel)
|
||||||
|
├── If new: AddDataItemAsync / InsertDataItemAsync
|
||||||
|
├── If existing: UpdateDataItemAsync
|
||||||
|
├── Reset GridEditState = None
|
||||||
|
└── Clear InfoPanel edit mode
|
||||||
|
|
||||||
|
User clicks Cancel → OnEditCanceling
|
||||||
|
├── Reset GridEditState = None
|
||||||
|
└── Clear InfoPanel edit mode
|
||||||
|
```
|
||||||
|
|
||||||
|
### Edit Row Highlighting
|
||||||
|
|
||||||
|
When `GridEditState != None`, the focused row and its cells get `background-color: #fffbeb` (warm yellow).
|
||||||
|
|
||||||
|
### Server Sync
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// All CRUD methods eventually call:
|
||||||
|
AcSignalRDataSource.SaveChangesAsync()
|
||||||
|
// → Sends tracked changes via SignalR using appropriate CRUD tags
|
||||||
|
// → Server processes and responds
|
||||||
|
// → OnDataSourceItemChanged fires for each changed item
|
||||||
|
```
|
||||||
|
|
||||||
|
## Layout Persistence
|
||||||
|
|
||||||
|
### Storage Keys
|
||||||
|
|
||||||
|
Grid layouts are stored in **localStorage** with structured keys:
|
||||||
|
|
||||||
|
```
|
||||||
|
AutoSave: {AutoSaveLayoutName}_{MasterOrParentTypeName}_AutoSave_{UserId}
|
||||||
|
UserSave: {AutoSaveLayoutName}_{MasterOrParentTypeName}_UserSave_{UserId}
|
||||||
|
Splitter: Splitter_{grid.AutomaticLayoutStorageKey}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
```
|
||||||
|
GridShipping_Master_AutoSave_42 ← master grid, user #42
|
||||||
|
GridShipping_Shipping_AutoSave_42 ← detail grid under Shipping parent
|
||||||
|
GridShipping_Master_UserSave_42 ← manually saved layout
|
||||||
|
Splitter_GridShipping_Master_AutoSave_42 ← splitter pane size
|
||||||
|
```
|
||||||
|
|
||||||
|
### Three Layout Tiers
|
||||||
|
|
||||||
|
| Tier | Key Contains | When Saved | When Loaded |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **Default** | (in-memory `_defaultLayoutJson`) | First `LayoutAutoLoading` — captures layout before any load | `ResetLayoutAsync()` — restores original |
|
||||||
|
| **AutoSave** | `_AutoSave_` | Every `LayoutAutoSaving` event (on any layout change) | Every `LayoutAutoLoading` event (on grid init) |
|
||||||
|
| **UserSave** | `_UserSave_` | `SaveUserLayoutAsync()` — explicit user action | `LoadUserLayoutAsync()` — explicit user action |
|
||||||
|
|
||||||
|
### Layout Operations
|
||||||
|
|
||||||
|
| Method | Behavior |
|
||||||
|
|---|---|
|
||||||
|
| `SaveUserLayoutAsync()` | Saves current layout to both UserSave AND AutoSave keys |
|
||||||
|
| `LoadUserLayoutAsync()` | Loads from UserSave key (if exists) |
|
||||||
|
| `ResetLayoutAsync()` | Removes AutoSave key, restores in-memory default layout |
|
||||||
|
| `HasUserLayoutAsync()` | Checks if UserSave key exists in localStorage |
|
||||||
|
|
||||||
|
### Persisted State
|
||||||
|
|
||||||
|
The layout (`GridPersistentLayout`) includes: column order, column widths, sort descriptors, group descriptors, filter row values, page size — serialized as JSON.
|
||||||
|
|
||||||
|
### User Identification
|
||||||
|
|
||||||
|
`GetLayoutUserId()` is virtual — defaults to `0`. Override in project adapter:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// FruitBankGridBase overrides this:
|
||||||
|
protected override int GetLayoutUserId() => LoggedInModel.CustomerDto?.Id ?? 0;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Master-Detail Hierarchy
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
|
||||||
|
1. `MgGridBase.BuildRenderTree` wraps content in `CascadingValue<IMgGridBase>`
|
||||||
|
2. Child grids receive this via `[CascadingParameter] IMgGridBase? ParentGrid`
|
||||||
|
3. `IsMasterGrid` = `ParentDataItem == null`
|
||||||
|
4. `GetRootGrid()` walks the `ParentGrid` chain to find the topmost grid
|
||||||
|
|
||||||
|
### Detail Grid Setup
|
||||||
|
|
||||||
|
```razor
|
||||||
|
<DetailRowTemplate>
|
||||||
|
@{
|
||||||
|
var parent = (Shipping)context.DataItem;
|
||||||
|
<GridShippingDocument ParentDataItem="@parent"
|
||||||
|
KeyFieldNameToParentId="ShippingId"
|
||||||
|
ContextIds="@(new object[] { parent.Id })" />
|
||||||
|
}
|
||||||
|
</DetailRowTemplate>
|
||||||
|
```
|
||||||
|
|
||||||
|
When `ParentDataItem` is set and `KeyFieldNameToParentId` is provided, new items automatically get their parent FK set via reflection.
|
||||||
|
|
||||||
|
## InfoPanel Integration
|
||||||
|
|
||||||
|
### MgGridWithInfoPanel Wrapper
|
||||||
|
|
||||||
|
```razor
|
||||||
|
<MgGridWithInfoPanel ShowInfoPanel="true" InfoPanelSize="400px">
|
||||||
|
<GridContent>
|
||||||
|
<GridShippingBase @ref="Grid" ... />
|
||||||
|
</GridContent>
|
||||||
|
@* Optional: custom InfoPanel via ChildContent *@
|
||||||
|
</MgGridWithInfoPanel>
|
||||||
|
```
|
||||||
|
|
||||||
|
The wrapper provides:
|
||||||
|
- `DxSplitter` with collapsible right pane
|
||||||
|
- Fullscreen overlay (`mg-fullscreen-overlay`)
|
||||||
|
- Splitter size persistence (`Splitter_{key}` in localStorage)
|
||||||
|
- Default `MgGridInfoPanel` if no custom `ChildContent`
|
||||||
|
|
||||||
|
### InfoPanel Data Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
FocusedRowChanged → InfoPanelInstance.RefreshData(grid, dataItem, visibleIndex)
|
||||||
|
Edit starts → InfoPanelInstance.SetEditMode(grid, editModel)
|
||||||
|
Edit ends/cancel → InfoPanelInstance.ClearEditMode()
|
||||||
|
```
|
||||||
|
|
||||||
|
`InfoPanelInstance` resolution: own `GridWrapper` → root grid's `GridWrapper` → `null`.
|
||||||
|
|
||||||
|
## Fullscreen Mode
|
||||||
|
|
||||||
|
Two modes depending on whether `MgGridWithInfoPanel` wraps the grid:
|
||||||
|
|
||||||
|
| Scenario | Behavior |
|
||||||
|
|---|---|
|
||||||
|
| **With wrapper** | `ToggleFullscreen()` delegates to wrapper — fullscreen includes grid + InfoPanel |
|
||||||
|
| **Standalone** | Grid renders its own `mg-fullscreen-overlay` with header (Caption + close button) and body |
|
||||||
|
|
||||||
|
## Rendering
|
||||||
|
|
||||||
|
`BuildRenderTree` uses manual render tree building (not Razor markup):
|
||||||
|
|
||||||
|
1. Outer `CascadingValue<IMgGridBase>` — provides this grid as `ParentGrid` to children
|
||||||
|
2. If standalone fullscreen: `div.mg-fullscreen-overlay` > `div.mg-fullscreen-header` + `div.mg-fullscreen-body` > `base.BuildRenderTree`
|
||||||
|
3. If normal: `div[style=display:contents]` > `base.BuildRenderTree`
|
||||||
|
4. `_gridRenderKey` (Guid) used as element key — changed by `ForceRenderAsync()` to force re-initialization
|
||||||
|
|
||||||
|
## Default Grid Settings
|
||||||
|
|
||||||
|
Set in `SetParametersAsyncCore` (first call only):
|
||||||
|
|
||||||
|
| Setting | Value |
|
||||||
|
|---|---|
|
||||||
|
| `KeyFieldName` | `"Id"` |
|
||||||
|
| `TextWrapEnabled` | `false` |
|
||||||
|
| `AllowSelectRowByClick` | `true` |
|
||||||
|
| `HighlightRowOnHover` | `true` |
|
||||||
|
| `AutoCollapseDetailRow` | `true` |
|
||||||
|
| `AutoExpandAllGroupRows` | `false` |
|
||||||
|
|
||||||
|
Project adapters add more defaults (e.g., `FruitBankGridBase` sets `EditMode=EditRow`, `FocusedRowEnabled=true`, `PageSize`, `ShowFilterRow` based on `IsMasterGrid`).
|
||||||
|
|
||||||
|
## Disposal
|
||||||
|
|
||||||
|
`DisposeAsync()` handles cleanup:
|
||||||
|
1. Set `_isDisposed = true` (guards all async callbacks)
|
||||||
|
2. Unsubscribe from `OnDataSourceLoaded`, `OnDataSourceItemChanged`, `OnSyncingStateChanged`
|
||||||
|
3. Remove `OnCustomizeElement` handler
|
||||||
|
|
||||||
|
All async callbacks check `_isDisposed` before proceeding.
|
||||||
|
|
||||||
|
## Interface: IMgGridBase
|
||||||
|
|
||||||
|
The public contract exposed to companion components (toolbar, InfoPanel, wrapper):
|
||||||
|
|
||||||
|
| Member | Type | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `IsSyncing` | `bool` | Whether SignalR sync is in progress |
|
||||||
|
| `Caption` | `string` | Grid title |
|
||||||
|
| `GridEditState` | `MgGridEditState` | `None` / `New` / `Edit` |
|
||||||
|
| `ParentGrid` | `IMgGridBase?` | Parent in master-detail hierarchy |
|
||||||
|
| `GetRootGrid()` | `IMgGridBase` | Walks to topmost grid |
|
||||||
|
| `StepPrevRow()` | `void` | Navigate to previous visible row |
|
||||||
|
| `StepNextRow()` | `void` | Navigate to next visible row |
|
||||||
|
| `InfoPanelInstance` | `IInfoPanelBase?` | Resolved InfoPanel reference |
|
||||||
|
| `IsFullscreen` | `bool` | Current fullscreen state |
|
||||||
|
| `AutomaticLayoutStorageKey` | `string` | Current auto-save storage key |
|
||||||
|
| `ToggleFullscreen()` | `void` | Toggle fullscreen mode |
|
||||||
|
| `SaveUserLayoutAsync()` | `Task` | Save layout manually |
|
||||||
|
| `LoadUserLayoutAsync()` | `Task` | Load manually saved layout |
|
||||||
|
| `ResetLayoutAsync()` | `Task` | Reset to default layout |
|
||||||
|
| `HasUserLayoutAsync()` | `Task<bool>` | Check if manual save exists |
|
||||||
|
|
||||||
|
## Event Args Classes
|
||||||
|
|
||||||
|
| Class | Base | Extra Properties |
|
||||||
|
|---|---|---|
|
||||||
|
| `GridDataItemChangedEventArgs<T>` | — | `Grid`, `DataItem`, `TrackingState`, `CancelStateChangeInvoke` |
|
||||||
|
| `GridDataItemChangingEventArgs<T>` | `GridDataItemChangedEventArgs<T>` | `IsCanceled` |
|
||||||
Loading…
Reference in New Issue