From 90f12a160e7f3cde8fe837df5dd1aba7255e86a0 Mon Sep 17 00:00:00 2001 From: Loretta Date: Wed, 17 Dec 2025 13:54:07 +0100 Subject: [PATCH] Refactor InfoPanel: non-generic, nested grid support - Replace generic InfoPanel with non-generic version using IInfoPanelBase - Add ParentGrid, GetRootGrid, and InfoPanelInstance to IMgGridBase for nested grid hierarchy - Only root grid displays InfoPanel; nested grids inherit context - InfoPanel now handles any data type via reflection and object - All grid-to-InfoPanel communication routed through root grid - Add option to show/hide readonly fields in edit mode - Improve InfoPanel CSS for up to 4 responsive columns - Remove redundant code and add debug output for InfoPanel data flow --- .../Components/Grids/MgGridBase.cs | 171 +++++++---- .../Components/Grids/MgGridInfoPanel.razor | 288 +++--------------- .../Components/Grids/MgGridInfoPanel.razor.cs | 106 ++++--- .../Grids/MgGridInfoPanel.razor.css | 12 +- 4 files changed, 221 insertions(+), 356 deletions(-) diff --git a/AyCode.Blazor.Components/Components/Grids/MgGridBase.cs b/AyCode.Blazor.Components/Components/Grids/MgGridBase.cs index 3bc7653..8c03df1 100644 --- a/AyCode.Blazor.Components/Components/Grids/MgGridBase.cs +++ b/AyCode.Blazor.Components/Components/Grids/MgGridBase.cs @@ -27,6 +27,16 @@ public interface IMgGridBase : IGrid /// MgGridEditState GridEditState { get; } + /// + /// Parent grid in nested grid hierarchy (null if this is a root grid) + /// + IMgGridBase? ParentGrid { get; } + + /// + /// Gets the root grid in the hierarchy + /// + IMgGridBase GetRootGrid(); + /// /// Navigates to the previous row in the grid /// @@ -36,6 +46,16 @@ public interface IMgGridBase : IGrid /// Navigates to the next row in the grid /// void StepNextRow(); + + /// + /// Whether this grid shows an InfoPanel + /// + bool ShowInfoPanel { get; set; } + + /// + /// InfoPanel instance for displaying row details + /// + IInfoPanelBase? InfoPanelInstance { get; set; } } public abstract class MgGridBase : DxGrid, IMgGridBase, IAsyncDisposable @@ -61,11 +81,30 @@ public abstract class MgGridBase public MgGridEditState GridEditState { get; private set; } = MgGridEditState.None; - [Parameter] public bool ShowInfoPanel { get; set; } = true; + /// + [CascadingParameter] + public IMgGridBase? ParentGrid { get; set; } + + /// + public IMgGridBase GetRootGrid() + { + var current = (IMgGridBase)this; + while (current.ParentGrid != null) + { + current = current.ParentGrid; + } + return current; + } + + [Parameter] public bool ShowInfoPanel { get; set; } = false; private object _focusedDataItem; - private MgGridInfoPanel? _infoPanelInstance; - + + /// + /// InfoPanel instance for displaying row details + /// + public IInfoPanelBase? InfoPanelInstance { get; set; } + public MgGridBase() : base() { } @@ -137,54 +176,65 @@ public abstract class MgGridBase(seq++); - builder.AddAttribute(seq++, "Width", "100%"); - builder.AddAttribute(seq++, "CssClass", "mg-grid-splitter"); - builder.AddAttribute(seq++, "Orientation", Orientation.Horizontal); - - builder.AddAttribute(seq++, "Panes", (RenderFragment)(panesBuilder => + // Wrap everything in a CascadingValue to provide this grid as ParentGrid to nested grids + builder.OpenComponent>(seq++); + builder.AddAttribute(seq++, "Value", (IMgGridBase)this); + builder.AddAttribute(seq++, "ChildContent", (RenderFragment)(contentBuilder => { - var paneSeq = 0; - - // Left pane - Grid - panesBuilder.OpenComponent(paneSeq++); - panesBuilder.AddAttribute(paneSeq++, "ChildContent", (RenderFragment)(gridBuilder => + // Nested grids or root without InfoPanel: render base grid only + if (ParentGrid != null || !ShowInfoPanel) { - base.BuildRenderTree(gridBuilder); - })); - panesBuilder.CloseComponent(); + base.BuildRenderTree(contentBuilder); + return; + } - // Right pane - InfoPanel (sticky to viewport) - panesBuilder.OpenComponent(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"); + // Root grid with InfoPanel enabled: render splitter with grid + InfoPanel + var innerSeq = 0; - panesBuilder.AddAttribute(paneSeq++, "ChildContent", (RenderFragment)(infoPanelBuilder => + contentBuilder.OpenComponent(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 infoPanelSeq = 0; - infoPanelBuilder.OpenComponent>(infoPanelSeq++); - infoPanelBuilder.AddComponentReferenceCapture(infoPanelSeq++, instance => + var paneSeq = 0; + + // Left pane - Grid + panesBuilder.OpenComponent(paneSeq++); + panesBuilder.AddAttribute(paneSeq++, "ChildContent", (RenderFragment)(gridBuilder => { - _infoPanelInstance = (MgGridInfoPanel)instance; - }); - infoPanelBuilder.CloseComponent(); + base.BuildRenderTree(gridBuilder); + })); + panesBuilder.CloseComponent(); + + // Right pane - InfoPanel + panesBuilder.OpenComponent(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; + infoPanelBuilder.OpenComponent(infoPanelSeq++); + infoPanelBuilder.AddComponentReferenceCapture(infoPanelSeq++, instance => + { + InfoPanelInstance = (MgGridInfoPanel)instance; + }); + infoPanelBuilder.CloseComponent(); + })); + + panesBuilder.CloseComponent(); })); - panesBuilder.CloseComponent(); + contentBuilder.CloseComponent(); })); - - builder.CloseComponent(); + builder.CloseComponent(); // Close CascadingValue } //protected override Task RaiseFocusedRowChangedAsync(GridFocusedRowChangedEventArgsBase args) @@ -311,6 +361,7 @@ public abstract class MgGridBase - @* Header - matches grid toolbar height *@ + @* Header *@
Sor részletei
- @* Content - scrollable area *@ + @* Content *@
@if (GetActiveDataItem() != null && _currentGrid != null) { var dataItem = GetActiveDataItem()!; + var dataItemType = dataItem.GetType();
- @foreach (var column in _allDataColumns) + @foreach (var column in GetVisibleColumns()) { var displayText = GetDisplayTextFromGrid(column); var value = GetCellValue(column); @@ -31,7 +31,7 @@
@if (_isEditMode && !column.ReadOnly) { - @RenderEditableCell(column, dataItem, value, displayText, settingsType) + @RenderEditableCell(column, dataItem, dataItemType, value, displayText, settingsType) } else { @@ -53,30 +53,40 @@
@code { - private string GetColumnCaption(DxGridDataColumn column) + /// + /// Gets the columns to display based on edit mode and ShowReadOnlyFieldsInEditMode setting + /// + private IEnumerable GetVisibleColumns() + { + if (!_isEditMode || ShowReadOnlyFieldsInEditMode) + { + return _allDataColumns; + } + + // In edit mode with ShowReadOnlyFieldsInEditMode=false, hide readonly columns + return _allDataColumns.Where(c => !c.ReadOnly); + } + + private static string GetColumnCaption(DxGridDataColumn column) { return !string.IsNullOrWhiteSpace(column.Caption) ? column.Caption : column.FieldName; } - private string GetCaptionCssClass(bool isReadOnly) + private static string GetCaptionCssClass(bool isReadOnly) { return isReadOnly ? "fw-semibold" : "fw-semibold text-primary"; } - /// - /// Renders an editable cell with two-way binding to the EditModel - /// - private RenderFragment RenderEditableCell(DxGridDataColumn column, TDataItem dataItem, object? value, string displayText, EditSettingsType settingsType) + private RenderFragment RenderEditableCell(DxGridDataColumn column, object dataItem, Type dataItemType, object? value, string displayText, EditSettingsType settingsType) { return builder => { var seq = 0; var fieldName = column.FieldName; - var propertyInfo = typeof(TDataItem).GetProperty(fieldName); - + var propertyInfo = dataItemType.GetProperty(fieldName); + if (propertyInfo == null) { - // Fallback to readonly if property not found RenderCellContent(column, value, displayText, settingsType)(builder); return; } @@ -84,10 +94,10 @@ var propertyType = propertyInfo.PropertyType; var underlyingType = Nullable.GetUnderlyingType(propertyType) ?? propertyType; - // ComboBox columns + // ComboBox if (settingsType == EditSettingsType.ComboBox) { - var comboSettings = GetEditSettings(fieldName) as DxComboBoxSettings; + var comboSettings = GetEditSettings(column.FieldName) as DxComboBoxSettings; if (comboSettings != null) { RenderComboBoxEditor(builder, ref seq, dataItem, propertyInfo, comboSettings); @@ -95,54 +105,29 @@ } } - // Render based on property type + // Render based on type if (underlyingType == typeof(bool)) - { RenderCheckBoxEditor(builder, ref seq, dataItem, propertyInfo); - } else if (underlyingType == typeof(DateTime)) - { RenderDateTimeEditor(builder, ref seq, dataItem, propertyInfo, column.DisplayFormat); - } else if (underlyingType == typeof(DateOnly)) - { RenderDateOnlyEditor(builder, ref seq, dataItem, propertyInfo, column.DisplayFormat); - } - else if (underlyingType == typeof(TimeOnly)) - { - RenderTimeOnlyEditor(builder, ref seq, dataItem, propertyInfo); - } - else if (underlyingType == typeof(TimeSpan)) - { - RenderTimeSpanEditor(builder, ref seq, dataItem, propertyInfo); - } else if (underlyingType == typeof(int)) - { RenderSpinIntEditor(builder, ref seq, dataItem, propertyInfo); - } else if (underlyingType == typeof(decimal)) - { RenderSpinDecimalEditor(builder, ref seq, dataItem, propertyInfo); - } else if (underlyingType == typeof(double)) - { RenderSpinDoubleEditor(builder, ref seq, dataItem, propertyInfo); - } - else if (settingsType == EditSettingsType.Memo || (propertyType == typeof(string) && fieldName.Contains("Comment", StringComparison.OrdinalIgnoreCase))) - { + else if (settingsType == EditSettingsType.Memo) RenderMemoEditor(builder, ref seq, dataItem, propertyInfo); - } else - { RenderTextBoxEditor(builder, ref seq, dataItem, propertyInfo); - } }; } - private void RenderCheckBoxEditor(RenderTreeBuilder builder, ref int seq, TDataItem dataItem, PropertyInfo propertyInfo) + private void RenderCheckBoxEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo) { var currentValue = (bool)(propertyInfo.GetValue(dataItem) ?? false); - builder.OpenComponent>(seq++); builder.AddAttribute(seq++, "Checked", currentValue); builder.AddAttribute(seq++, "CheckedChanged", EventCallback.Factory.Create(this, newValue => @@ -153,7 +138,7 @@ builder.CloseComponent(); } - private void RenderDateTimeEditor(RenderTreeBuilder builder, ref int seq, TDataItem dataItem, PropertyInfo propertyInfo, string? displayFormat) + private void RenderDateTimeEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo, string? displayFormat) { var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null; var currentValue = propertyInfo.GetValue(dataItem); @@ -182,7 +167,7 @@ builder.CloseComponent(); } - private void RenderDateOnlyEditor(RenderTreeBuilder builder, ref int seq, TDataItem dataItem, PropertyInfo propertyInfo, string? displayFormat) + private void RenderDateOnlyEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo, string? displayFormat) { var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null; var currentValue = propertyInfo.GetValue(dataItem); @@ -211,63 +196,7 @@ builder.CloseComponent(); } - private void RenderTimeOnlyEditor(RenderTreeBuilder builder, ref int seq, TDataItem dataItem, PropertyInfo propertyInfo) - { - var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null; - var currentValue = propertyInfo.GetValue(dataItem); - - if (isNullable) - { - builder.OpenComponent>(seq++); - builder.AddAttribute(seq++, "Time", (TimeOnly?)currentValue); - builder.AddAttribute(seq++, "TimeChanged", EventCallback.Factory.Create(this, newValue => - { - propertyInfo.SetValue(dataItem, newValue); - InvokeAsync(StateHasChanged); - })); - } - else - { - builder.OpenComponent>(seq++); - builder.AddAttribute(seq++, "Time", (TimeOnly)(currentValue ?? TimeOnly.MinValue)); - builder.AddAttribute(seq++, "TimeChanged", EventCallback.Factory.Create(this, newValue => - { - propertyInfo.SetValue(dataItem, newValue); - InvokeAsync(StateHasChanged); - })); - } - builder.CloseComponent(); - } - - private void RenderTimeSpanEditor(RenderTreeBuilder builder, ref int seq, TDataItem dataItem, PropertyInfo propertyInfo) - { - var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null; - var currentValue = propertyInfo.GetValue(dataItem); - - if (isNullable) - { - builder.OpenComponent>(seq++); - builder.AddAttribute(seq++, "Time", (TimeSpan?)currentValue); - builder.AddAttribute(seq++, "TimeChanged", EventCallback.Factory.Create(this, newValue => - { - propertyInfo.SetValue(dataItem, newValue); - InvokeAsync(StateHasChanged); - })); - } - else - { - builder.OpenComponent>(seq++); - builder.AddAttribute(seq++, "Time", (TimeSpan)(currentValue ?? TimeSpan.Zero)); - builder.AddAttribute(seq++, "TimeChanged", EventCallback.Factory.Create(this, newValue => - { - propertyInfo.SetValue(dataItem, newValue); - InvokeAsync(StateHasChanged); - })); - } - builder.CloseComponent(); - } - - private void RenderSpinIntEditor(RenderTreeBuilder builder, ref int seq, TDataItem dataItem, PropertyInfo propertyInfo) + private void RenderSpinIntEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo) { var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null; var currentValue = propertyInfo.GetValue(dataItem); @@ -295,7 +224,7 @@ builder.CloseComponent(); } - private void RenderSpinDecimalEditor(RenderTreeBuilder builder, ref int seq, TDataItem dataItem, PropertyInfo propertyInfo) + private void RenderSpinDecimalEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo) { var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null; var currentValue = propertyInfo.GetValue(dataItem); @@ -323,7 +252,7 @@ builder.CloseComponent(); } - private void RenderSpinDoubleEditor(RenderTreeBuilder builder, ref int seq, TDataItem dataItem, PropertyInfo propertyInfo) + private void RenderSpinDoubleEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo) { var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null; var currentValue = propertyInfo.GetValue(dataItem); @@ -351,10 +280,9 @@ builder.CloseComponent(); } - private void RenderTextBoxEditor(RenderTreeBuilder builder, ref int seq, TDataItem dataItem, PropertyInfo propertyInfo) + private void RenderTextBoxEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo) { var currentValue = propertyInfo.GetValue(dataItem)?.ToString() ?? string.Empty; - builder.OpenComponent(seq++); builder.AddAttribute(seq++, "Text", currentValue); builder.AddAttribute(seq++, "TextChanged", EventCallback.Factory.Create(this, newValue => @@ -365,10 +293,9 @@ builder.CloseComponent(); } - private void RenderMemoEditor(RenderTreeBuilder builder, ref int seq, TDataItem dataItem, PropertyInfo propertyInfo) + private void RenderMemoEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo) { var currentValue = propertyInfo.GetValue(dataItem)?.ToString() ?? string.Empty; - builder.OpenComponent(seq++); builder.AddAttribute(seq++, "Text", currentValue); builder.AddAttribute(seq++, "TextChanged", EventCallback.Factory.Create(this, newValue => @@ -380,40 +307,29 @@ builder.CloseComponent(); } - private void RenderComboBoxEditor(RenderTreeBuilder builder, ref int seq, TDataItem dataItem, PropertyInfo propertyInfo, DxComboBoxSettings settings) + private void RenderComboBoxEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo, DxComboBoxSettings settings) { var currentValue = propertyInfo.GetValue(dataItem); var propertyType = propertyInfo.PropertyType; var underlyingType = Nullable.GetUnderlyingType(propertyType) ?? propertyType; - // Determine TData type from settings.Data var dataType = settings.Data?.GetType(); var itemType = typeof(object); - if (dataType != null && dataType.IsGenericType) + if (dataType?.IsGenericType == true) { var genericArgs = dataType.GetGenericArguments(); if (genericArgs.Length > 0) - { itemType = genericArgs[0]; - } } - // Handle common value types for FK fields if (underlyingType == typeof(int)) - { RenderComboBoxInt(builder, ref seq, dataItem, propertyInfo, settings, itemType, currentValue); - } else if (underlyingType == typeof(long)) - { RenderComboBoxLong(builder, ref seq, dataItem, propertyInfo, settings, itemType, currentValue); - } else if (underlyingType == typeof(Guid)) - { RenderComboBoxGuid(builder, ref seq, dataItem, propertyInfo, settings, itemType, currentValue); - } else { - // Fallback: render as TextBox with display text var displayText = ResolveComboBoxDisplayText(settings, currentValue ?? new object()) ?? currentValue?.ToString() ?? string.Empty; builder.OpenComponent(seq++); builder.AddAttribute(seq++, "Text", displayText); @@ -422,12 +338,10 @@ } } - private void RenderComboBoxInt(RenderTreeBuilder builder, ref int seq, TDataItem dataItem, PropertyInfo propertyInfo, DxComboBoxSettings settings, Type itemType, object? currentValue) + private void RenderComboBoxInt(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo, DxComboBoxSettings settings, Type itemType, object? currentValue) { var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null; - - // Create the generic ComboBox type: DxComboBox or DxComboBox - var comboType = isNullable + var comboType = isNullable ? typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(int?)) : typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(int)); @@ -435,7 +349,7 @@ builder.AddAttribute(seq++, "Data", settings.Data); builder.AddAttribute(seq++, "ValueFieldName", settings.ValueFieldName); builder.AddAttribute(seq++, "TextFieldName", settings.TextFieldName); - + if (isNullable) { builder.AddAttribute(seq++, "Value", currentValue as int?); @@ -454,16 +368,14 @@ StateHasChanged(); })); } - builder.AddAttribute(seq++, "ClearButtonDisplayMode", DataEditorClearButtonDisplayMode.Auto); builder.CloseComponent(); } - private void RenderComboBoxLong(RenderTreeBuilder builder, ref int seq, TDataItem dataItem, PropertyInfo propertyInfo, DxComboBoxSettings settings, Type itemType, object? currentValue) + private void RenderComboBoxLong(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo, DxComboBoxSettings settings, Type itemType, object? currentValue) { var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null; - - var comboType = isNullable + var comboType = isNullable ? typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(long?)) : typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(long)); @@ -471,7 +383,7 @@ builder.AddAttribute(seq++, "Data", settings.Data); builder.AddAttribute(seq++, "ValueFieldName", settings.ValueFieldName); builder.AddAttribute(seq++, "TextFieldName", settings.TextFieldName); - + if (isNullable) { builder.AddAttribute(seq++, "Value", currentValue as long?); @@ -490,16 +402,14 @@ StateHasChanged(); })); } - builder.AddAttribute(seq++, "ClearButtonDisplayMode", DataEditorClearButtonDisplayMode.Auto); builder.CloseComponent(); } - private void RenderComboBoxGuid(RenderTreeBuilder builder, ref int seq, TDataItem dataItem, PropertyInfo propertyInfo, DxComboBoxSettings settings, Type itemType, object? currentValue) + private void RenderComboBoxGuid(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo, DxComboBoxSettings settings, Type itemType, object? currentValue) { var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null; - - var comboType = isNullable + var comboType = isNullable ? typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(Guid?)) : typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(Guid)); @@ -507,7 +417,7 @@ builder.AddAttribute(seq++, "Data", settings.Data); builder.AddAttribute(seq++, "ValueFieldName", settings.ValueFieldName); builder.AddAttribute(seq++, "TextFieldName", settings.TextFieldName); - + if (isNullable) { builder.AddAttribute(seq++, "Value", currentValue as Guid?); @@ -526,7 +436,6 @@ StateHasChanged(); })); } - builder.AddAttribute(seq++, "ClearButtonDisplayMode", DataEditorClearButtonDisplayMode.Auto); builder.CloseComponent(); } @@ -537,11 +446,9 @@ { var seq = 0; - // If column has EditSettings, render based on that switch (settingsType) { case EditSettingsType.ComboBox: - // ComboBox columns show resolved display text builder.OpenElement(seq++, "div"); builder.AddAttribute(seq++, "title", displayText); builder.AddAttribute(seq++, "class", "info-panel-text-wrapper"); @@ -559,26 +466,6 @@ builder.CloseComponent(); return; - case EditSettingsType.DateEdit: - RenderDateEditor(builder, ref seq, value, column.DisplayFormat); - return; - - case EditSettingsType.TimeEdit: - RenderTimeEditor(builder, ref seq, value, column.DisplayFormat); - return; - - case EditSettingsType.SpinEdit: - builder.OpenElement(seq++, "div"); - builder.AddAttribute(seq++, "title", displayText); - builder.AddAttribute(seq++, "class", "info-panel-text-wrapper"); - builder.OpenComponent(seq++); - builder.AddAttribute(seq++, "Text", displayText); - builder.AddAttribute(seq++, "ReadOnly", true); - builder.AddAttribute(seq++, "CssClass", "text-end"); - builder.CloseComponent(); - builder.CloseElement(); - return; - case EditSettingsType.Memo: builder.OpenElement(seq++, "div"); builder.AddAttribute(seq++, "title", displayText); @@ -591,7 +478,7 @@ return; } - // Default: render based on value type + // Default by value type switch (value) { case bool boolValue: @@ -617,20 +504,6 @@ builder.CloseComponent(); break; - case TimeOnly timeOnlyValue: - builder.OpenComponent>(seq++); - builder.AddAttribute(seq++, "Time", timeOnlyValue); - builder.AddAttribute(seq++, "ReadOnly", true); - builder.CloseComponent(); - break; - - case TimeSpan timeSpanValue: - builder.OpenComponent>(seq++); - builder.AddAttribute(seq++, "Time", timeSpanValue); - builder.AddAttribute(seq++, "ReadOnly", true); - builder.CloseComponent(); - break; - case decimal or double or float or int or long or short: builder.OpenElement(seq++, "div"); builder.AddAttribute(seq++, "title", displayText); @@ -656,69 +529,4 @@ } }; } - - private void RenderDateEditor(RenderTreeBuilder builder, ref int seq, object? value, string? displayFormat) - { - switch (value) - { - case DateTime dateTime: - builder.OpenComponent>(seq++); - builder.AddAttribute(seq++, "Date", dateTime); - builder.AddAttribute(seq++, "ReadOnly", true); - builder.AddAttribute(seq++, "DisplayFormat", displayFormat ?? "yyyy-MM-dd HH:mm"); - builder.CloseComponent(); - break; - case DateOnly dateOnly: - builder.OpenComponent>(seq++); - builder.AddAttribute(seq++, "Date", dateOnly); - builder.AddAttribute(seq++, "ReadOnly", true); - builder.AddAttribute(seq++, "DisplayFormat", displayFormat ?? "yyyy-MM-dd"); - builder.CloseComponent(); - break; - case DateTimeOffset dateTimeOffset: - builder.OpenComponent>(seq++); - builder.AddAttribute(seq++, "Date", dateTimeOffset); - builder.AddAttribute(seq++, "ReadOnly", true); - builder.AddAttribute(seq++, "DisplayFormat", displayFormat ?? "yyyy-MM-dd HH:mm"); - builder.CloseComponent(); - break; - default: - builder.OpenComponent(seq++); - builder.AddAttribute(seq++, "Text", value?.ToString() ?? string.Empty); - builder.AddAttribute(seq++, "ReadOnly", true); - builder.CloseComponent(); - break; - } - } - - private void RenderTimeEditor(RenderTreeBuilder builder, ref int seq, object? value, string? displayFormat) - { - switch (value) - { - case TimeOnly timeOnly: - builder.OpenComponent>(seq++); - builder.AddAttribute(seq++, "Time", timeOnly); - builder.AddAttribute(seq++, "ReadOnly", true); - builder.CloseComponent(); - break; - case TimeSpan timeSpan: - builder.OpenComponent>(seq++); - builder.AddAttribute(seq++, "Time", timeSpan); - builder.AddAttribute(seq++, "ReadOnly", true); - builder.CloseComponent(); - break; - case DateTime dateTime: - builder.OpenComponent>(seq++); - builder.AddAttribute(seq++, "Time", dateTime); - builder.AddAttribute(seq++, "ReadOnly", true); - builder.CloseComponent(); - break; - default: - builder.OpenComponent(seq++); - builder.AddAttribute(seq++, "Text", value?.ToString() ?? string.Empty); - builder.AddAttribute(seq++, "ReadOnly", true); - builder.CloseComponent(); - break; - } - } } diff --git a/AyCode.Blazor.Components/Components/Grids/MgGridInfoPanel.razor.cs b/AyCode.Blazor.Components/Components/Grids/MgGridInfoPanel.razor.cs index fbe17ce..20cb506 100644 --- a/AyCode.Blazor.Components/Components/Grids/MgGridInfoPanel.razor.cs +++ b/AyCode.Blazor.Components/Components/Grids/MgGridInfoPanel.razor.cs @@ -4,23 +4,40 @@ using Microsoft.JSInterop; namespace AyCode.Blazor.Components.Components.Grids; -public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable where TDataItem : class +/// +/// Interface for InfoPanel to support grid access +/// +public interface IInfoPanelBase +{ + void ClearEditMode(); + void SetEditMode(object editModel); + void RefreshData(DxGrid grid, object? dataItem, int visibleIndex = -1); +} + +/// +/// Non-generic version of the InfoPanel component +/// +public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPanelBase { [Inject] private IJSRuntime JSRuntime { get; set; } = null!; + /// + /// Whether to show readonly fields when in edit mode. Default is false. + /// + [Parameter] public bool ShowReadOnlyFieldsInEditMode { get; set; } = false; + private ElementReference _panelElement; - private IJSObjectReference? _jsModule; private bool _isJsInitialized; private const int DefaultTopOffset = 300; // Increased from 180 to account for header + tabs + toolbar private DxGrid? _currentGrid; - private TDataItem? _currentDataItem; + private object? _currentDataItem; private int _focusedRowVisibleIndex = -1; private List _allDataColumns = []; // Edit mode state private bool _isEditMode; - private TDataItem? _editModel; + private object? _editModel; // Cache for edit settings to avoid repeated lookups private readonly Dictionary _editSettingsCache = []; @@ -52,39 +69,46 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposabl /// /// Refreshes the InfoPanel with data from the specified grid row (view mode) /// - public void RefreshData(DxGrid grid, TDataItem? dataItem, int visibleIndex = -1) + public void RefreshData(DxGrid grid, object? dataItem, int visibleIndex = -1) { ArgumentNullException.ThrowIfNull(grid); + System.Diagnostics.Debug.WriteLine($"[InfoPanel] RefreshData called - dataItem type: {dataItem?.GetType().Name ?? "null"}, visibleIndex: {visibleIndex}"); + _currentGrid = grid; _currentDataItem = dataItem; _focusedRowVisibleIndex = visibleIndex; _editSettingsCache.Clear(); + 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) { _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(); + System.Diagnostics.Debug.WriteLine($"[InfoPanel] RefreshData - StateHasChanged called"); } /// /// Sets the InfoPanel to edit mode with the given edit model /// - public void SetEditMode(TDataItem editModel) + public void SetEditMode(object editModel) { _editModel = editModel; _isEditMode = true; - _currentDataItem = editModel; + _currentDataItem = _editModel; if (_currentGrid != null) { @@ -101,6 +125,7 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposabl { _isEditMode = false; _editModel = null; + _editSettingsCache.Clear(); StateHasChanged(); } @@ -132,17 +157,12 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposabl // Ignore disposal errors } } - - if (_jsModule != null) - { - await _jsModule.DisposeAsync(); - } } /// /// Gets the data item to display/edit (EditModel in edit mode, otherwise CurrentDataItem) /// - private TDataItem? GetActiveDataItem() => _isEditMode && _editModel != null ? _editModel : _currentDataItem; + private object? GetActiveDataItem() => _isEditMode && _editModel != null ? _editModel : _currentDataItem; /// /// Gets the display text for a field using the grid's internal formatting. @@ -163,9 +183,9 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposabl // Try to resolve display text from EditSettings var editSettings = GetEditSettings(column.FieldName); - if (editSettings != null) + if (editSettings is DxComboBoxSettings comboSettings) { - var displayText = ResolveEditSettingsDisplayText(editSettings, value); + var displayText = ResolveComboBoxDisplayText(comboSettings, value); if (!string.IsNullOrEmpty(displayText)) return displayText; } @@ -206,15 +226,13 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposabl try { // Try each EditSettings type - settings = TryGetEditSettings(fieldName) - ?? TryGetEditSettings(fieldName) - ?? TryGetEditSettings(fieldName) - ?? TryGetEditSettings(fieldName) - ?? TryGetEditSettings(fieldName) - ?? TryGetEditSettings(fieldName) - ?? TryGetEditSettings(fieldName) - ?? TryGetEditSettings(fieldName) - ?? (IEditSettings?)TryGetEditSettings(fieldName); + settings = _currentGrid.GetColumnEditSettings(fieldName) + ?? _currentGrid.GetColumnEditSettings(fieldName) + ?? _currentGrid.GetColumnEditSettings(fieldName) + ?? _currentGrid.GetColumnEditSettings(fieldName) + ?? _currentGrid.GetColumnEditSettings(fieldName) + ?? _currentGrid.GetColumnEditSettings(fieldName) + ?? (IEditSettings?)_currentGrid.GetColumnEditSettings(fieldName); } catch { @@ -225,33 +243,6 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposabl return settings; } - private T? TryGetEditSettings(string fieldName) where T : class, IEditSettings - { - try - { - return _currentGrid?.GetColumnEditSettings(fieldName); - } - catch - { - return null; - } - } - - /// - /// Resolves display text based on EditSettings type - /// - private string? ResolveEditSettingsDisplayText(IEditSettings settings, object value) - { - return settings switch - { - DxComboBoxSettings comboSettings => ResolveComboBoxDisplayText(comboSettings, value), - _ => null - }; - } - - /// - /// Resolves the display text from a ComboBox data source - /// private string? ResolveComboBoxDisplayText(DxComboBoxSettings settings, object value) { if (settings.Data == null || string.IsNullOrEmpty(settings.ValueFieldName) || string.IsNullOrEmpty(settings.TextFieldName)) @@ -284,7 +275,7 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposabl return null; } - private string FormatValue(object? value) + private static string FormatValue(object? value) { if (value == null) return string.Empty; @@ -304,13 +295,16 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposabl }; } - private List GetAllDataColumns(DxGrid grid) + private static List GetAllDataColumns(DxGrid grid) { var columns = new List(); try { var allColumns = grid.GetDataColumns(); + + System.Diagnostics.Debug.WriteLine($"[InfoPanel] GetAllDataColumns - grid type: {grid.GetType().Name}, columns count: {allColumns?.Count() ?? 0}"); + if (allColumns != null) { foreach (var column in allColumns) @@ -319,15 +313,17 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposabl !string.IsNullOrWhiteSpace(dataColumn.FieldName)) { columns.Add(dataColumn); + System.Diagnostics.Debug.WriteLine($"[InfoPanel] - Column: {dataColumn.FieldName}"); } } } } - catch (Exception) + catch (Exception ex) { - // Fallback: empty list if GetDataColumns fails + System.Diagnostics.Debug.WriteLine($"[InfoPanel] GetAllDataColumns error: {ex.Message}"); } + System.Diagnostics.Debug.WriteLine($"[InfoPanel] GetAllDataColumns result: {columns.Count} columns"); return columns; } diff --git a/AyCode.Blazor.Components/Components/Grids/MgGridInfoPanel.razor.css b/AyCode.Blazor.Components/Components/Grids/MgGridInfoPanel.razor.css index 4d63be8..01adbaf 100644 --- a/AyCode.Blazor.Components/Components/Grids/MgGridInfoPanel.razor.css +++ b/AyCode.Blazor.Components/Components/Grids/MgGridInfoPanel.razor.css @@ -6,6 +6,7 @@ /* Breakpoint configuration - CHANGE ONLY THESE VALUES */ /* 2 column breakpoint: 500px */ /* 3 column breakpoint: 800px */ +/* 4 column breakpoint: 1200px (for 1920px+ screens) */ /* Main panel - contained within splitter pane */ .mg-grid-info-panel { @@ -61,13 +62,20 @@ } } -/* 3 columns for wider panels (>= 800px) */ -@container infopanel (min-width: 800px) { +/* 3 columns for wider panels (800px - 1199px) */ +@container infopanel (min-width: 800px) and (max-width: 1199px) { .mg-info-panel-grid { grid-template-columns: repeat(3, 1fr); } } +/* 4 columns for very wide panels (>= 1200px, typically on 1920px+ screens) */ +@container infopanel (min-width: 1200px) { + .mg-info-panel-grid { + grid-template-columns: repeat(4, 1fr); + } +} + .mg-info-panel-item { min-width: 0; /* Prevent grid blowout */ }