From 285f0293111585cb7aab65372260ed538b76b56c Mon Sep 17 00:00:00 2001 From: OlgunR Date: Tue, 14 Apr 2026 16:10:14 +0200 Subject: [PATCH] Add BandGridBase for reusable banded grid logic Introduced an abstract base class BandGridBase to centralize banded grid functionality in Blazor apps using DevExpress. This class manages band layouts, column definitions, user-specific layout persistence, grid size modes, and rendering logic. It integrates with BandLayoutService for layout storage and retrieval, and is intended for inheritance by specific grid components. --- .../Components/BandGridBase.cs | 215 ++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 DbFirst.BlazorWebApp/Components/BandGridBase.cs diff --git a/DbFirst.BlazorWebApp/Components/BandGridBase.cs b/DbFirst.BlazorWebApp/Components/BandGridBase.cs new file mode 100644 index 0000000..62517e8 --- /dev/null +++ b/DbFirst.BlazorWebApp/Components/BandGridBase.cs @@ -0,0 +1,215 @@ +using DbFirst.BlazorWebApp.Models.Grid; +using DbFirst.BlazorWebApp.Services; +using DevExpress.Blazor; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Rendering; + +namespace DbFirst.BlazorWebApp.Components; + +public abstract class BandGridBase : ComponentBase +{ + [Inject] protected BandLayoutService BandLayoutService { get; set; } = default!; + + // --- Abstract: jedes Grid definiert diese selbst --- + protected abstract string LayoutKey { get; } + protected abstract List ColumnDefinitions { get; } + + // --- Band-Layout Felder --- + protected BandLayout bandLayout = new(); + protected Dictionary columnBandAssignments = new(); + protected List bandOptions = new(); + protected Dictionary columnLookup = new(); + protected string? layoutUser; + protected bool gridLayoutApplied; + protected bool bandEditorExpanded; + protected IGrid? gridRef; + + // --- SizeMode --- + protected SizeMode _sizeMode = SizeMode.Medium; + protected static readonly List _sizeModes = Enum.GetValues().ToList(); + + private const string LayoutType = "GRID_BANDS"; + + // --- Lifecycle --- + protected async Task InitializeBandLayoutAsync() + { + columnLookup = ColumnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase); + layoutUser = await BandLayoutService.EnsureLayoutUserAsync(); + bandLayout = await BandLayoutService.LoadBandLayoutAsync(LayoutType, LayoutKey, layoutUser, columnLookup); + columnBandAssignments = BandLayoutService.BuildAssignmentsFromLayout(bandLayout); + ApplyColumnLayoutFromStorage(); + _sizeMode = bandLayout.SizeMode; + UpdateBandOptions(); + } + + protected async Task ApplyGridLayoutAfterRenderAsync() + { + if (!gridLayoutApplied && gridRef != null && bandLayout.GridLayout != null) + { + gridRef.LoadLayout(bandLayout.GridLayout); + gridLayoutApplied = true; + await InvokeAsync(StateHasChanged); + } + } + + // --- Layout speichern / zurücksetzen --- + protected async Task SaveLayoutAsync() + { + if (string.IsNullOrWhiteSpace(layoutUser)) return; + CaptureColumnLayoutFromGrid(); + await BandLayoutService.SaveBandLayoutAsync(LayoutType, LayoutKey, layoutUser, bandLayout); + } + + protected async Task ResetLayoutAsync() + { + if (string.IsNullOrWhiteSpace(layoutUser)) return; + await BandLayoutService.ResetBandLayoutAsync(LayoutType, LayoutKey, layoutUser); + bandLayout = new BandLayout(); + columnBandAssignments.Clear(); + UpdateBandOptions(); + foreach (var column in ColumnDefinitions) + column.Width = null; + columnLookup = ColumnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase); + _sizeMode = SizeMode.Medium; + gridRef?.LoadLayout(new GridPersistentLayout()); + gridLayoutApplied = false; + } + + private void CaptureColumnLayoutFromGrid() + { + if (gridRef == null) return; + var layout = gridRef.SaveLayout(); + bandLayout.GridLayout = layout; + bandLayout.SizeMode = _sizeMode; + var orderedColumns = layout.Columns + .Where(c => !string.IsNullOrWhiteSpace(c.FieldName)) + .OrderBy(c => c.VisibleIndex) + .ToList(); + bandLayout.ColumnOrder = orderedColumns.Select(c => c.FieldName).ToList(); + bandLayout.ColumnWidths = orderedColumns + .Where(c => !string.IsNullOrWhiteSpace(c.Width)) + .ToDictionary(c => c.FieldName, c => c.Width, StringComparer.OrdinalIgnoreCase); + } + + private void ApplyColumnLayoutFromStorage() + { + foreach (var column in ColumnDefinitions) + { + if (bandLayout.ColumnWidths.TryGetValue(column.FieldName, out var width) && !string.IsNullOrWhiteSpace(width)) + column.Width = width; + } + columnLookup = ColumnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase); + } + + // --- Band-Methoden --- + protected bool CanSaveBandLayout => !string.IsNullOrWhiteSpace(layoutUser); + + protected void AddBand() + { + bandLayout.Bands.Add(new BandDefinition { Id = Guid.NewGuid().ToString("N"), Caption = "Band" }); + UpdateBandOptions(); + } + + protected void RemoveBand(BandDefinition band) + { + bandLayout.Bands.Remove(band); + foreach (var key in columnBandAssignments.Where(p => p.Value == band.Id).Select(p => p.Key).ToList()) + columnBandAssignments.Remove(key); + UpdateBandOptions(); + SyncBandsFromAssignments(); + } + + protected void UpdateBandCaption(BandDefinition band, string value) + { + band.Caption = value; + UpdateBandOptions(); + } + + protected void UpdateColumnBand(string fieldName, string? bandId) + { + if (string.IsNullOrWhiteSpace(bandId)) + columnBandAssignments.Remove(fieldName); + else + columnBandAssignments[fieldName] = bandId; + SyncBandsFromAssignments(); + } + + protected string GetColumnBand(string fieldName) + => columnBandAssignments.TryGetValue(fieldName, out var bandId) ? bandId : string.Empty; + + protected void SyncBandsFromAssignments() + { + foreach (var band in bandLayout.Bands) + { + band.Columns = ColumnDefinitions + .Where(c => columnBandAssignments.TryGetValue(c.FieldName, out var id) && id == band.Id) + .Select(c => c.FieldName) + .ToList(); + } + StateHasChanged(); + } + + protected void UpdateBandOptions() + { + bandOptions = new List { new() { Id = string.Empty, Caption = "Ohne Band" } }; + bandOptions.AddRange(bandLayout.Bands.Select(b => new BandOption { Id = b.Id, Caption = b.Caption })); + } + + // --- SizeMode --- + protected string FormatSizeText(SizeMode size) => size switch + { + SizeMode.Small => "Klein", + SizeMode.Medium => "Mittel", + SizeMode.Large => "Groß", + _ => size.ToString() + }; + + protected void OnSizeChange(DropDownButtonItemClickEventArgs args) + { + _sizeMode = Enum.Parse(args.ItemInfo.Id); + } + + // --- RenderColumns / BuildDataColumn --- + protected RenderFragment RenderColumns() => builder => + { + var seq = 0; + builder.OpenComponent(seq++); + builder.AddAttribute(seq++, "Width", "120px"); + builder.CloseComponent(); + + var grouped = bandLayout.Bands.SelectMany(b => b.Columns).ToHashSet(StringComparer.OrdinalIgnoreCase); + foreach (var column in ColumnDefinitions.Where(c => !grouped.Contains(c.FieldName))) + BuildDataColumn(builder, ref seq, column); + + foreach (var band in bandLayout.Bands) + { + if (band.Columns.Count == 0) continue; + builder.OpenComponent(seq++); + builder.AddAttribute(seq++, "Caption", band.Caption); + builder.AddAttribute(seq++, "Columns", (RenderFragment)(bandBuilder => + { + var bandSeq = 0; + foreach (var columnName in band.Columns) + { + if (columnLookup.TryGetValue(columnName, out var column)) + BuildDataColumn(bandBuilder, ref bandSeq, column); + } + })); + builder.CloseComponent(); + } + }; + + protected void BuildDataColumn(RenderTreeBuilder builder, ref int seq, ColumnDefinition column) + { + builder.OpenComponent(seq++); + builder.AddAttribute(seq++, "FieldName", column.FieldName); + builder.AddAttribute(seq++, "Caption", column.Caption); + if (!string.IsNullOrWhiteSpace(column.Width)) + builder.AddAttribute(seq++, "Width", column.Width); + if (!string.IsNullOrWhiteSpace(column.DisplayFormat)) + builder.AddAttribute(seq++, "DisplayFormat", column.DisplayFormat); + if (column.ReadOnly) + builder.AddAttribute(seq++, "ReadOnly", true); + builder.CloseComponent(); + } +} \ No newline at end of file