Files
radzen-blazor/Radzen.Blazor/Spreadsheet/TableDesignPanel.razor
Atanas Korchev 04163ceb3e Fix CellMenu filter dropdown ignoring other-column filters, plus minor cleanups
The autofilter dropdown for column X is supposed to list distinct values
visible under filters on OTHER columns (Excel parity). The old implementation
used filter.Range.Contains(row, Column) to decide which filters target the
current column — but filters created from FilterDialog use the full table /
auto-filter range, so Range.Contains returns true for EVERY filter at any
cell in that range. Result: ALL filters were treated as 'for this column'
and the dropdown ignored every cross-column filter.

Introduce FilterColumnVisitor (extends FilterCriterionVisitorBase) that
extracts the target column(s) via the existing visitor pattern. The new
IsRowHiddenByOtherColumnFilter helper skips filters that target only the
current column and re-evaluates the criterion via Matches(worksheet, row).
Adds 4 tests covering: no filters, only-current-column filter ignored,
other-column filter applied, two-column cross-visibility.

Also covers:
- TableDesignPanel TotalsOptions cached in OnInitialized (was allocating
  an object[] per render).
- CellMenu HasFilterApplied collapsed from a foreach to a one-liner LINQ.
- CellMenuItem.razor: Click.InvokeAsync(null) -> Click.InvokeAsync().
2026-06-15 18:45:16 +03:00

238 lines
10 KiB
Plaintext

@using System.Collections.Generic
@using System.Linq
@using Radzen.Blazor
@using Radzen.Documents.Spreadsheet
@implements IDisposable
@inject Localizer Localizer
@if (CurrentTable is not null)
{
<RadzenStack Orientation="Orientation.Horizontal" Gap="0.75rem" AlignItems="AlignItems.Center"
class="rz-spreadsheet-table-design">
<RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" Gap="0.25rem">
<RadzenLabel Text=@L(nameof(RadzenStrings.Spreadsheet_TableDesignName)) Component="tableNameInput" />
<RadzenTextBox @bind-Value=@tableName Name="tableNameInput"
Change=@(_ => CommitName()) InputSize="InputSize.Small" Style="width:160px" />
</RadzenStack>
<RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" Gap="0.25rem">
<RadzenLabel Text=@L(nameof(RadzenStrings.Spreadsheet_TableDesignStyle)) Component="tableStyleDD" />
<RadzenDropDown TValue="string" Data=@styleOptions
Value=@(CurrentTable.TableStyle ?? "")
Change=@(v => CommitStyle(v as string))
Name="tableStyleDD" InputSize="InputSize.Small" Style="width:200px" />
</RadzenStack>
<RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" Gap="0.75rem">
<RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" Gap="0.25rem">
<RadzenCheckBox Value=@CurrentTable.ShowHeaderRow
Change=@(v => Toggle(t => t.ShowHeaderRow = (bool)v)) TValue="bool"
Name="hdrChk" />
<RadzenLabel Text=@L(nameof(RadzenStrings.Spreadsheet_TableDesignHeader)) Component="hdrChk" />
</RadzenStack>
<RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" Gap="0.25rem">
<RadzenCheckBox Value=@CurrentTable.ShowTotals
Change=@(v => Toggle(t => t.ShowTotals = (bool)v)) TValue="bool"
Name="totChk" />
<RadzenLabel Text=@L(nameof(RadzenStrings.Spreadsheet_TableDesignTotals)) Component="totChk" />
</RadzenStack>
<RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" Gap="0.25rem">
<RadzenCheckBox Value=@CurrentTable.ShowFilterButton
Change=@(v => Toggle(t => t.ShowFilterButton = (bool)v)) TValue="bool"
Disabled=@(!CurrentTable.ShowHeaderRow)
Name="fltChk" />
<RadzenLabel Text=@L(nameof(RadzenStrings.Spreadsheet_TableDesignFilter)) Component="fltChk" />
</RadzenStack>
<RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" Gap="0.25rem">
<RadzenCheckBox Value=@CurrentTable.ShowBandedRows
Change=@(v => Toggle(t => t.ShowBandedRows = (bool)v)) TValue="bool"
Name="bndRowChk" />
<RadzenLabel Text=@L(nameof(RadzenStrings.Spreadsheet_TableDesignBandedRows)) Component="bndRowChk" />
</RadzenStack>
<RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" Gap="0.25rem">
<RadzenCheckBox Value=@CurrentTable.ShowBandedColumns
Change=@(v => Toggle(t => t.ShowBandedColumns = (bool)v)) TValue="bool"
Name="bndColChk" />
<RadzenLabel Text=@L(nameof(RadzenStrings.Spreadsheet_TableDesignBandedColumns)) Component="bndColChk" />
</RadzenStack>
<RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" Gap="0.25rem">
<RadzenCheckBox Value=@CurrentTable.HighlightFirstColumn
Change=@(v => Toggle(t => t.HighlightFirstColumn = (bool)v)) TValue="bool"
Name="firstChk" />
<RadzenLabel Text=@L(nameof(RadzenStrings.Spreadsheet_TableDesignFirstColumn)) Component="firstChk" />
</RadzenStack>
<RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" Gap="0.25rem">
<RadzenCheckBox Value=@CurrentTable.HighlightLastColumn
Change=@(v => Toggle(t => t.HighlightLastColumn = (bool)v)) TValue="bool"
Name="lastChk" />
<RadzenLabel Text=@L(nameof(RadzenStrings.Spreadsheet_TableDesignLastColumn)) Component="lastChk" />
</RadzenStack>
</RadzenStack>
@if (TotalsColumn is { } totalsCol)
{
<RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" Gap="0.25rem">
<RadzenLabel Text=@string.Format(System.Globalization.CultureInfo.CurrentCulture, L(nameof(RadzenStrings.Spreadsheet_TableDesignTotalsForColumn)), totalsCol.Name) />
<RadzenDropDown TValue="TotalsCalculation" Data=@TotalsOptions
TextProperty="@nameof(TotalsOption.Text)" ValueProperty="@nameof(TotalsOption.Value)"
Value=@totalsCol.TotalsCalculation
Change=@(v => CommitTotals(totalsCol, (TotalsCalculation)v))
InputSize="InputSize.Small" Style="width:180px" />
</RadzenStack>
}
<span style="flex:1"></span>
<RadzenButton Icon="grid_off" Text=@L(nameof(RadzenStrings.Spreadsheet_ConvertTableToRange))
ButtonStyle="ButtonStyle.Base" Variant="Variant.Flat" Size="ButtonSize.Small"
Click=@OnConvertToRange />
</RadzenStack>
}
@code {
#nullable enable
[Parameter] public Worksheet? Worksheet { get; set; }
[CascadingParameter] public ISpreadsheet? Spreadsheet { get; set; }
private string L(string key) => Localizer.Get(key, System.Globalization.CultureInfo.CurrentUICulture);
private Table? CurrentTable
{
get
{
if (Worksheet is null) return null;
var sel = Worksheet.Selection.Cell;
if (sel == CellRef.Invalid) return null;
return Worksheet.GetTableContaining(sel.Row, sel.Column);
}
}
private string tableName = "";
private TableColumn? TotalsColumn
{
get
{
var t = CurrentTable;
if (t is null || !t.ShowTotals || Worksheet is null) return null;
var sel = Worksheet.Selection.Cell;
if (sel == CellRef.Invalid) return null;
var totals = t.TotalsRowRange;
if (totals is null || !totals.Value.Contains(sel.Row, sel.Column)) return null;
var idx = sel.Column - t.Range.Start.Column;
return idx >= 0 && idx < t.Columns.Count ? t.Columns[idx] : null;
}
}
private sealed record TotalsOption(string Text, TotalsCalculation Value);
private TotalsOption[] TotalsOptions = [];
protected override void OnInitialized()
{
TotalsOptions =
[
new(L(nameof(RadzenStrings.Spreadsheet_TotalsNone)), TotalsCalculation.None),
new(L(nameof(RadzenStrings.Spreadsheet_TotalsSum)), TotalsCalculation.Sum),
new(L(nameof(RadzenStrings.Spreadsheet_TotalsAverage)), TotalsCalculation.Average),
new(L(nameof(RadzenStrings.Spreadsheet_TotalsCount)), TotalsCalculation.Count),
new(L(nameof(RadzenStrings.Spreadsheet_TotalsCountNumbers)), TotalsCalculation.CountNumbers),
new(L(nameof(RadzenStrings.Spreadsheet_TotalsMin)), TotalsCalculation.Min),
new(L(nameof(RadzenStrings.Spreadsheet_TotalsMax)), TotalsCalculation.Max),
new(L(nameof(RadzenStrings.Spreadsheet_TotalsStdDev)), TotalsCalculation.StdDev),
new(L(nameof(RadzenStrings.Spreadsheet_TotalsVar)), TotalsCalculation.Var),
];
}
private void CommitTotals(TableColumn col, TotalsCalculation calc)
{
col.TotalsCalculation = calc;
StateHasChanged();
}
private static readonly List<string> styleOptions = BuildStyleOptions();
private static List<string> BuildStyleOptions()
{
var list = new List<string> { "" };
for (var i = 1; i <= 21; i++) list.Add($"TableStyleLight{i}");
for (var i = 1; i <= 28; i++) list.Add($"TableStyleMedium{i}");
for (var i = 1; i <= 11; i++) list.Add($"TableStyleDark{i}");
return list;
}
public override async Task SetParametersAsync(ParameterView parameters)
{
if (Worksheet?.Selection is not null)
{
Worksheet.Selection.Changed -= OnSelectionChanged;
}
await base.SetParametersAsync(parameters);
if (Worksheet?.Selection is not null)
{
Worksheet.Selection.Changed += OnSelectionChanged;
}
SyncFromTable();
}
private void OnSelectionChanged()
{
SyncFromTable();
StateHasChanged();
}
private void SyncFromTable()
{
tableName = CurrentTable?.Name ?? "";
}
private void CommitName()
{
if (CurrentTable is null) return;
var trimmed = tableName.Trim();
if (string.IsNullOrEmpty(trimmed) || trimmed == CurrentTable.Name) return;
try { CurrentTable.Name = trimmed; }
catch (System.ArgumentException) { tableName = CurrentTable.Name; }
StateHasChanged();
}
private void CommitStyle(string? value)
{
if (CurrentTable is null) return;
CurrentTable.TableStyle = string.IsNullOrEmpty(value) ? null : value;
StateHasChanged();
}
private void Toggle(System.Action<Table> mutator)
{
if (CurrentTable is null) return;
mutator(CurrentTable);
StateHasChanged();
}
private async Task OnConvertToRange()
{
if (Worksheet is null || CurrentTable is null || Spreadsheet is null) return;
await Spreadsheet.ExecuteAsync(new RemoveTableCommand(Worksheet, CurrentTable));
StateHasChanged();
}
public void Dispose()
{
if (Worksheet?.Selection is not null)
{
Worksheet.Selection.Changed -= OnSelectionChanged;
}
}
}