+ @if (GetActiveDataItem() != null && _currentGrid != null)
{
- var colSpan = _allDataColumns.Count > 10 ? 6 : 12;
+ var colSpan = _allDataColumns.Count > 10 ? 6 : 12;
+ var dataItem = GetActiveDataItem()!;
-
@foreach (var column in _allDataColumns)
{
+ var displayText = GetDisplayTextFromGrid(column);
+ var value = GetCellValue(column);
+ var settingsType = GetEditSettingsType(column);
+ var isReadOnly = !_isEditMode || column.ReadOnly;
+
+ ColSpanXs="@colSpan">
+
+ @if (_isEditMode && !column.ReadOnly)
+ {
+ @RenderEditableCell(column, dataItem, value, displayText, settingsType)
+ }
+ else
+ {
+ @RenderCellContent(column, value, displayText, settingsType)
+ }
+
+
}
}
@@ -39,4 +54,649 @@
{
return !string.IsNullOrWhiteSpace(column.Caption) ? column.Caption : column.FieldName;
}
+
+ private 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)
+ {
+ return builder =>
+ {
+ var seq = 0;
+ var fieldName = column.FieldName;
+ var propertyInfo = typeof(TDataItem).GetProperty(fieldName);
+
+ if (propertyInfo == null)
+ {
+ // Fallback to readonly if property not found
+ RenderCellContent(column, value, displayText, settingsType)(builder);
+ return;
+ }
+
+ var propertyType = propertyInfo.PropertyType;
+ var underlyingType = Nullable.GetUnderlyingType(propertyType) ?? propertyType;
+
+ // ComboBox columns
+ if (settingsType == EditSettingsType.ComboBox)
+ {
+ var comboSettings = GetEditSettings(fieldName) as DxComboBoxSettings;
+ if (comboSettings != null)
+ {
+ RenderComboBoxEditor(builder, ref seq, dataItem, propertyInfo, comboSettings);
+ return;
+ }
+ }
+
+ // Render based on property 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)))
+ {
+ 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)
+ {
+ var currentValue = (bool)(propertyInfo.GetValue(dataItem) ?? false);
+
+ builder.OpenComponent
>(seq++);
+ builder.AddAttribute(seq++, "Checked", currentValue);
+ builder.AddAttribute(seq++, "CheckedChanged", EventCallback.Factory.Create(this, newValue =>
+ {
+ propertyInfo.SetValue(dataItem, newValue);
+ InvokeAsync(StateHasChanged);
+ }));
+ builder.CloseComponent();
+ }
+
+ private void RenderDateTimeEditor(RenderTreeBuilder builder, ref int seq, TDataItem dataItem, PropertyInfo propertyInfo, string? displayFormat)
+ {
+ var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
+ var currentValue = propertyInfo.GetValue(dataItem);
+
+ if (isNullable)
+ {
+ builder.OpenComponent>(seq++);
+ builder.AddAttribute(seq++, "Date", (DateTime?)currentValue);
+ builder.AddAttribute(seq++, "DateChanged", EventCallback.Factory.Create(this, newValue =>
+ {
+ propertyInfo.SetValue(dataItem, newValue);
+ InvokeAsync(StateHasChanged);
+ }));
+ }
+ else
+ {
+ builder.OpenComponent>(seq++);
+ builder.AddAttribute(seq++, "Date", (DateTime)(currentValue ?? DateTime.MinValue));
+ builder.AddAttribute(seq++, "DateChanged", EventCallback.Factory.Create(this, newValue =>
+ {
+ propertyInfo.SetValue(dataItem, newValue);
+ InvokeAsync(StateHasChanged);
+ }));
+ }
+ builder.AddAttribute(seq++, "DisplayFormat", displayFormat ?? "yyyy-MM-dd HH:mm");
+ builder.CloseComponent();
+ }
+
+ private void RenderDateOnlyEditor(RenderTreeBuilder builder, ref int seq, TDataItem dataItem, PropertyInfo propertyInfo, string? displayFormat)
+ {
+ var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
+ var currentValue = propertyInfo.GetValue(dataItem);
+
+ if (isNullable)
+ {
+ builder.OpenComponent>(seq++);
+ builder.AddAttribute(seq++, "Date", (DateOnly?)currentValue);
+ builder.AddAttribute(seq++, "DateChanged", EventCallback.Factory.Create(this, newValue =>
+ {
+ propertyInfo.SetValue(dataItem, newValue);
+ InvokeAsync(StateHasChanged);
+ }));
+ }
+ else
+ {
+ builder.OpenComponent>(seq++);
+ builder.AddAttribute(seq++, "Date", (DateOnly)(currentValue ?? DateOnly.MinValue));
+ builder.AddAttribute(seq++, "DateChanged", EventCallback.Factory.Create(this, newValue =>
+ {
+ propertyInfo.SetValue(dataItem, newValue);
+ InvokeAsync(StateHasChanged);
+ }));
+ }
+ builder.AddAttribute(seq++, "DisplayFormat", displayFormat ?? "yyyy-MM-dd");
+ 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)
+ {
+ var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
+ var currentValue = propertyInfo.GetValue(dataItem);
+
+ if (isNullable)
+ {
+ builder.OpenComponent>(seq++);
+ builder.AddAttribute(seq++, "Value", (int?)currentValue);
+ builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create(this, newValue =>
+ {
+ propertyInfo.SetValue(dataItem, newValue);
+ InvokeAsync(StateHasChanged);
+ }));
+ }
+ else
+ {
+ builder.OpenComponent>(seq++);
+ builder.AddAttribute(seq++, "Value", (int)(currentValue ?? 0));
+ builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create(this, newValue =>
+ {
+ propertyInfo.SetValue(dataItem, newValue);
+ InvokeAsync(StateHasChanged);
+ }));
+ }
+ builder.CloseComponent();
+ }
+
+ private void RenderSpinDecimalEditor(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++, "Value", (decimal?)currentValue);
+ builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create(this, newValue =>
+ {
+ propertyInfo.SetValue(dataItem, newValue);
+ InvokeAsync(StateHasChanged);
+ }));
+ }
+ else
+ {
+ builder.OpenComponent>(seq++);
+ builder.AddAttribute(seq++, "Value", (decimal)(currentValue ?? 0m));
+ builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create(this, newValue =>
+ {
+ propertyInfo.SetValue(dataItem, newValue);
+ InvokeAsync(StateHasChanged);
+ }));
+ }
+ builder.CloseComponent();
+ }
+
+ private void RenderSpinDoubleEditor(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++, "Value", (double?)currentValue);
+ builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create(this, newValue =>
+ {
+ propertyInfo.SetValue(dataItem, newValue);
+ InvokeAsync(StateHasChanged);
+ }));
+ }
+ else
+ {
+ builder.OpenComponent>(seq++);
+ builder.AddAttribute(seq++, "Value", (double)(currentValue ?? 0d));
+ builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create(this, newValue =>
+ {
+ propertyInfo.SetValue(dataItem, newValue);
+ InvokeAsync(StateHasChanged);
+ }));
+ }
+ builder.CloseComponent();
+ }
+
+ private void RenderTextBoxEditor(RenderTreeBuilder builder, ref int seq, TDataItem 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 =>
+ {
+ propertyInfo.SetValue(dataItem, newValue);
+ InvokeAsync(StateHasChanged);
+ }));
+ builder.CloseComponent();
+ }
+
+ private void RenderMemoEditor(RenderTreeBuilder builder, ref int seq, TDataItem 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 =>
+ {
+ propertyInfo.SetValue(dataItem, newValue);
+ InvokeAsync(StateHasChanged);
+ }));
+ builder.AddAttribute(seq++, "Rows", 3);
+ builder.CloseComponent();
+ }
+
+ private void RenderComboBoxEditor(RenderTreeBuilder builder, ref int seq, TDataItem 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)
+ {
+ 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);
+ builder.AddAttribute(seq++, "ReadOnly", true);
+ builder.CloseComponent();
+ }
+ }
+
+ private void RenderComboBoxInt(RenderTreeBuilder builder, ref int seq, TDataItem 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
+ ? typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(int?))
+ : typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(int));
+
+ builder.OpenComponent(seq++, comboType);
+ 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?);
+ builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create(this, newValue =>
+ {
+ propertyInfo.SetValue(dataItem, newValue);
+ StateHasChanged();
+ }));
+ }
+ else
+ {
+ builder.AddAttribute(seq++, "Value", currentValue is int intVal ? intVal : 0);
+ builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create(this, newValue =>
+ {
+ propertyInfo.SetValue(dataItem, newValue);
+ 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)
+ {
+ var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
+
+ var comboType = isNullable
+ ? typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(long?))
+ : typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(long));
+
+ builder.OpenComponent(seq++, comboType);
+ 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?);
+ builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create(this, newValue =>
+ {
+ propertyInfo.SetValue(dataItem, newValue);
+ StateHasChanged();
+ }));
+ }
+ else
+ {
+ builder.AddAttribute(seq++, "Value", currentValue is long longVal ? longVal : 0L);
+ builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create(this, newValue =>
+ {
+ propertyInfo.SetValue(dataItem, newValue);
+ 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)
+ {
+ var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
+
+ var comboType = isNullable
+ ? typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(Guid?))
+ : typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(Guid));
+
+ builder.OpenComponent(seq++, comboType);
+ 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?);
+ builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create(this, newValue =>
+ {
+ propertyInfo.SetValue(dataItem, newValue);
+ StateHasChanged();
+ }));
+ }
+ else
+ {
+ builder.AddAttribute(seq++, "Value", currentValue is Guid guidVal ? guidVal : Guid.Empty);
+ builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create(this, newValue =>
+ {
+ propertyInfo.SetValue(dataItem, newValue);
+ StateHasChanged();
+ }));
+ }
+
+ builder.AddAttribute(seq++, "ClearButtonDisplayMode", DataEditorClearButtonDisplayMode.Auto);
+ builder.CloseComponent();
+ }
+
+ private RenderFragment RenderCellContent(DxGridDataColumn column, object? value, string displayText, EditSettingsType settingsType)
+ {
+ return builder =>
+ {
+ var seq = 0;
+
+ // If column has EditSettings, render based on that
+ switch (settingsType)
+ {
+ case EditSettingsType.ComboBox:
+ // ComboBox columns show resolved display text
+ builder.OpenComponent(seq++);
+ builder.AddAttribute(seq++, "Text", displayText);
+ builder.AddAttribute(seq++, "ReadOnly", true);
+ builder.CloseComponent();
+ return;
+
+ case EditSettingsType.CheckBox when value is bool boolVal:
+ builder.OpenComponent>(seq++);
+ builder.AddAttribute(seq++, "Checked", boolVal);
+ builder.AddAttribute(seq++, "ReadOnly", true);
+ 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.OpenComponent(seq++);
+ builder.AddAttribute(seq++, "Text", displayText);
+ builder.AddAttribute(seq++, "ReadOnly", true);
+ builder.AddAttribute(seq++, "CssClass", "text-end");
+ builder.CloseComponent();
+ return;
+
+ case EditSettingsType.Memo:
+ builder.OpenComponent(seq++);
+ builder.AddAttribute(seq++, "Text", displayText);
+ builder.AddAttribute(seq++, "ReadOnly", true);
+ builder.AddAttribute(seq++, "Rows", 3);
+ builder.CloseComponent();
+ return;
+ }
+
+ // Default: render based on value type
+ switch (value)
+ {
+ case bool boolValue:
+ builder.OpenComponent>(seq++);
+ builder.AddAttribute(seq++, "Checked", boolValue);
+ builder.AddAttribute(seq++, "ReadOnly", true);
+ builder.CloseComponent();
+ break;
+
+ case DateTime dateValue:
+ builder.OpenComponent>(seq++);
+ builder.AddAttribute(seq++, "Date", dateValue);
+ builder.AddAttribute(seq++, "ReadOnly", true);
+ builder.AddAttribute(seq++, "DisplayFormat", column.DisplayFormat ?? "yyyy-MM-dd HH:mm");
+ builder.CloseComponent();
+ break;
+
+ case DateOnly dateOnlyValue:
+ builder.OpenComponent>(seq++);
+ builder.AddAttribute(seq++, "Date", dateOnlyValue);
+ builder.AddAttribute(seq++, "ReadOnly", true);
+ builder.AddAttribute(seq++, "DisplayFormat", column.DisplayFormat ?? "yyyy-MM-dd");
+ 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.OpenComponent(seq++);
+ builder.AddAttribute(seq++, "Text", displayText);
+ builder.AddAttribute(seq++, "ReadOnly", true);
+ builder.AddAttribute(seq++, "CssClass", "text-end");
+ builder.CloseComponent();
+ break;
+
+ default:
+ builder.OpenComponent(seq++);
+ builder.AddAttribute(seq++, "Text", displayText);
+ builder.AddAttribute(seq++, "ReadOnly", true);
+ builder.CloseComponent();
+ break;
+ }
+ };
+ }
+
+ 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 f75d4dc..f16eb24 100644
--- a/AyCode.Blazor.Components/Components/Grids/MgGridInfoPanel.razor.cs
+++ b/AyCode.Blazor.Components/Components/Grids/MgGridInfoPanel.razor.cs
@@ -1,6 +1,5 @@
using DevExpress.Blazor;
using Microsoft.AspNetCore.Components;
-using System.Reflection;
namespace AyCode.Blazor.Components.Components.Grids;
@@ -8,41 +7,249 @@ public partial class MgGridInfoPanel : ComponentBase where TDataItem
{
private DxGrid? _currentGrid;
private TDataItem? _currentDataItem;
+ private int _focusedRowVisibleIndex = -1;
private List _allDataColumns = [];
+ // Edit mode state
+ private bool _isEditMode;
+ private TDataItem? _editModel;
+
+ // Cache for edit settings to avoid repeated lookups
+ private readonly Dictionary _editSettingsCache = [];
+
///
- /// Refreshes the InfoPanel with data from the specified grid row
+ /// Refreshes the InfoPanel with data from the specified grid row (view mode)
///
- /// The grid instance
- /// The data item from the focused row
- public void RefreshData(DxGrid grid, TDataItem? dataItem)
+ public void RefreshData(DxGrid grid, TDataItem? dataItem, int visibleIndex = -1)
{
ArgumentNullException.ThrowIfNull(grid);
_currentGrid = grid;
_currentDataItem = dataItem;
-
- if (_currentGrid != null && _currentDataItem != null)
+ _focusedRowVisibleIndex = visibleIndex;
+ _editSettingsCache.Clear();
+
+ // Ha nem vagyunk edit módban, frissítjük az oszlopokat
+ if (!_isEditMode)
{
- _allDataColumns = GetAllDataColumns(_currentGrid);
- }
- else
- {
- _allDataColumns = [];
+ if (_currentGrid != null && _currentDataItem != null)
+ {
+ _allDataColumns = GetAllDataColumns(_currentGrid);
+ }
+ else
+ {
+ _allDataColumns = [];
+ }
}
- InvokeAsync(StateHasChanged);
+ StateHasChanged();
}
///
- /// Clears the InfoPanel
+ /// Sets the InfoPanel to edit mode with the given edit model
+ ///
+ public void SetEditMode(TDataItem editModel)
+ {
+ _editModel = editModel;
+ _isEditMode = true;
+ _currentDataItem = editModel;
+
+ if (_currentGrid != null)
+ {
+ _allDataColumns = GetAllDataColumns(_currentGrid);
+ }
+
+ StateHasChanged();
+ }
+
+ ///
+ /// Clears edit mode and returns to view mode
+ ///
+ public void ClearEditMode()
+ {
+ _isEditMode = false;
+ _editModel = null;
+ StateHasChanged();
+ }
+
+ ///
+ /// Clears the InfoPanel completely
///
public void Clear()
{
_currentGrid = null;
_currentDataItem = null;
+ _focusedRowVisibleIndex = -1;
_allDataColumns = [];
- InvokeAsync(StateHasChanged);
+ _editSettingsCache.Clear();
+ _isEditMode = false;
+ _editModel = null;
+ StateHasChanged();
+ }
+
+ ///
+ /// Gets the data item to display/edit (EditModel in edit mode, otherwise CurrentDataItem)
+ ///
+ private TDataItem? GetActiveDataItem() => _isEditMode && _editModel != null ? _editModel : _currentDataItem;
+
+ ///
+ /// 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.
+ ///
+ private string GetDisplayTextFromGrid(DxGridDataColumn column)
+ {
+ var dataItem = GetActiveDataItem();
+ if (_currentGrid == null || dataItem == null || string.IsNullOrWhiteSpace(column.FieldName))
+ return string.Empty;
+
+ try
+ {
+ var value = _currentGrid.GetDataItemValue(dataItem, column.FieldName);
+
+ if (value == null)
+ return string.Empty;
+
+ // Try to resolve display text from EditSettings
+ var editSettings = GetEditSettings(column.FieldName);
+ if (editSettings != null)
+ {
+ var displayText = ResolveEditSettingsDisplayText(editSettings, value);
+ if (!string.IsNullOrEmpty(displayText))
+ return displayText;
+ }
+
+ // Apply column's DisplayFormat if available
+ if (!string.IsNullOrEmpty(column.DisplayFormat))
+ {
+ try
+ {
+ return string.Format(column.DisplayFormat, value);
+ }
+ catch
+ {
+ // If format fails, fall through to default formatting
+ }
+ }
+
+ return FormatValue(value);
+ }
+ catch
+ {
+ return string.Empty;
+ }
+ }
+
+ ///
+ /// Gets edit settings for the specified field (with caching)
+ ///
+ private IEditSettings? GetEditSettings(string fieldName)
+ {
+ if (_currentGrid == null || string.IsNullOrEmpty(fieldName))
+ return null;
+
+ if (_editSettingsCache.TryGetValue(fieldName, out var cached))
+ return cached;
+
+ IEditSettings? settings = null;
+ 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);
+ }
+ catch
+ {
+ // Ignore errors
+ }
+
+ _editSettingsCache[fieldName] = settings;
+ 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))
+ return null;
+
+ try
+ {
+ foreach (var item in (System.Collections.IEnumerable)settings.Data)
+ {
+ if (item == null) continue;
+
+ var itemType = item.GetType();
+ var valueProperty = itemType.GetProperty(settings.ValueFieldName);
+ var textProperty = itemType.GetProperty(settings.TextFieldName);
+
+ if (valueProperty == null || textProperty == null) continue;
+
+ var itemValue = valueProperty.GetValue(item);
+ if (itemValue != null && itemValue.Equals(value))
+ {
+ return textProperty.GetValue(item)?.ToString();
+ }
+ }
+ }
+ catch
+ {
+ // If lookup fails, return null and fall back to default formatting
+ }
+
+ return null;
+ }
+
+ private string FormatValue(object? value)
+ {
+ if (value == null)
+ return string.Empty;
+
+ return value switch
+ {
+ DateTime dateTime => dateTime.ToString("yyyy-MM-dd HH:mm:ss"),
+ DateOnly dateOnly => dateOnly.ToString("yyyy-MM-dd"),
+ TimeOnly timeOnly => timeOnly.ToString("HH:mm:ss"),
+ TimeSpan timeSpan => timeSpan.ToString(@"hh\:mm\:ss"),
+ bool boolValue => boolValue ? "Igen" : "Nem",
+ decimal decValue => decValue.ToString("N2"),
+ double dblValue => dblValue.ToString("N2"),
+ float fltValue => fltValue.ToString("N2"),
+ int or long or short or byte => string.Format("{0:N0}", value),
+ _ => value.ToString() ?? string.Empty
+ };
}
private List GetAllDataColumns(DxGrid grid)
@@ -56,8 +263,6 @@ public partial class MgGridInfoPanel : ComponentBase where TDataItem
{
foreach (var column in allColumns)
{
- // Minden DxGridDataColumn-t felveszünk, ha van FieldName-je
- // NEM vizsgáljuk a Visible property-t!
if (column is DxGridDataColumn dataColumn &&
!string.IsNullOrWhiteSpace(dataColumn.FieldName))
{
@@ -76,28 +281,13 @@ public partial class MgGridInfoPanel : ComponentBase where TDataItem
private object? GetCellValue(DxGridDataColumn column)
{
- if (_currentDataItem == null || string.IsNullOrWhiteSpace(column.FieldName))
+ var dataItem = GetActiveDataItem();
+ if (_currentGrid == null || dataItem == null || string.IsNullOrWhiteSpace(column.FieldName))
return null;
try
{
- var fieldName = column.FieldName;
- var properties = fieldName.Split('.');
- object? currentValue = _currentDataItem;
-
- foreach (var propertyName in properties)
- {
- if (currentValue == null)
- break;
-
- var propertyInfo = currentValue.GetType().GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);
- if (propertyInfo == null)
- return null;
-
- currentValue = propertyInfo.GetValue(currentValue);
- }
-
- return currentValue;
+ return _currentGrid.GetDataItemValue(dataItem, column.FieldName);
}
catch
{
@@ -105,31 +295,33 @@ public partial class MgGridInfoPanel : ComponentBase where TDataItem
}
}
- private string GetDisplayText(DxGridDataColumn column, object? value)
+ ///
+ /// Gets the EditSettings type for rendering logic
+ ///
+ private EditSettingsType GetEditSettingsType(DxGridDataColumn column)
{
- if (value == null)
- return string.Empty;
-
- if (value is DateTime dateTime)
+ var settings = GetEditSettings(column.FieldName);
+
+ return settings switch
{
- return dateTime.ToString("yyyy-MM-dd HH:mm:ss");
- }
+ DxComboBoxSettings => EditSettingsType.ComboBox,
+ DxDateEditSettings => EditSettingsType.DateEdit,
+ DxTimeEditSettings => EditSettingsType.TimeEdit,
+ DxSpinEditSettings => EditSettingsType.SpinEdit,
+ DxCheckBoxSettings => EditSettingsType.CheckBox,
+ DxMemoSettings => EditSettingsType.Memo,
+ _ => EditSettingsType.None
+ };
+ }
- if (value is bool boolValue)
- {
- return boolValue ? "Igen" : "Nem";
- }
-
- if (value is decimal || value is double || value is float)
- {
- return string.Format("{0:N2}", value);
- }
-
- if (value is int || value is long || value is short || value is byte)
- {
- return string.Format("{0:N0}", value);
- }
-
- return value.ToString() ?? string.Empty;
+ private enum EditSettingsType
+ {
+ None,
+ ComboBox,
+ DateEdit,
+ TimeEdit,
+ SpinEdit,
+ CheckBox,
+ Memo
}
}
diff --git a/AyCode.Blazor.Components/Components/Grids/MgGridInfoPanel.razor.css b/AyCode.Blazor.Components/Components/Grids/MgGridInfoPanel.razor.css
index fd262fb..73ec890 100644
--- a/AyCode.Blazor.Components/Components/Grids/MgGridInfoPanel.razor.css
+++ b/AyCode.Blazor.Components/Components/Grids/MgGridInfoPanel.razor.css
@@ -2,7 +2,18 @@
height: 100%;
overflow-y: auto;
padding: 1rem;
+ background-color: var(--dxbl-bg-secondary, #f8f9fa);
+ transition: background-color 0.3s ease, border-color 0.3s ease;
+}
+
+.mg-grid-info-panel.edit-mode {
+ background-color: #fffbeb;
+ border-left: 3px solid #f59e0b;
+}
+
+.mg-grid-info-panel.view-mode {
background-color: #f8f9fa;
+ border-left: 3px solid transparent;
}
.info-panel-form {
@@ -11,16 +22,20 @@
.info-panel-form .fw-semibold {
font-weight: 600;
- color: #495057;
+ color: var(--dxbl-text-secondary, #495057);
font-size: 0.875rem;
}
+.info-panel-form .fw-semibold.text-primary {
+ color: var(--dxbl-primary, #0d6efd);
+}
+
.info-panel-empty {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
- color: #6c757d;
+ color: var(--dxbl-text-secondary, #6c757d);
font-style: italic;
}