Add dynamic Info Panel to grids and update app splash image

Introduced a dynamic Info Panel to MgGridBase, allowing users to view details of the selected row in a side panel. Added the MgGridInfoPanel component with automatic form generation and styling. Updated all grid usages to use the new OnGridFocusedRowChanged event and enabled the info panel in GridPartner. Changed app logo and splash screen references in the Windows packaging manifest and added a placeholder splash image. Also included minor using fixes.
This commit is contained in:
Loretta 2025-12-16 16:12:38 +01:00
parent 920bc299aa
commit c1cf30b8f0
4 changed files with 355 additions and 1 deletions

View File

@ -8,8 +8,10 @@ using AyCode.Services.SignalRs;
using AyCode.Utils.Extensions; using AyCode.Utils.Extensions;
using DevExpress.Blazor; using DevExpress.Blazor;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
using System.ComponentModel; using System.ComponentModel;
using System.Reflection; using System.Reflection;
using DevExpress.Blazor.Internal;
namespace AyCode.Blazor.Components.Components.Grids; namespace AyCode.Blazor.Components.Components.Grids;
@ -45,14 +47,141 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
/// <inheritdoc /> /// <inheritdoc />
public bool IsSyncing => _dataSource?.IsSyncing ?? false; public bool IsSyncing => _dataSource?.IsSyncing ?? false;
/// <inheritdoc /> /// <inheritdoc />
public event Action<bool>? OnSyncingStateChanged; public event Action<bool>? OnSyncingStateChanged;
[Parameter] public bool ShowInfoPanel { get; set; } = true;
private object _focusedDataItem;
private MgGridInfoPanel<TDataItem>? _infoPanelInstance;
public MgGridBase() : base() 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)
{
if (!ShowInfoPanel)
{
base.BuildRenderTree(builder);
return;
}
var seq = 0;
builder.OpenComponent<DxSplitter>(seq++);
builder.AddAttribute(seq++, "Width", "100%");
builder.AddAttribute(seq++, "Height", "100%");
builder.AddAttribute(seq++, "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", "350px");
panesBuilder.AddAttribute(paneSeq++, "MinSize", "300px");
panesBuilder.AddAttribute(paneSeq++, "MaxSize", "800px");
panesBuilder.AddAttribute(paneSeq++, "AllowCollapse", true);
panesBuilder.AddAttribute(paneSeq++, "ChildContent", (RenderFragment)(infoPanelBuilder =>
{
var infoPanelSeq = 0;
infoPanelBuilder.OpenComponent<MgGridInfoPanel<TDataItem>>(infoPanelSeq++);
infoPanelBuilder.AddComponentReferenceCapture(infoPanelSeq++, instance =>
{
_infoPanelInstance = (MgGridInfoPanel<TDataItem>)instance;
});
infoPanelBuilder.CloseComponent();
}));
panesBuilder.CloseComponent();
}));
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(TDataItem dataItem) => HasIdValue(dataItem.Id);
protected bool HasIdValue(TId id) => !_equalityComparerId.Equals(id, default); protected bool HasIdValue(TId id) => !_equalityComparerId.Equals(id, default);
protected bool IdEquals(TId id1, TId id2) => _equalityComparerId.Equals(id1, id2); protected bool IdEquals(TId id1, TId id2) => _equalityComparerId.Equals(id1, id2);
@ -104,6 +233,9 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
protected new EventCallback<GridCustomizeEditModelEventArgs> CustomizeEditModel { get; set; } protected new EventCallback<GridCustomizeEditModelEventArgs> CustomizeEditModel { get; set; }
[Parameter] public EventCallback<GridCustomizeEditModelEventArgs> OnGridCustomizeEditModel { get; set; } [Parameter] public EventCallback<GridCustomizeEditModelEventArgs> OnGridCustomizeEditModel { get; set; }
protected new EventCallback<GridFocusedRowChangedEventArgs> FocusedRowChanged { get; set; }
[Parameter] public EventCallback<GridFocusedRowChangedEventArgs> OnGridFocusedRowChanged { get; set; }
[Parameter] public EventCallback<IList<TDataItem>> OnDataSourceChanged { get; set; } [Parameter] public EventCallback<IList<TDataItem>> OnDataSourceChanged { get; set; }
[Parameter] public EventCallback<GridDataItemChangingEventArgs<TDataItem>> OnGridItemChanging { get; set; } [Parameter] public EventCallback<GridDataItemChangingEventArgs<TDataItem>> OnGridItemChanging { get; set; }
@ -328,6 +460,19 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
// e.EditModel = model; // e.EditModel = model;
// } // }
//} //}
protected virtual async Task OnFocusedRowChanged(GridFocusedRowChangedEventArgs e)
{
_focusedDataItem = e.DataItem;
if (ShowInfoPanel && _infoPanelInstance != null && e.DataItem is TDataItem dataItem)
{
_infoPanelInstance.RefreshData(this, dataItem);
}
await OnGridFocusedRowChanged.InvokeAsync(e);
}
private async Task OnItemSaving(GridEditModelSavingEventArgs e) private async Task OnItemSaving(GridEditModelSavingEventArgs e)
{ {
var dataItem = (e.EditModel as TDataItem)!; var dataItem = (e.EditModel as TDataItem)!;
@ -446,6 +591,7 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
base.EditModelSaving = EventCallback.Factory.Create<GridEditModelSavingEventArgs>(this, OnItemSaving); base.EditModelSaving = EventCallback.Factory.Create<GridEditModelSavingEventArgs>(this, OnItemSaving);
base.CustomizeEditModel = EventCallback.Factory.Create<GridCustomizeEditModelEventArgs>(this, OnCustomizeEditModel); base.CustomizeEditModel = EventCallback.Factory.Create<GridCustomizeEditModelEventArgs>(this, OnCustomizeEditModel);
//base.customizecel= EventCallback.Factory.Create<GridCustomizeEditModelEventArgs>(this, OnCustomizeEditModel); //base.customizecel= EventCallback.Factory.Create<GridCustomizeEditModelEventArgs>(this, OnCustomizeEditModel);
base.FocusedRowChanged = EventCallback.Factory.Create<GridFocusedRowChangedEventArgs>(this, OnFocusedRowChanged);
base.EditStart = EventCallback.Factory.Create<GridEditStartEventArgs>(this, OnEditStart); base.EditStart = EventCallback.Factory.Create<GridEditStartEventArgs>(this, OnEditStart);
CustomizeElement += OnCustomizeElement; CustomizeElement += OnCustomizeElement;

View File

@ -0,0 +1,42 @@
@using DevExpress.Blazor
@using Microsoft.AspNetCore.Components.Rendering
@typeparam TDataItem where TDataItem : class
<div class="mg-grid-info-panel">
@if (_currentDataItem != null && _currentGrid != null)
{
var colSpan = _allDataColumns.Count > 10 ? 6 : 12;
<DxFormLayout Data="_currentDataItem"
CssClass="info-panel-form"
CaptionPosition="CaptionPosition.Vertical"
SizeMode="SizeMode.Small">
@foreach (var column in _allDataColumns)
{
<DxFormLayoutItem Caption="@GetColumnCaption(column)"
CaptionCssClass="fw-semibold"
Field="@column.FieldName"
ColSpanXxl="@colSpan"
ColSpanXl="@colSpan"
ColSpanLg="@colSpan"
ColSpanMd="@colSpan"
ColSpanSm="@colSpan"
ColSpanXs="@colSpan"
ReadOnly="@column.ReadOnly" />
}
</DxFormLayout>
}
else
{
<div class="info-panel-empty">
<p>Válasszon ki egy sort az adatok megtekintéséhez</p>
</div>
}
</div>
@code {
private string GetColumnCaption(DxGridDataColumn column)
{
return !string.IsNullOrWhiteSpace(column.Caption) ? column.Caption : column.FieldName;
}
}

View File

@ -0,0 +1,135 @@
using DevExpress.Blazor;
using Microsoft.AspNetCore.Components;
using System.Reflection;
namespace AyCode.Blazor.Components.Components.Grids;
public partial class MgGridInfoPanel<TDataItem> : ComponentBase where TDataItem : class
{
private DxGrid? _currentGrid;
private TDataItem? _currentDataItem;
private List<DxGridDataColumn> _allDataColumns = [];
/// <summary>
/// Refreshes the InfoPanel with data from the specified grid row
/// </summary>
/// <param name="grid">The grid instance</param>
/// <param name="dataItem">The data item from the focused row</param>
public void RefreshData(DxGrid grid, TDataItem? dataItem)
{
ArgumentNullException.ThrowIfNull(grid);
_currentGrid = grid;
_currentDataItem = dataItem;
if (_currentGrid != null && _currentDataItem != null)
{
_allDataColumns = GetAllDataColumns(_currentGrid);
}
else
{
_allDataColumns = [];
}
InvokeAsync(StateHasChanged);
}
/// <summary>
/// Clears the InfoPanel
/// </summary>
public void Clear()
{
_currentGrid = null;
_currentDataItem = null;
_allDataColumns = [];
InvokeAsync(StateHasChanged);
}
private List<DxGridDataColumn> GetAllDataColumns(DxGrid grid)
{
var columns = new List<DxGridDataColumn>();
try
{
var allColumns = grid.GetDataColumns();
if (allColumns != null)
{
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))
{
columns.Add(dataColumn);
}
}
}
}
catch (Exception)
{
// Fallback: empty list if GetDataColumns fails
}
return columns;
}
private object? GetCellValue(DxGridDataColumn column)
{
if (_currentDataItem == 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;
}
catch
{
return null;
}
}
private string GetDisplayText(DxGridDataColumn column, object? value)
{
if (value == null)
return string.Empty;
if (value is DateTime dateTime)
{
return dateTime.ToString("yyyy-MM-dd HH:mm:ss");
}
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;
}
}

View File

@ -0,0 +1,31 @@
.mg-grid-info-panel {
height: 100%;
overflow-y: auto;
padding: 1rem;
background-color: #f8f9fa;
}
.info-panel-form {
width: 100%;
}
.info-panel-form .fw-semibold {
font-weight: 600;
color: #495057;
font-size: 0.875rem;
}
.info-panel-empty {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
color: #6c757d;
font-style: italic;
}
.info-panel-empty p {
margin: 0;
text-align: center;
padding: 2rem;
}