Refactor: centralize grid band layout logic in service

Introduce BandLayoutService and shared models to manage grid band layouts across components. Refactor CatalogsGrid and MassDataGrid to use the new service, removing duplicated layout logic. Update _Imports.razor and register the service in Program.cs for improved maintainability and code reuse.
This commit is contained in:
OlgunR
2026-03-25 17:04:19 +01:00
parent 566c3b3276
commit dc74d21426
6 changed files with 472 additions and 730 deletions

View File

@@ -1,6 +1,5 @@
@inject CatalogApiClient Api
@inject LayoutApiClient LayoutApi
@inject IJSRuntime JsRuntime
@inject BandLayoutService BandLayoutService
@if (!string.IsNullOrWhiteSpace(errorMessage))
{
@@ -101,7 +100,9 @@ else
@RenderColumns()
</Columns>
<EditFormTemplate Context="editFormContext">
@{ SetEditContext(editFormContext.EditContext); var editModel = (CatalogEditModel)editFormContext.EditModel; SetPopupHeaderText(editModel.IsNew); }
@{
SetEditContext(editFormContext.EditContext); var editModel = (CatalogEditModel)editFormContext.EditModel; SetPopupHeaderText(editModel.IsNew);
}
<DxFormLayout ColCount="2">
<DxFormLayoutItem Caption="Titel">
<DxTextBox @bind-Text="editModel.CatTitle" Width="100%" />
@@ -143,13 +144,13 @@ else
private string popupHeaderText = "Edit";
private const string LayoutType = "GRID_BANDS";
private const string LayoutKey = "CatalogsGrid";
private const string LayoutUserStorageKey = "layoutUser";
private string? layoutUser;
private BandLayout bandLayout = new();
private Dictionary<string, string> columnBandAssignments = new();
private List<BandOption> bandOptions = new();
private Dictionary<string, ColumnDefinition> columnLookup = new();
private readonly JsonSerializerOptions jsonOptions = new(JsonSerializerDefaults.Web);
private bool gridLayoutApplied;
private List<ColumnDefinition> columnDefinitions = new()
{
new() { FieldName = nameof(CatalogReadDto.Guid), Caption = "Id", Width = "140px", FilterType = ColumnFilterType.Text },
@@ -168,30 +169,32 @@ else
};
private bool CanSaveBandLayout => !string.IsNullOrWhiteSpace(layoutUser);
private bool gridLayoutApplied;
private DevExpress.Blazor.SizeMode _sizeMode = DevExpress.Blazor.SizeMode.Medium;
private static readonly List<DevExpress.Blazor.SizeMode> _sizeModes =
Enum.GetValues<DevExpress.Blazor.SizeMode>().ToList();
private SizeMode _sizeMode = SizeMode.Medium;
private static readonly List<SizeMode> _sizeModes = Enum.GetValues<SizeMode>().ToList();
private string FormatSizeText(DevExpress.Blazor.SizeMode size) => size switch
private string FormatSizeText(SizeMode size) => size switch
{
DevExpress.Blazor.SizeMode.Small => "Klein",
DevExpress.Blazor.SizeMode.Medium => "Mittel",
DevExpress.Blazor.SizeMode.Large => "Groß",
SizeMode.Small => "Klein",
SizeMode.Medium => "Mittel",
SizeMode.Large => "Groß",
_ => size.ToString()
};
private void OnSizeChange(DropDownButtonItemClickEventArgs args)
{
_sizeMode = Enum.Parse<DevExpress.Blazor.SizeMode>(args.ItemInfo.Id);
_sizeMode = Enum.Parse<SizeMode>(args.ItemInfo.Id);
}
protected override async Task OnInitializedAsync()
{
columnLookup = columnDefinitions.ToDictionary(column => column.FieldName, StringComparer.OrdinalIgnoreCase);
await EnsureLayoutUserAsync();
await LoadBandLayoutAsync();
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();
await LoadCatalogs();
}
@@ -205,71 +208,6 @@ else
}
}
private void SetEditContext(EditContext context)
{
if (editContext == context)
{
return;
}
if (editContext != null)
{
editContext.OnFieldChanged -= OnEditFieldChanged;
}
editContext = context;
validationMessageStore = new ValidationMessageStore(editContext);
editContext.OnFieldChanged += OnEditFieldChanged;
}
private void OnEditFieldChanged(object? sender, FieldChangedEventArgs e)
{
if (validationMessageStore == null || editContext == null)
{
return;
}
if (e.FieldIdentifier.FieldName == nameof(CatalogEditModel.UpdateProcedure))
{
validationMessageStore.Clear();
editContext.NotifyValidationStateChanged();
return;
}
if (e.FieldIdentifier.FieldName == nameof(CatalogEditModel.CatTitle))
{
var field = new FieldIdentifier(editContext.Model, nameof(CatalogEditModel.CatTitle));
validationMessageStore.Clear(field);
editContext.NotifyValidationStateChanged();
}
}
private void SetPopupHeaderText(bool isNew)
{
popupHeaderText = isNew ? "Neu" : "Edit";
}
private void OnCustomizeEditModel(GridCustomizeEditModelEventArgs e)
{
popupHeaderText = e.IsNew ? "Neu" : "Edit";
if (e.IsNew)
{
e.EditModel = new CatalogEditModel { IsNew = true };
return;
}
var item = (CatalogReadDto)e.DataItem;
e.EditModel = new CatalogEditModel
{
Guid = item.Guid,
CatTitle = item.CatTitle,
CatString = item.CatString,
UpdateProcedure = 0,
OriginalCatTitle = item.CatTitle,
IsNew = false
};
}
private async Task LoadCatalogs()
{
isLoading = true;
@@ -290,11 +228,229 @@ else
}
}
private async Task SaveLayoutAsync()
{
if (string.IsNullOrWhiteSpace(layoutUser))
return;
try
{
CaptureColumnLayoutFromGrid();
await BandLayoutService.SaveBandLayoutAsync(LayoutType, LayoutKey, layoutUser, bandLayout);
infoMessage = "Layout gespeichert.";
errorMessage = null;
}
catch (Exception ex)
{
errorMessage = $"Layout konnte nicht gespeichert werden: {ex.Message}";
}
}
private 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;
if (gridRef != null)
gridRef.LoadLayout(new GridPersistentLayout());
gridLayoutApplied = false;
infoMessage = "Layout zurückgesetzt.";
errorMessage = null;
}
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);
}
private void AddBand()
{
bandLayout.Bands.Add(new BandDefinition { Id = Guid.NewGuid().ToString("N"), Caption = "Band" });
UpdateBandOptions();
}
private 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();
}
private void UpdateBandCaption(BandDefinition band, string value)
{
band.Caption = value;
UpdateBandOptions();
}
private void UpdateColumnBand(string fieldName, string? bandId)
{
if (string.IsNullOrWhiteSpace(bandId))
columnBandAssignments.Remove(fieldName);
else
columnBandAssignments[fieldName] = bandId;
SyncBandsFromAssignments();
}
private string GetColumnBand(string fieldName)
=> columnBandAssignments.TryGetValue(fieldName, out var bandId) ? bandId : string.Empty;
private 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();
}
private void UpdateBandOptions()
{
bandOptions = new List<BandOption> { new() { Id = string.Empty, Caption = "Ohne Band" } };
bandOptions.AddRange(bandLayout.Bands.Select(b => new BandOption { Id = b.Id, Caption = b.Caption }));
}
private RenderFragment RenderColumns() => builder =>
{
var seq = 0;
builder.OpenComponent<DxGridCommandColumn>(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<DxGridBandColumn>(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();
}
};
private void BuildDataColumn(RenderTreeBuilder builder, ref int seq, ColumnDefinition column)
{
builder.OpenComponent<DxGridDataColumn>(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();
}
private void SetEditContext(EditContext context)
{
if (editContext == context) return;
if (editContext != null)
editContext.OnFieldChanged -= OnEditFieldChanged;
editContext = context;
validationMessageStore = new ValidationMessageStore(editContext);
editContext.OnFieldChanged += OnEditFieldChanged;
}
private void OnEditFieldChanged(object? sender, FieldChangedEventArgs e)
{
if (validationMessageStore == null || editContext == null) return;
if (e.FieldIdentifier.FieldName == nameof(CatalogEditModel.UpdateProcedure))
{
validationMessageStore.Clear();
editContext.NotifyValidationStateChanged();
return;
}
if (e.FieldIdentifier.FieldName == nameof(CatalogEditModel.CatTitle))
{
validationMessageStore.Clear(new FieldIdentifier(editContext.Model, nameof(CatalogEditModel.CatTitle)));
editContext.NotifyValidationStateChanged();
}
}
private void SetPopupHeaderText(bool isNew) => popupHeaderText = isNew ? "Neu" : "Edit";
private void OnCustomizeEditModel(GridCustomizeEditModelEventArgs e)
{
popupHeaderText = e.IsNew ? "Neu" : "Edit";
if (e.IsNew)
{
e.EditModel = new CatalogEditModel { IsNew = true };
return;
}
var item = (CatalogReadDto)e.DataItem;
e.EditModel = new CatalogEditModel
{
Guid = item.Guid,
CatTitle = item.CatTitle,
CatString = item.CatString,
UpdateProcedure = 0,
OriginalCatTitle = item.CatTitle,
IsNew = false
};
}
private async Task OnEditModelSaving(GridEditModelSavingEventArgs e)
{
errorMessage = null;
infoMessage = null;
validationMessageStore?.Clear();
editContext?.NotifyValidationStateChanged();
@@ -320,18 +476,12 @@ else
if (!created.Success || created.Value == null)
{
if (!string.IsNullOrWhiteSpace(created.Error))
{
AddValidationError(editModel, nameof(CatalogEditModel.CatTitle), created.Error);
}
else
{
errorMessage = "Anlegen fehlgeschlagen.";
}
e.Cancel = true;
return;
}
infoMessage = "Katalog angelegt.";
focusedRowKey = created.Value.Guid;
}
@@ -344,7 +494,6 @@ else
e.Cancel = true;
return;
}
infoMessage = "Katalog aktualisiert.";
focusedRowKey = editModel.Guid;
}
@@ -360,30 +509,20 @@ else
private void AddValidationError(CatalogEditModel editModel, string fieldName, string message)
{
if (editContext == null || validationMessageStore == null)
{
return;
}
var field = new FieldIdentifier(editModel, fieldName);
validationMessageStore.Add(field, message);
if (editContext == null || validationMessageStore == null) return;
validationMessageStore.Add(new FieldIdentifier(editModel, fieldName), message);
editContext.NotifyValidationStateChanged();
}
private bool ValidateEditModel(CatalogEditModel editModel, bool isNew)
{
if (isNew)
{
return true;
}
if (isNew) return true;
if (editModel.UpdateProcedure == 0 &&
!string.Equals(editModel.CatTitle, editModel.OriginalCatTitle, StringComparison.OrdinalIgnoreCase))
{
AddValidationError(editModel, nameof(CatalogEditModel.CatTitle), "Titel kann nicht geändert werden.");
return false;
}
return true;
}
@@ -391,9 +530,7 @@ else
{
errorMessage = null;
infoMessage = null;
var item = (CatalogReadDto)e.DataItem;
try
{
var deleted = await Api.DeleteAsync(item.Guid);
@@ -403,350 +540,16 @@ else
e.Cancel = true;
return;
}
infoMessage = "Katalog gelöscht.";
await LoadCatalogs();
}
catch (Exception ex)
{
errorMessage = $"Fehler beim L&#246;schen: {ex.Message}";
errorMessage = $"Fehler beim Löschen: {ex.Message}";
e.Cancel = true;
}
}
private async Task EnsureLayoutUserAsync()
{
layoutUser = await JsRuntime.InvokeAsync<string?>("localStorage.getItem", LayoutUserStorageKey);
if (string.IsNullOrWhiteSpace(layoutUser))
{
layoutUser = Guid.NewGuid().ToString("N");
await JsRuntime.InvokeVoidAsync("localStorage.setItem", LayoutUserStorageKey, layoutUser);
}
}
private async Task SaveLayoutAsync()
{
if (string.IsNullOrWhiteSpace(layoutUser))
{
return;
}
try
{
CaptureColumnLayoutFromGrid();
var layoutData = JsonSerializer.Serialize(bandLayout, jsonOptions);
await LayoutApi.UpsertAsync(new LayoutDto
{
LayoutType = LayoutType,
LayoutKey = LayoutKey,
UserName = layoutUser,
LayoutData = layoutData
});
infoMessage = "Layout gespeichert.";
errorMessage = null;
}
catch (Exception ex)
{
errorMessage = $"Layout konnte nicht gespeichert werden: {ex.Message}";
}
}
private void CaptureColumnLayoutFromGrid()
{
if (gridRef == null)
{
return;
}
var layout = gridRef.SaveLayout();
bandLayout.GridLayout = layout;
bandLayout.SizeMode = _sizeMode;
var orderedColumns = layout.Columns
.Where(column => !string.IsNullOrWhiteSpace(column.FieldName))
.OrderBy(column => column.VisibleIndex)
.ToList();
bandLayout.ColumnOrder = orderedColumns
.Select(column => column.FieldName)
.ToList();
bandLayout.ColumnWidths = orderedColumns
.Where(column => !string.IsNullOrWhiteSpace(column.Width))
.ToDictionary(column => column.FieldName, column => column.Width, StringComparer.OrdinalIgnoreCase);
}
private async Task LoadBandLayoutAsync()
{
if (string.IsNullOrWhiteSpace(layoutUser))
{
bandLayout = new BandLayout();
UpdateBandOptions();
return;
}
var stored = await LayoutApi.GetAsync(LayoutType, LayoutKey, layoutUser);
if (stored != null && !string.IsNullOrWhiteSpace(stored.LayoutData))
{
var parsed = JsonSerializer.Deserialize<BandLayout>(stored.LayoutData, jsonOptions);
bandLayout = NormalizeBandLayout(parsed);
}
else
{
bandLayout = new BandLayout();
}
columnBandAssignments = BuildAssignmentsFromLayout(bandLayout);
ApplyColumnLayoutFromStorage();
_sizeMode = bandLayout.SizeMode;
UpdateBandOptions();
}
private async Task ResetLayoutAsync()
{
if (string.IsNullOrWhiteSpace(layoutUser))
{
return;
}
await LayoutApi.DeleteAsync(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 = DevExpress.Blazor.SizeMode.Medium;
if (gridRef != null)
gridRef.LoadLayout(new GridPersistentLayout());
gridLayoutApplied = false;
infoMessage = "Layout zur\u00fcckgesetzt.";
errorMessage = null;
}
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(column => column.FieldName, StringComparer.OrdinalIgnoreCase);
}
private void AddBand()
{
bandLayout.Bands.Add(new BandDefinition
{
Id = Guid.NewGuid().ToString("N"),
Caption = "Band"
});
UpdateBandOptions();
}
private void RemoveBand(BandDefinition band)
{
bandLayout.Bands.Remove(band);
var removedColumns = columnBandAssignments.Where(pair => pair.Value == band.Id)
.Select(pair => pair.Key)
.ToList();
foreach (var column in removedColumns)
{
columnBandAssignments.Remove(column);
}
UpdateBandOptions();
SyncBandsFromAssignments();
}
private void UpdateBandCaption(BandDefinition band, string value)
{
band.Caption = value;
UpdateBandOptions();
}
private void UpdateColumnBand(string fieldName, string? bandId)
{
if (string.IsNullOrWhiteSpace(bandId))
{
columnBandAssignments.Remove(fieldName);
}
else
{
columnBandAssignments[fieldName] = bandId;
}
SyncBandsFromAssignments();
}
private string GetColumnBand(string fieldName)
{
return columnBandAssignments.TryGetValue(fieldName, out var bandId) ? bandId : string.Empty;
}
private void SyncBandsFromAssignments()
{
foreach (var band in bandLayout.Bands)
{
band.Columns = columnDefinitions
.Where(column => columnBandAssignments.TryGetValue(column.FieldName, out var bandId) && bandId == band.Id)
.Select(column => column.FieldName)
.ToList();
}
StateHasChanged();
}
private void UpdateBandOptions()
{
bandOptions = new List<BandOption> { new() { Id = string.Empty, Caption = "Ohne Band" } };
bandOptions.AddRange(bandLayout.Bands.Select(band => new BandOption { Id = band.Id, Caption = band.Caption }));
}
private BandLayout NormalizeBandLayout(BandLayout? layout)
{
layout ??= new BandLayout();
layout.Bands ??= new List<BandDefinition>();
layout.ColumnOrder ??= new List<string>();
layout.ColumnWidths ??= new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
foreach (var band in layout.Bands)
{
if (string.IsNullOrWhiteSpace(band.Id))
{
band.Id = Guid.NewGuid().ToString("N");
}
if (string.IsNullOrWhiteSpace(band.Caption))
{
band.Caption = "Band";
}
band.Columns = band.Columns?.Where(columnLookup.ContainsKey).ToList() ?? new List<string>();
}
return layout;
}
private Dictionary<string, string> BuildAssignmentsFromLayout(BandLayout layout)
{
var assignments = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var band in layout.Bands)
{
foreach (var column in band.Columns)
{
assignments[column] = band.Id;
}
}
return assignments;
}
private RenderFragment RenderColumns() => builder =>
{
var seq = 0;
builder.OpenComponent<DxGridCommandColumn>(seq++);
builder.AddAttribute(seq++, "Width", "120px");
builder.CloseComponent();
var grouped = bandLayout.Bands.SelectMany(band => band.Columns).ToHashSet(StringComparer.OrdinalIgnoreCase);
foreach (var column in columnDefinitions.Where(column => !grouped.Contains(column.FieldName)))
{
BuildDataColumn(builder, ref seq, column);
}
foreach (var band in bandLayout.Bands)
{
if (band.Columns.Count == 0)
{
continue;
}
builder.OpenComponent<DxGridBandColumn>(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();
}
};
private void BuildDataColumn(RenderTreeBuilder builder, ref int seq, ColumnDefinition column)
{
builder.OpenComponent<DxGridDataColumn>(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();
}
private sealed class BandLayout
{
public List<BandDefinition> Bands { get; set; } = new();
public List<string> ColumnOrder { get; set; } = new();
public Dictionary<string, string?> ColumnWidths { get; set; } = new(StringComparer.OrdinalIgnoreCase);
public GridPersistentLayout? GridLayout { get; set; }
public DevExpress.Blazor.SizeMode SizeMode { get; set; } = DevExpress.Blazor.SizeMode.Medium;
}
private sealed class BandDefinition
{
public string Id { get; set; } = string.Empty;
public string Caption { get; set; } = string.Empty;
public List<string> Columns { get; set; } = new();
}
private sealed class BandOption
{
public string Id { get; set; } = string.Empty;
public string Caption { get; set; } = string.Empty;
}
private sealed class ColumnDefinition
{
public string FieldName { get; init; } = string.Empty;
public string Caption { get; init; } = string.Empty;
public string? Width { get; set; }
public string? DisplayFormat { get; init; }
public bool ReadOnly { get; init; }
public ColumnFilterType FilterType { get; init; }
}
private enum ColumnFilterType
{
Text,
Date
}
private sealed class CatalogEditModel
{
public int Guid { get; set; }
@@ -762,10 +565,4 @@ else
public int Value { get; set; }
public string Text { get; set; } = string.Empty;
}
private sealed class FilterOperatorOption
{
public string Value { get; set; } = string.Empty;
public string Text { get; set; } = string.Empty;
}
}

View File

@@ -1,6 +1,5 @@
@inject MassDataApiClient Api
@inject LayoutApiClient LayoutApi
@inject IJSRuntime JsRuntime
@inject BandLayoutService BandLayoutService
@if (!string.IsNullOrWhiteSpace(errorMessage))
{
@@ -113,7 +112,9 @@ else
@RenderColumns()
</Columns>
<EditFormTemplate Context="editFormContext">
@{ SetEditContext(editFormContext.EditContext); var editModel = (MassDataEditModel)editFormContext.EditModel; SetPopupHeaderText(editModel.IsNew); }
@{
SetEditContext(editFormContext.EditContext); var editModel = (MassDataEditModel)editFormContext.EditModel; SetPopupHeaderText(editModel.IsNew);
}
<DxFormLayout ColCount="2">
<DxFormLayoutItem Caption="CustomerName">
<DxTextBox @bind-Text="editModel.CustomerName" Width="100%" />
@@ -171,13 +172,13 @@ else
private int? focusedRowKey;
private const string LayoutType = "GRID_BANDS";
private const string LayoutKey = "MassDataGrid";
private const string LayoutUserStorageKey = "layoutUser";
private string? layoutUser;
private BandLayout bandLayout = new();
private Dictionary<string, string> columnBandAssignments = new();
private List<BandOption> bandOptions = new();
private Dictionary<string, ColumnDefinition> columnLookup = new();
private readonly JsonSerializerOptions jsonOptions = new(JsonSerializerDefaults.Web);
private bool gridLayoutApplied;
private List<ColumnDefinition> columnDefinitions = new()
{
new() { FieldName = nameof(MassDataReadDto.Id), Caption = "Id", Width = "90px", ReadOnly = true, FilterType = ColumnFilterType.Text },
@@ -189,15 +190,13 @@ else
new() { FieldName = nameof(MassDataReadDto.ChangedWhen), Caption = "Changed", ReadOnly = true, FilterType = ColumnFilterType.Date }
};
private bool CanSaveBandLayout => !string.IsNullOrWhiteSpace(layoutUser);
private readonly List<PageSizeOption> pageSizeOptions = new()
{
new() { Value = 100, Text = "100" },
new() { Value = 1000, Text = "1.000" },
new() { Value = 10000, Text = "10.000" },
new() { Value = 100, Text = "100" },
new() { Value = 1000, Text = "1.000" },
new() { Value = 10000, Text = "10.000" },
new() { Value = 100000, Text = "100.000" },
new() { Value = null, Text = "Alle" }
new() { Value = null, Text = "Alle" }
};
private readonly List<ProcedureOption> procedureOptions = new()
@@ -205,33 +204,46 @@ else
new() { Value = 0, Text = "PRMassdata_UpsertByCustomerName" }
};
private bool gridLayoutApplied;
private bool CanSaveBandLayout => !string.IsNullOrWhiteSpace(layoutUser);
private DevExpress.Blazor.SizeMode _sizeMode = DevExpress.Blazor.SizeMode.Medium;
private static readonly List<DevExpress.Blazor.SizeMode> _sizeModes =
Enum.GetValues<DevExpress.Blazor.SizeMode>().ToList();
private SizeMode _sizeMode = SizeMode.Medium;
private static readonly List<SizeMode> _sizeModes = Enum.GetValues<SizeMode>().ToList();
private string FormatSizeText(DevExpress.Blazor.SizeMode size) => size switch
private string FormatSizeText(SizeMode size) => size switch
{
DevExpress.Blazor.SizeMode.Small => "Klein",
DevExpress.Blazor.SizeMode.Medium => "Mittel",
DevExpress.Blazor.SizeMode.Large => "Groß",
SizeMode.Small => "Klein",
SizeMode.Medium => "Mittel",
SizeMode.Large => "Groß",
_ => size.ToString()
};
private void OnSizeChange(DropDownButtonItemClickEventArgs args)
{
_sizeMode = Enum.Parse<DevExpress.Blazor.SizeMode>(args.ItemInfo.Id);
_sizeMode = Enum.Parse<SizeMode>(args.ItemInfo.Id);
}
protected override async Task OnInitializedAsync()
{
columnLookup = columnDefinitions.ToDictionary(column => column.FieldName, StringComparer.OrdinalIgnoreCase);
await EnsureLayoutUserAsync();
await LoadBandLayoutAsync();
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();
await LoadPage(0);
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!gridLayoutApplied && gridRef != null && bandLayout.GridLayout != null)
{
gridRef.LoadLayout(bandLayout.GridLayout);
gridLayoutApplied = true;
await InvokeAsync(StateHasChanged);
}
}
private async Task LoadPage(int page)
{
isLoading = true;
@@ -242,7 +254,6 @@ else
var effectivePageSize = pageSize ?? (total == 0 ? 1 : total);
pageCount = Math.Max(1, (int)Math.Ceiling(total / (double)effectivePageSize));
pageIndex = Math.Clamp(page, 0, pageCount - 1);
var skip = pageSize.HasValue ? pageIndex * pageSize.Value : 0;
items = await Api.GetAllAsync(skip, pageSize);
}
@@ -258,10 +269,7 @@ else
}
}
private async Task OnPageChanged(int index)
{
await LoadPage(index);
}
private async Task OnPageChanged(int index) => await LoadPage(index);
private async Task OnPageSizeChanged(int? size)
{
@@ -269,61 +277,15 @@ else
await LoadPage(0);
}
private async Task EnsureLayoutUserAsync()
{
layoutUser = await JsRuntime.InvokeAsync<string?>("localStorage.getItem", LayoutUserStorageKey);
if (string.IsNullOrWhiteSpace(layoutUser))
{
layoutUser = Guid.NewGuid().ToString("N");
await JsRuntime.InvokeVoidAsync("localStorage.setItem", LayoutUserStorageKey, layoutUser);
}
}
private async Task LoadBandLayoutAsync()
{
if (string.IsNullOrWhiteSpace(layoutUser))
{
bandLayout = new BandLayout();
UpdateBandOptions();
return;
}
var stored = await LayoutApi.GetAsync(LayoutType, LayoutKey, layoutUser);
if (stored != null && !string.IsNullOrWhiteSpace(stored.LayoutData))
{
var parsed = JsonSerializer.Deserialize<BandLayout>(stored.LayoutData, jsonOptions);
bandLayout = NormalizeBandLayout(parsed);
}
else
{
bandLayout = new BandLayout();
}
columnBandAssignments = BuildAssignmentsFromLayout(bandLayout);
ApplyColumnLayoutFromStorage();
_sizeMode = bandLayout.SizeMode;
UpdateBandOptions();
}
private async Task SaveLayoutAsync()
{
if (string.IsNullOrWhiteSpace(layoutUser))
{
return;
}
try
{
CaptureColumnLayoutFromGrid();
var layoutData = JsonSerializer.Serialize(bandLayout, jsonOptions);
await LayoutApi.UpsertAsync(new LayoutDto
{
LayoutType = LayoutType,
LayoutKey = LayoutKey,
UserName = layoutUser,
LayoutData = layoutData
});
await BandLayoutService.SaveBandLayoutAsync(LayoutType, LayoutKey, layoutUser, bandLayout);
infoMessage = "Layout gespeichert.";
errorMessage = null;
}
@@ -333,41 +295,12 @@ else
}
}
private void CaptureColumnLayoutFromGrid()
{
if (gridRef == null)
{
return;
}
var layout = gridRef.SaveLayout();
bandLayout.GridLayout = layout;
bandLayout.SizeMode = _sizeMode;
var orderedColumns = layout.Columns
.Where(column => !string.IsNullOrWhiteSpace(column.FieldName))
.OrderBy(column => column.VisibleIndex)
.ToList();
bandLayout.ColumnOrder = orderedColumns
.Select(column => column.FieldName)
.ToList();
bandLayout.ColumnWidths = orderedColumns
.Where(column => !string.IsNullOrWhiteSpace(column.Width))
.ToDictionary(column => column.FieldName, column => column.Width, StringComparer.OrdinalIgnoreCase);
bandLayout.SizeMode = _sizeMode;
}
private async Task ResetLayoutAsync()
{
if (string.IsNullOrWhiteSpace(layoutUser))
{
return;
}
await LayoutApi.DeleteAsync(LayoutType, LayoutKey, layoutUser);
await BandLayoutService.ResetBandLayoutAsync(LayoutType, LayoutKey, layoutUser);
bandLayout = new BandLayout();
columnBandAssignments.Clear();
@@ -377,49 +310,57 @@ else
column.Width = null;
columnLookup = columnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase);
_sizeMode = DevExpress.Blazor.SizeMode.Medium;
_sizeMode = SizeMode.Medium;
if (gridRef != null)
gridRef.LoadLayout(new GridPersistentLayout());
gridLayoutApplied = false;
infoMessage = "Layout zur\u00fcckgesetzt.";
infoMessage = "Layout zurückgesetzt.";
errorMessage = null;
}
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(column => column.FieldName, StringComparer.OrdinalIgnoreCase);
columnLookup = columnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase);
}
private void AddBand()
{
bandLayout.Bands.Add(new BandDefinition
{
Id = Guid.NewGuid().ToString("N"),
Caption = "Band"
});
bandLayout.Bands.Add(new BandDefinition { Id = Guid.NewGuid().ToString("N"), Caption = "Band" });
UpdateBandOptions();
}
private void RemoveBand(BandDefinition band)
{
bandLayout.Bands.Remove(band);
var removedColumns = columnBandAssignments.Where(pair => pair.Value == band.Id)
.Select(pair => pair.Key)
.ToList();
foreach (var column in removedColumns)
{
columnBandAssignments.Remove(column);
}
foreach (var key in columnBandAssignments.Where(p => p.Value == band.Id).Select(p => p.Key).ToList())
columnBandAssignments.Remove(key);
UpdateBandOptions();
SyncBandsFromAssignments();
}
@@ -433,77 +374,31 @@ else
private void UpdateColumnBand(string fieldName, string? bandId)
{
if (string.IsNullOrWhiteSpace(bandId))
{
columnBandAssignments.Remove(fieldName);
}
else
{
columnBandAssignments[fieldName] = bandId;
}
SyncBandsFromAssignments();
}
private string GetColumnBand(string fieldName)
{
return columnBandAssignments.TryGetValue(fieldName, out var bandId) ? bandId : string.Empty;
}
=> columnBandAssignments.TryGetValue(fieldName, out var bandId) ? bandId : string.Empty;
private void SyncBandsFromAssignments()
{
foreach (var band in bandLayout.Bands)
{
band.Columns = columnDefinitions
.Where(column => columnBandAssignments.TryGetValue(column.FieldName, out var bandId) && bandId == band.Id)
.Select(column => column.FieldName)
.Where(c => columnBandAssignments.TryGetValue(c.FieldName, out var id) && id == band.Id)
.Select(c => c.FieldName)
.ToList();
}
StateHasChanged();
}
private void UpdateBandOptions()
{
bandOptions = new List<BandOption> { new() { Id = string.Empty, Caption = "Ohne Band" } };
bandOptions.AddRange(bandLayout.Bands.Select(band => new BandOption { Id = band.Id, Caption = band.Caption }));
}
private Dictionary<string, string> BuildAssignmentsFromLayout(BandLayout layout)
{
var assignments = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var band in layout.Bands)
{
foreach (var column in band.Columns)
{
assignments[column] = band.Id;
}
}
return assignments;
}
private BandLayout NormalizeBandLayout(BandLayout? layout)
{
layout ??= new BandLayout();
layout.Bands ??= new List<BandDefinition>();
layout.ColumnOrder ??= new List<string>();
layout.ColumnWidths ??= new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
foreach (var band in layout.Bands)
{
if (string.IsNullOrWhiteSpace(band.Id))
{
band.Id = Guid.NewGuid().ToString("N");
}
if (string.IsNullOrWhiteSpace(band.Caption))
{
band.Caption = "Band";
}
band.Columns = band.Columns?.Where(columnLookup.ContainsKey).ToList() ?? new List<string>();
}
return layout;
bandOptions.AddRange(bandLayout.Bands.Select(b => new BandOption { Id = b.Id, Caption = b.Caption }));
}
private RenderFragment RenderColumns() => builder =>
@@ -513,18 +408,13 @@ else
builder.AddAttribute(seq++, "Width", "120px");
builder.CloseComponent();
var grouped = bandLayout.Bands.SelectMany(band => band.Columns).ToHashSet(StringComparer.OrdinalIgnoreCase);
foreach (var column in columnDefinitions.Where(column => !grouped.Contains(column.FieldName)))
{
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;
}
if (band.Columns.Count == 0) continue;
builder.OpenComponent<DxGridBandColumn>(seq++);
builder.AddAttribute(seq++, "Caption", band.Caption);
@@ -534,9 +424,7 @@ else
foreach (var columnName in band.Columns)
{
if (columnLookup.TryGetValue(columnName, out var column))
{
BuildDataColumn(bandBuilder, ref bandSeq, column);
}
}
}));
builder.CloseComponent();
@@ -549,35 +437,19 @@ else
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();
}
private void SetEditContext(EditContext context)
{
if (editContext == context)
{
return;
}
if (editContext == context) return;
if (editContext != null)
{
editContext.OnFieldChanged -= OnEditFieldChanged;
}
editContext = context;
validationMessageStore = new ValidationMessageStore(editContext);
editContext.OnFieldChanged += OnEditFieldChanged;
@@ -585,10 +457,7 @@ else
private void OnEditFieldChanged(object? sender, FieldChangedEventArgs e)
{
if (validationMessageStore == null || editContext == null)
{
return;
}
if (validationMessageStore == null || editContext == null) return;
if (e.FieldIdentifier.FieldName == nameof(MassDataEditModel.UpdateProcedure))
{
@@ -599,16 +468,12 @@ else
if (e.FieldIdentifier.FieldName == nameof(MassDataEditModel.CustomerName))
{
var field = new FieldIdentifier(editContext.Model, nameof(MassDataEditModel.CustomerName));
validationMessageStore.Clear(field);
validationMessageStore.Clear(new FieldIdentifier(editContext.Model, nameof(MassDataEditModel.CustomerName)));
editContext.NotifyValidationStateChanged();
}
}
private void SetPopupHeaderText(bool isNew)
{
popupHeaderText = isNew ? "Neu" : "Edit";
}
private void SetPopupHeaderText(bool isNew) => popupHeaderText = isNew ? "Neu" : "Edit";
private async Task OnCustomizeEditModel(GridCustomizeEditModelEventArgs e)
{
@@ -638,7 +503,6 @@ else
{
errorMessage = null;
infoMessage = null;
validationMessageStore?.Clear();
editContext?.NotifyValidationStateChanged();
@@ -685,13 +549,8 @@ else
private void AddValidationError(MassDataEditModel editModel, string fieldName, string message)
{
if (editContext == null || validationMessageStore == null)
{
return;
}
var field = new FieldIdentifier(editModel, fieldName);
validationMessageStore.Add(field, message);
if (editContext == null || validationMessageStore == null) return;
validationMessageStore.Add(new FieldIdentifier(editModel, fieldName), message);
editContext.NotifyValidationStateChanged();
}
@@ -703,55 +562,6 @@ else
return Task.CompletedTask;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!gridLayoutApplied && gridRef != null && bandLayout.GridLayout != null)
{
gridRef.LoadLayout(bandLayout.GridLayout);
gridLayoutApplied = true;
await InvokeAsync(StateHasChanged);
}
}
private sealed class BandLayout
{
public List<BandDefinition> Bands { get; set; } = new();
public List<string> ColumnOrder { get; set; } = new();
public Dictionary<string, string?> ColumnWidths { get; set; } = new(StringComparer.OrdinalIgnoreCase);
public GridPersistentLayout? GridLayout { get; set; }
public DevExpress.Blazor.SizeMode SizeMode { get; set; } = DevExpress.Blazor.SizeMode.Medium;
}
private sealed class BandDefinition
{
public string Id { get; set; } = string.Empty;
public string Caption { get; set; } = string.Empty;
public List<string> Columns { get; set; } = new();
}
private sealed class BandOption
{
public string Id { get; set; } = string.Empty;
public string Caption { get; set; } = string.Empty;
}
private sealed class ColumnDefinition
{
public string FieldName { get; init; } = string.Empty;
public string Caption { get; init; } = string.Empty;
public string? Width { get; set; }
public string? DisplayFormat { get; init; }
public bool ReadOnly { get; init; }
public ColumnFilterType FilterType { get; init; }
}
private enum ColumnFilterType
{
Text,
Bool,
Date
}
private sealed class MassDataEditModel
{
public int Id { get; set; }
@@ -770,21 +580,9 @@ else
public string Text { get; set; } = string.Empty;
}
private sealed class BoolFilterOption
{
public bool? Value { get; set; }
public string Text { get; set; } = string.Empty;
}
private sealed class PageSizeOption
{
public int? Value { get; set; }
public string Text { get; set; } = string.Empty;
}
private sealed class FilterOperatorOption
{
public string Value { get; set; } = string.Empty;
public string Text { get; set; } = string.Empty;
}
}

View File

@@ -1,5 +1,6 @@
@using System.Net.Http
@using System.Net.Http.Json
@using System.Text.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Rendering
@using Microsoft.AspNetCore.Components.Routing
@@ -11,10 +12,9 @@
@using DbFirst.BlazorWebApp
@using DbFirst.BlazorWebApp.Components
@using DbFirst.BlazorWebApp.Models
@using DbFirst.BlazorWebApp.Models.Grid
@using DbFirst.BlazorWebApp.Services
@using DevExpress.Blazor
@using DevExpress.DashboardBlazor
@using DevExpress.DashboardWeb
@using DevExpress.Data.Filtering
@using DbFirst.BlazorWebApp
@using System.Text.Json

View File

@@ -0,0 +1,43 @@
using DevExpress.Blazor;
namespace DbFirst.BlazorWebApp.Models.Grid
{
public class BandLayout
{
public List<BandDefinition> Bands { get; set; } = new();
public List<string> ColumnOrder { get; set; } = new();
public Dictionary<string, string?> ColumnWidths { get; set; } = new(StringComparer.OrdinalIgnoreCase);
public GridPersistentLayout? GridLayout { get; set; }
public SizeMode SizeMode { get; set; } = SizeMode.Medium;
}
public class BandDefinition
{
public string Id { get; set; } = string.Empty;
public string Caption { get; set; } = string.Empty;
public List<string> Columns { get; set; } = new();
}
public class BandOption
{
public string Id { get; set; } = string.Empty;
public string Caption { get; set; } = string.Empty;
}
public class ColumnDefinition
{
public string FieldName { get; init; } = string.Empty;
public string Caption { get; init; } = string.Empty;
public string? Width { get; set; }
public string? DisplayFormat { get; init; }
public bool ReadOnly { get; init; }
public ColumnFilterType FilterType { get; init; }
}
public enum ColumnFilterType
{
Text,
Bool,
Date
}
}

View File

@@ -10,6 +10,7 @@ builder.Services.AddRazorComponents()
builder.Services.AddDevExpressBlazor(options => options.BootstrapVersion = BootstrapVersion.v5);
builder.Services.AddScoped<ThemeState>();
builder.Services.AddScoped<BandLayoutService>();
var apiBaseUrl = builder.Configuration["ApiBaseUrl"];
if (!string.IsNullOrWhiteSpace(apiBaseUrl))

View File

@@ -0,0 +1,103 @@
using DbFirst.BlazorWebApp.Models;
using DbFirst.BlazorWebApp.Models.Grid;
using Microsoft.JSInterop;
using System.Text.Json;
namespace DbFirst.BlazorWebApp.Services
{
public class BandLayoutService(LayoutApiClient layoutApi, IJSRuntime jsRuntime)
{
private const string LayoutUserStorageKey = "layoutUser";
private readonly JsonSerializerOptions _jsonOptions = new(JsonSerializerDefaults.Web);
public async Task<string> EnsureLayoutUserAsync()
{
var layoutUser = await jsRuntime.InvokeAsync<string?>("localStorage.getItem", LayoutUserStorageKey);
if (string.IsNullOrWhiteSpace(layoutUser))
{
layoutUser = Guid.NewGuid().ToString("N");
await jsRuntime.InvokeVoidAsync("localStorage.setItem", LayoutUserStorageKey, layoutUser);
}
return layoutUser;
}
public async Task<BandLayout> LoadBandLayoutAsync(
string layoutType,
string layoutKey,
string layoutUser,
Dictionary<string, ColumnDefinition> columnLookup)
{
if (string.IsNullOrWhiteSpace(layoutUser))
return new BandLayout();
var stored = await layoutApi.GetAsync(layoutType, layoutKey, layoutUser);
if (stored != null && !string.IsNullOrWhiteSpace(stored.LayoutData))
{
var parsed = JsonSerializer.Deserialize<BandLayout>(stored.LayoutData, _jsonOptions);
return NormalizeBandLayout(parsed, columnLookup);
}
return new BandLayout();
}
public async Task SaveBandLayoutAsync(
string layoutType,
string layoutKey,
string layoutUser,
BandLayout bandLayout)
{
var layoutData = JsonSerializer.Serialize(bandLayout, _jsonOptions);
await layoutApi.UpsertAsync(new LayoutDto
{
LayoutType = layoutType,
LayoutKey = layoutKey,
UserName = layoutUser,
LayoutData = layoutData
});
}
public async Task ResetBandLayoutAsync(
string layoutType,
string layoutKey,
string layoutUser)
{
await layoutApi.DeleteAsync(layoutType, layoutKey, layoutUser);
}
public Dictionary<string, string> BuildAssignmentsFromLayout(BandLayout layout)
{
var assignments = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var band in layout.Bands)
{
foreach (var column in band.Columns)
{
assignments[column] = band.Id;
}
}
return assignments;
}
public BandLayout NormalizeBandLayout(
BandLayout? layout,
Dictionary<string, ColumnDefinition> columnLookup)
{
layout ??= new BandLayout();
layout.Bands ??= new List<BandDefinition>();
layout.ColumnOrder ??= new List<string>();
layout.ColumnWidths ??= new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
foreach (var band in layout.Bands)
{
if (string.IsNullOrWhiteSpace(band.Id))
band.Id = Guid.NewGuid().ToString("N");
if (string.IsNullOrWhiteSpace(band.Caption))
band.Caption = "Band";
band.Columns = band.Columns?.Where(columnLookup.ContainsKey).ToList() ?? new List<string>();
}
return layout;
}
}
}