Refactor: decouple InfoPanel using MgGridWithInfoPanel

Major refactor to decouple InfoPanel logic from grid base. Introduces MgGridWithInfoPanel wrapper component to manage grid and InfoPanel layout and communication. InfoPanels are now customizable via Razor templates with named slots (header, footer, etc.), and grid-to-InfoPanel communication is routed through the wrapper. Removes legacy C#-only InfoPanel base classes and parameters from grid base. This improves flexibility, composability, and maintainability of grid+InfoPanel UIs.
This commit is contained in:
Loretta 2025-12-18 11:02:53 +01:00
parent 112d633590
commit 739d0fa808
5 changed files with 246 additions and 309 deletions

View File

@ -23,6 +23,7 @@ public interface IMgGridBase : IGrid
bool IsSyncing { get; }
string Caption { get; set; }
/// <summary>
/// Current edit state of the grid (None, New, Edit)
/// </summary>
@ -47,16 +48,11 @@ public interface IMgGridBase : IGrid
/// Navigates to the next row in the grid
/// </summary>
void StepNextRow();
/// <summary>
/// Whether this grid shows an InfoPanel
/// </summary>
bool ShowInfoPanel { get; set; }
/// <summary>
/// InfoPanel instance for displaying row details
/// InfoPanel instance for displaying row details (from wrapper)
/// </summary>
IInfoPanelBase? InfoPanelInstance { get; set; }
IInfoPanelBase? InfoPanelInstance { get; }
}
public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClient> : DxGrid, IMgGridBase, IAsyncDisposable
@ -97,90 +93,27 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
return current;
}
[Parameter] public bool ShowInfoPanel { get; set; } = false;
/// <summary>
/// Custom InfoPanel component type. Must inherit from MgInfoPanelTemplateBase.
/// If not set, the default MgGridInfoPanel is used.
/// Reference to the wrapper component for grid-InfoPanel communication
/// </summary>
[Parameter] public Type? InfoPanelType { get; set; }
[CascadingParameter]
public MgGridWithInfoPanel? GridWrapper { get; set; }
private object _focusedDataItem;
/// <summary>
/// InfoPanel instance for displaying row details
/// InfoPanel instance for displaying row details (from wrapper or direct)
/// </summary>
public IInfoPanelBase? InfoPanelInstance { get; set; }
public IInfoPanelBase? InfoPanelInstance
{
get => GridWrapper?.InfoPanelInstance;
set { /* Set through wrapper */ }
}
public MgGridBase() : base()
{
}
//protected override RenderFragment<RenderFragment> CreateRootComponent()
//{
// System.Diagnostics.Debug.WriteLine($"[MgGridBase] CreateRootComponent - ShowInfoPanel: {ShowInfoPanel}, GridName: {GridName}");
// if (!ShowInfoPanel)
// {
// // Ha nincs InfoPanel, használjuk az alapértelmezett renderelést
// System.Diagnostics.Debug.WriteLine("[MgGridBase] Using base CreateRootComponent");
// return base.CreateRootComponent();
// }
// // Ha van InfoPanel, akkor splitter-rel burkoljuk be
// System.Diagnostics.Debug.WriteLine("[MgGridBase] Creating splitter wrapper");
// return (RenderFragment<RenderFragment>)(content => (RenderFragment)(builder =>
// {
// var seq = 0;
// // DxSplitter
// builder.OpenComponent<DxSplitter>(seq++);
// builder.AddAttribute(seq++, "Width", "100%");
// builder.AddAttribute(seq++, "Height", "100%");
// // Panes
// builder.AddAttribute(seq++, "Panes", (RenderFragment)(panesBuilder =>
// {
// var paneSeq = 0;
// // Bal pane - Grid
// panesBuilder.OpenComponent<DxSplitterPane>(paneSeq++);
// panesBuilder.AddAttribute(paneSeq++, "ChildContent", (RenderFragment)(gridBuilder =>
// {
// // A grid eredeti content-jét rendereljük
// var baseRootComponent = base.CreateRootComponent();
// baseRootComponent(content)(gridBuilder);
// }));
// panesBuilder.CloseComponent();
// // Jobb pane - Egyelőre üres
// panesBuilder.OpenComponent<DxSplitterPane>(paneSeq++);
// panesBuilder.AddAttribute(paneSeq++, "Size", "0px");
// panesBuilder.AddAttribute(paneSeq++, "MinSize", "0px");
// panesBuilder.AddAttribute(paneSeq++, "AllowCollapse", true);
// panesBuilder.AddAttribute(paneSeq++, "Collapsed", true);
// panesBuilder.AddAttribute(paneSeq++, "ChildContent", (RenderFragment)(infoPanelBuilder =>
// {
// infoPanelBuilder.OpenElement(0, "div");
// infoPanelBuilder.AddAttribute(1, "style", "padding: 1rem;");
// infoPanelBuilder.AddContent(2, "Info Panel - Coming soon...");
// infoPanelBuilder.CloseElement();
// }));
// panesBuilder.CloseComponent();
// }));
// builder.CloseComponent();
// }));
//}
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
var seq = 0;
@ -190,70 +123,11 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
builder.AddAttribute(seq++, "Value", (IMgGridBase)this);
builder.AddAttribute(seq++, "ChildContent", (RenderFragment)(contentBuilder =>
{
// Nested grids or root without InfoPanel: render base grid only
if (ParentGrid != null || !ShowInfoPanel)
{
base.BuildRenderTree(contentBuilder);
return;
}
// Root grid with InfoPanel enabled: render splitter with grid + InfoPanel
var innerSeq = 0;
contentBuilder.OpenComponent<DxSplitter>(innerSeq++);
contentBuilder.AddAttribute(innerSeq++, "Width", "100%");
contentBuilder.AddAttribute(innerSeq++, "CssClass", "mg-grid-splitter");
contentBuilder.AddAttribute(innerSeq++, "Orientation", Orientation.Horizontal);
contentBuilder.AddAttribute(innerSeq++, "Panes", (RenderFragment)(panesBuilder =>
{
var paneSeq = 0;
// Left pane - Grid
panesBuilder.OpenComponent<DxSplitterPane>(paneSeq++);
panesBuilder.AddAttribute(paneSeq++, "ChildContent", (RenderFragment)(gridBuilder =>
{
base.BuildRenderTree(gridBuilder);
}));
panesBuilder.CloseComponent();
// Right pane - InfoPanel
panesBuilder.OpenComponent<DxSplitterPane>(paneSeq++);
panesBuilder.AddAttribute(paneSeq++, "Size", "400px");
panesBuilder.AddAttribute(paneSeq++, "MinSize", "0px");
panesBuilder.AddAttribute(paneSeq++, "MaxSize", "100%");
panesBuilder.AddAttribute(paneSeq++, "AllowCollapse", true);
panesBuilder.AddAttribute(paneSeq++, "CssClass", "mg-info-panel-pane");
panesBuilder.AddAttribute(paneSeq++, "ChildContent", (RenderFragment)(infoPanelBuilder =>
{
var infoPanelSeq = 0;
// Use custom InfoPanel type if provided, otherwise use default MgGridInfoPanel
var panelType = InfoPanelType ?? typeof(MgGridInfoPanel);
infoPanelBuilder.OpenComponent(infoPanelSeq++, panelType);
infoPanelBuilder.AddComponentReferenceCapture(infoPanelSeq++, instance =>
{
InfoPanelInstance = instance as IInfoPanelBase;
});
infoPanelBuilder.CloseComponent();
}));
panesBuilder.CloseComponent();
}));
contentBuilder.CloseComponent();
base.BuildRenderTree(contentBuilder);
}));
builder.CloseComponent(); // Close CascadingValue
builder.CloseComponent();
}
//protected override Task RaiseFocusedRowChangedAsync(GridFocusedRowChangedEventArgsBase args)
//{
// _focusedDataItem = args.DataItem;
// InvokeAsync(StateHasChanged);
// return base.RaiseFocusedRowChangedAsync(args);
//}
protected bool HasIdValue(TDataItem dataItem) => HasIdValue(dataItem.Id);
protected bool HasIdValue(TId id) => !_equalityComparerId.Equals(id, default);
protected bool IdEquals(TId id1, TId id2) => _equalityComparerId.Equals(id1, id2);
@ -373,12 +247,10 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
throw new NullReferenceException($"[{GetType().Name}] SignalRClient == null");
}
ShowInfoPanel = IsMasterGrid;
var crudTags = new SignalRCrudTags(GetAllMessageTag, GetItemMessageTag, AddMessageTag, UpdateMessageTag, RemoveMessageTag);
_dataSource = (TSignalRDataSource)Activator.CreateInstance(typeof(TSignalRDataSource), SignalRClient, crudTags, ContextIds)!;
_dataSource.FilterText = FilterText;
//_dataSource = new SignalRDataSource<TDataItem>(SignalRClient, crudTags, ContextIds) { FilterText = FilterText };
SetGridData(_dataSource.GetReferenceInnerList());
@ -514,11 +386,7 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
await OnGridCustomizeEditModel.InvokeAsync(e);
// Update InfoPanel to edit mode
var rootGrid = GetRootGrid();
if (rootGrid.ShowInfoPanel && rootGrid.InfoPanelInstance != null)
{
rootGrid.InfoPanelInstance.SetEditMode(editModel);
}
InfoPanelInstance?.SetEditMode(editModel);
await InvokeAsync(StateHasChanged);
}
@ -532,12 +400,7 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
{
_focusedDataItem = e.DataItem;
var rootGrid = GetRootGrid();
if (!rootGrid.ShowInfoPanel) return;
// Get the root grid's InfoPanel (the visible one)
var infoPanelInstance = rootGrid.InfoPanelInstance;
var infoPanelInstance = InfoPanelInstance;
if (infoPanelInstance != null && e.DataItem != null)
{
@ -549,7 +412,6 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
}
// Frissítjük az InfoPanel-t az új sor adataival
// The InfoPanel is now non-generic and works with any data type!
infoPanelInstance.RefreshData(this, e.DataItem, e.VisibleIndex);
}
@ -585,11 +447,7 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
GridEditState = MgGridEditState.None;
var rootGrid = GetRootGrid();
if (rootGrid.ShowInfoPanel && rootGrid.InfoPanelInstance != null)
{
rootGrid.InfoPanelInstance.ClearEditMode();
}
InfoPanelInstance?.ClearEditMode();
await InvokeAsync(StateHasChanged);
}
@ -598,11 +456,7 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
{
GridEditState = MgGridEditState.None;
var rootGrid = GetRootGrid();
if (rootGrid.ShowInfoPanel && rootGrid.InfoPanelInstance != null)
{
rootGrid.InfoPanelInstance.ClearEditMode();
}
InfoPanelInstance?.ClearEditMode();
await InvokeAsync(StateHasChanged);
}

View File

@ -6,63 +6,47 @@
@* Header *@
@if (HeaderTemplate != null)
{
@HeaderTemplate
@HeaderTemplate(GetActiveDataItem())
}
else
{
<div class="dxbl-grid-header-panel px-3 py-2 border-bottom">
<span class="fw-semibold">@((_currentGrid as IMgGridBase)?.Caption ?? "")</span>
<span class="fw-semibold">@(_currentGrid?.Caption ?? "")</span>
</div>
}
@* Toolbar *@
@if (_currentGrid != null)
{
<div class="mg-info-panel-toolbar border-bottom">
<MgGridToolbarTemplate Grid="_currentGrid as IMgGridBase" OnlyGridEditTools="true" ShowOnlyIcon="true" />
</div>
}
@* Toolbar *@
@if (_currentGrid != null)
{
<div class="mg-info-panel-toolbar border-bottom">
<MgGridToolbarTemplate Grid="_currentGrid" OnlyGridEditTools="true" ShowOnlyIcon="true" />
</div>
}
@* Content *@
<div class="mg-info-panel-content">
@if (GetActiveDataItem() != null && _currentGrid != null)
{
var dataItem = GetActiveDataItem()!;
var dataItemType = dataItem.GetType();
@* Before Columns *@
@if (BeforeColumnsTemplate != null)
{
@BeforeColumnsTemplate(GetActiveDataItem())
}
@* Columns *@
@if (ColumnsTemplate != null)
{
@ColumnsTemplate
@ColumnsTemplate(GetActiveDataItem())
}
else
{
<div class="mg-info-panel-grid">
@foreach (var column in GetVisibleColumns())
{
var displayText = GetDisplayTextFromGrid(column);
var value = GetCellValue(column);
var settingsType = GetEditSettingsType(column);
var isReadOnly = !_isEditMode || column.ReadOnly;
@RenderDefaultColumns()
}
<div class="mg-info-panel-item">
<div class="dxbl-form-layout-item">
<label class="dxbl-fl-lc @GetCaptionCssClass(isReadOnly) d-block mb-1 small">
@GetColumnCaption(column)
</label>
<div class="dxbl-fl-ec">
@if (_isEditMode && !column.ReadOnly)
{
@RenderEditableCell(column, dataItem, dataItemType, value, displayText, settingsType)
}
else
{
@RenderCellContent(column, value, displayText, settingsType)
}
</div>
</div>
</div>
}
</div>
@* After Columns *@
@if (AfterColumnsTemplate != null)
{
@AfterColumnsTemplate(GetActiveDataItem())
}
}
else
@ -76,24 +60,82 @@
@* Footer *@
@if (FooterTemplate != null)
{
@FooterTemplate
@FooterTemplate(GetActiveDataItem())
}
</div>
@code {
/// <summary>
/// Gets the columns to display based on edit mode and ShowReadOnlyFieldsInEditMode setting
/// Custom header template. Receives the current data item.
/// </summary>
private IEnumerable<DxGridDataColumn> GetVisibleColumns()
[Parameter] public RenderFragment<object?>? HeaderTemplate { get; set; }
/// <summary>
/// Content to render before the columns.
/// </summary>
[Parameter] public RenderFragment<object?>? BeforeColumnsTemplate { get; set; }
/// <summary>
/// Custom columns template. If not set, columns are auto-generated.
/// </summary>
[Parameter] public RenderFragment<object?>? ColumnsTemplate { get; set; }
/// <summary>
/// Content to render after the columns.
/// </summary>
[Parameter] public RenderFragment<object?>? AfterColumnsTemplate { get; set; }
/// <summary>
/// Custom footer template.
/// </summary>
[Parameter] public RenderFragment<object?>? FooterTemplate { get; set; }
private RenderFragment RenderDefaultColumns() => builder =>
{
if (!_isEditMode || ShowReadOnlyFieldsInEditMode)
var dataItem = GetActiveDataItem()!;
var dataItemType = dataItem.GetType();
var seq = 0;
builder.OpenElement(seq++, "div");
builder.AddAttribute(seq++, "class", "mg-info-panel-grid");
foreach (var column in GetVisibleColumns())
{
return _allDataColumns;
var displayText = GetDisplayTextFromGrid(column);
var value = GetCellValue(column);
var settingsType = GetEditSettingsType(column);
var isReadOnly = !_isEditMode || column.ReadOnly;
builder.OpenElement(seq++, "div");
builder.AddAttribute(seq++, "class", "mg-info-panel-item");
builder.OpenElement(seq++, "div");
builder.AddAttribute(seq++, "class", "dxbl-form-layout-item");
builder.OpenElement(seq++, "label");
builder.AddAttribute(seq++, "class", $"dxbl-fl-lc {GetCaptionCssClass(isReadOnly)} d-block mb-1 small");
builder.AddContent(seq++, GetColumnCaption(column));
builder.CloseElement();
builder.OpenElement(seq++, "div");
builder.AddAttribute(seq++, "class", "dxbl-fl-ec");
if (_isEditMode && !column.ReadOnly)
{
RenderEditableCell(column, dataItem, dataItemType, value, displayText, settingsType)(builder);
}
else
{
RenderCellContent(column, value, displayText, settingsType)(builder);
}
builder.CloseElement();
builder.CloseElement();
builder.CloseElement();
}
// In edit mode with ShowReadOnlyFieldsInEditMode=false, hide readonly columns
return _allDataColumns.Where(c => !c.ReadOnly);
}
builder.CloseElement();
};
private static string GetColumnCaption(DxGridDataColumn column)
{

View File

@ -27,32 +27,23 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
[Parameter] public bool ShowReadOnlyFieldsInEditMode { get; set; } = false;
/// <summary>
/// Custom header template. If not set, default header is used.
/// Reference to the wrapper component - automatically registers this InfoPanel
/// </summary>
[Parameter] public RenderFragment? HeaderTemplate { get; set; }
/// <summary>
/// Custom columns template. If not set, columns are auto-generated.
/// </summary>
[Parameter] public RenderFragment? ColumnsTemplate { get; set; }
/// <summary>
/// Custom footer template. If not set, no footer is rendered.
/// </summary>
[Parameter] public RenderFragment? FooterTemplate { get; set; }
[CascadingParameter]
public MgGridWithInfoPanel? GridWrapper { get; set; }
private ElementReference _panelElement;
private bool _isJsInitialized;
private const int DefaultTopOffset = 300; // Increased from 180 to account for header + tabs + toolbar
private IMgGridBase? _currentGrid;
private object? _currentDataItem;
private int _focusedRowVisibleIndex = -1;
private List<DxGridDataColumn> _allDataColumns = [];
protected IMgGridBase? _currentGrid;
protected object? _currentDataItem;
protected int _focusedRowVisibleIndex = -1;
protected List<DxGridDataColumn> _allDataColumns = [];
// Edit mode state
private bool _isEditMode;
private object? _editModel;
protected bool _isEditMode;
protected object? _editModel;
// Cache for edit settings to avoid repeated lookups
private readonly Dictionary<string, IEditSettings?> _editSettingsCache = [];
@ -61,6 +52,9 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
{
if (firstRender)
{
// Register this InfoPanel with the wrapper
GridWrapper?.RegisterInfoPanel(this);
await InitializeStickyAsync();
}
}
@ -95,21 +89,21 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
_focusedRowVisibleIndex = visibleIndex;
_editSettingsCache.Clear();
// Clear edit mode when refreshing with new data
_isEditMode = false;
_editModel = null;
System.Diagnostics.Debug.WriteLine($"[InfoPanel] RefreshData - _currentDataItem is null: {_currentDataItem == null}, cast success: {dataItem != null && _currentDataItem != null}");
// Ha nem vagyunk edit módban, frissítjük az oszlopokat
if (!_isEditMode)
if (_currentGrid != null && _currentDataItem != null)
{
if (_currentGrid != null && _currentDataItem != null)
{
_allDataColumns = GetAllDataColumns(_currentGrid);
System.Diagnostics.Debug.WriteLine($"[InfoPanel] RefreshData - loaded {_allDataColumns.Count} columns");
}
else
{
_allDataColumns = [];
System.Diagnostics.Debug.WriteLine($"[InfoPanel] RefreshData - cleared columns (grid or dataItem is null)");
}
_allDataColumns = GetAllDataColumns(_currentGrid);
System.Diagnostics.Debug.WriteLine($"[InfoPanel] RefreshData - loaded {_allDataColumns.Count} columns");
}
else
{
_allDataColumns = [];
System.Diagnostics.Debug.WriteLine($"[InfoPanel] RefreshData - cleared columns (grid or dataItem is null)");
}
StateHasChanged();
@ -121,6 +115,8 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
/// </summary>
public void SetEditMode(object editModel)
{
System.Diagnostics.Debug.WriteLine($"[InfoPanel] SetEditMode called - editModel type: {editModel?.GetType().Name ?? "null"}, grid.GridEditState: {_currentGrid?.GridEditState}");
_editModel = editModel;
_isEditMode = true;
_currentDataItem = _editModel;
@ -130,7 +126,8 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
_allDataColumns = GetAllDataColumns(_currentGrid);
}
StateHasChanged();
InvokeAsync(StateHasChanged);
System.Diagnostics.Debug.WriteLine($"[InfoPanel] SetEditMode - InvokeAsync(StateHasChanged) called, _isEditMode: {_isEditMode}");
}
/// <summary>
@ -138,10 +135,13 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
/// </summary>
public void ClearEditMode()
{
System.Diagnostics.Debug.WriteLine($"[InfoPanel] ClearEditMode called, grid.GridEditState: {_currentGrid?.GridEditState}");
_isEditMode = false;
_editModel = null;
_editSettingsCache.Clear();
StateHasChanged();
InvokeAsync(StateHasChanged);
System.Diagnostics.Debug.WriteLine($"[InfoPanel] ClearEditMode - InvokeAsync(StateHasChanged) called");
}
/// <summary>
@ -177,13 +177,13 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
/// <summary>
/// Gets the data item to display/edit (EditModel in edit mode, otherwise CurrentDataItem)
/// </summary>
private object? GetActiveDataItem() => _isEditMode && _editModel != null ? _editModel : _currentDataItem;
protected object? GetActiveDataItem() => _isEditMode && _editModel != null ? _editModel : _currentDataItem;
/// <summary>
/// Gets the display text for a field using the grid's internal formatting.
/// For ComboBox columns, tries to get the text from the lookup data source.
/// </summary>
private string GetDisplayTextFromGrid(DxGridDataColumn column)
protected string GetDisplayTextFromGrid(DxGridDataColumn column)
{
var dataItem = GetActiveDataItem();
if (_currentGrid == null || dataItem == null || string.IsNullOrWhiteSpace(column.FieldName))
@ -310,7 +310,37 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
};
}
private static List<DxGridDataColumn> GetAllDataColumns(IMgGridBase grid)
/// <summary>
/// Gets the columns to display based on edit mode and ShowReadOnlyFieldsInEditMode setting
/// </summary>
protected IEnumerable<DxGridDataColumn> GetVisibleColumns()
{
if (!_isEditMode || ShowReadOnlyFieldsInEditMode)
{
return _allDataColumns;
}
// In edit mode with ShowReadOnlyFieldsInEditMode=false, hide readonly columns
return _allDataColumns.Where(c => !c.ReadOnly);
}
protected object? GetCellValue(DxGridDataColumn column)
{
var dataItem = GetActiveDataItem();
if (_currentGrid == null || dataItem == null || string.IsNullOrWhiteSpace(column.FieldName))
return null;
try
{
return _currentGrid.GetDataItemValue(dataItem, column.FieldName);
}
catch
{
return null;
}
}
protected static List<DxGridDataColumn> GetAllDataColumns(IMgGridBase grid)
{
var columns = new List<DxGridDataColumn>();
@ -342,22 +372,6 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
return columns;
}
private object? GetCellValue(DxGridDataColumn column)
{
var dataItem = GetActiveDataItem();
if (_currentGrid == null || dataItem == null || string.IsNullOrWhiteSpace(column.FieldName))
return null;
try
{
return _currentGrid.GetDataItemValue(dataItem, column.FieldName);
}
catch
{
return null;
}
}
/// <summary>
/// Gets the EditSettings type for rendering logic
/// </summary>

View File

@ -0,0 +1,74 @@
@using DevExpress.Blazor
<CascadingValue Value="this">
@if (ShowInfoPanel)
{
<DxSplitter Width="100%" CssClass="mg-grid-with-info-panel" Orientation="Orientation.Horizontal">
<Panes>
<DxSplitterPane>
@GridContent
</DxSplitterPane>
<DxSplitterPane Size="@InfoPanelSize" MinSize="0px" MaxSize="100%" AllowCollapse="true" CssClass="mg-info-panel-pane">
@if (ChildContent != null)
{
@ChildContent
}
else
{
<MgGridInfoPanel />
}
</DxSplitterPane>
</Panes>
</DxSplitter>
}
else
{
@GridContent
}
</CascadingValue>
@code {
private IInfoPanelBase? _infoPanelInstance;
/// <summary>
/// The grid content to display in the left pane
/// </summary>
[Parameter]
public RenderFragment? GridContent { get; set; }
/// <summary>
/// InfoPanel content (e.g., GridShippingDocumentInfoPanel) to display in the right pane.
/// If not set, the default MgGridInfoPanel is used.
/// </summary>
[Parameter]
public RenderFragment? ChildContent { get; set; }
/// <summary>
/// Initial size of the InfoPanel pane. Default is "400px".
/// </summary>
[Parameter]
public string InfoPanelSize { get; set; } = "400px";
/// <summary>
/// Whether to show the InfoPanel. Default is true.
/// </summary>
[Parameter]
public bool ShowInfoPanel { get; set; } = true;
/// <summary>
/// Gets or sets the InfoPanel instance for grid-InfoPanel communication
/// </summary>
public IInfoPanelBase? InfoPanelInstance
{
get => _infoPanelInstance;
set => _infoPanelInstance = value;
}
/// <summary>
/// Registers an InfoPanel instance (called by child InfoPanel components)
/// </summary>
public void RegisterInfoPanel(IInfoPanelBase infoPanel)
{
_infoPanelInstance = infoPanel;
}
}

View File

@ -1,47 +0,0 @@
using Microsoft.AspNetCore.Components;
namespace AyCode.Blazor.Components.Components.Grids;
/// <summary>
/// Base class for custom InfoPanel templates.
/// Inherit from this class to create a custom InfoPanel for a specific grid.
/// </summary>
public abstract class MgInfoPanelTemplateBase : MgGridInfoPanel
{
/// <summary>
/// Override this to provide custom header content.
/// Return null to use default header.
/// </summary>
protected virtual RenderFragment? GetHeaderTemplate() => null;
/// <summary>
/// Override this to provide custom columns/content.
/// Return null to use auto-generated columns.
/// </summary>
protected virtual RenderFragment? GetColumnsTemplate() => null;
/// <summary>
/// Override this to provide custom footer content.
/// Return null to hide footer.
/// </summary>
protected virtual RenderFragment? GetFooterTemplate() => null;
protected override void OnInitialized()
{
SetTemplates();
base.OnInitialized();
}
protected override void OnParametersSet()
{
SetTemplates();
base.OnParametersSet();
}
private void SetTemplates()
{
HeaderTemplate = GetHeaderTemplate();
ColumnsTemplate = GetColumnsTemplate();
FooterTemplate = GetFooterTemplate();
}
}