diff --git a/AyCode.Blazor.Components/Components/Grids/MgGridBase.cs b/AyCode.Blazor.Components/Components/Grids/MgGridBase.cs
index 943e7d3..faf1036 100644
--- a/AyCode.Blazor.Components/Components/Grids/MgGridBase.cs
+++ b/AyCode.Blazor.Components/Components/Grids/MgGridBase.cs
@@ -101,6 +101,15 @@ public interface IMgGridBase : IGrid
/// = null clears this field's filter. See ADR 0004.
///
void SetFieldQuickFilter(string fieldName, CriteriaOperator? criteria);
+
+ /// Whether this grid has a quick-filter panel (i.e. FilterPanel is set).
+ bool HasFilterPanel { get; }
+
+ /// Whether the quick-filter panel is currently shown (toggled from the toolbar).
+ bool IsFilterPanelVisible { get; }
+
+ /// Shows/hides the quick-filter panel. No-op when the grid has no filter panel.
+ void ToggleFilterPanel();
}
public abstract class MgGridBase : DxGrid, IMgGridBase, IAsyncDisposable
@@ -251,7 +260,13 @@ public abstract class MgGridBase
- /// Optional quick-filter panel rendered above the grid (typically a GridFilterPanel hosting
- /// controls). Rendered within this grid's
+ /// Optional quick-filter controls (typically ) rendered above the grid
+ /// inside a styled .mg-grid-filter-panel container (the slot wraps the content). Within this grid's
/// cascade, so the controls can reach this grid and apply per-field filters. See ADR 0004.
///
[Parameter] public RenderFragment? FilterPanel { get; set; }
+ /// Initial visibility of the ; the toolbar toggle flips it at runtime. Default: shown.
+ [Parameter] public bool ShowFilterPanel { get; set; } = true;
+
+ ///
+ public bool HasFilterPanel => FilterPanel != null;
+
+ ///
+ public bool IsFilterPanelVisible { get; private set; } = true;
+
+ ///
+ public void ToggleFilterPanel()
+ {
+ IsFilterPanelVisible = !IsFilterPanelVisible;
+ InvokeAsync(StateHasChanged);
+ }
+
///
/// Name for auto-saving/loading grid layout. If not set, defaults to "Grid{TDataItem.Name}"
///
@@ -406,6 +443,8 @@ public abstract class MgGridBase
-/// Base for a grid quick-filter panel — a styled container placed in a grid's FilterPanel slot,
-/// hosting (or other) per-field quick-filter controls above the grid.
-///
-/// Framework base. Projects derive a (typically empty) GridFilterPanel per the Mg* seam policy
-/// (cf. MgGridBase → project grid). The grid reference reaches the child controls via the grid's own
-/// cascade (MgGridBase provides it) — this panel is layout only, no wiring.
-///
-///
-public class MgGridFilterPanelBase : ComponentBase
-{
- /// The quick-filter controls (e.g. ) to render in the panel.
- [Parameter] public RenderFragment? ChildContent { get; set; }
-
- /// Extra CSS classes appended to the panel container.
- [Parameter] public string? CssClass { get; set; }
-
- protected override void BuildRenderTree(RenderTreeBuilder builder)
- {
- builder.OpenElement(0, "div");
- builder.AddAttribute(1, "class", string.IsNullOrWhiteSpace(CssClass)
- ? "mg-grid-filter-panel"
- : $"mg-grid-filter-panel {CssClass}");
- builder.AddContent(2, ChildContent);
- builder.CloseElement();
- }
-}
diff --git a/AyCode.Blazor.Components/Components/Grids/MgGridToolbarTemplate.razor b/AyCode.Blazor.Components/Components/Grids/MgGridToolbarTemplate.razor
index 75885d3..0162ecb 100644
--- a/AyCode.Blazor.Components/Components/Grids/MgGridToolbarTemplate.razor
+++ b/AyCode.Blazor.Components/Components/Grids/MgGridToolbarTemplate.razor
@@ -30,6 +30,7 @@
+
@ToolbarItemsExtended
}
@@ -82,6 +83,16 @@
///
private string FullscreenIconCssClass => IsFullscreenMode ? "grid-fullscreen-exit" : "grid-fullscreen";
+ ///
+ /// Whether the quick-filter panel is currently shown
+ ///
+ private bool IsFilterPanelVisible => Grid?.IsFilterPanelVisible ?? false;
+
+ ///
+ /// Button text for the filter-panel toggle
+ ///
+ private string FilterPanelButtonText => IsFilterPanelVisible ? "Hide Filters" : "Show Filters";
+
protected override async Task OnInitializedAsync()
{
_hasUserLayout = await Grid.HasUserLayoutAsync();
@@ -145,6 +156,11 @@
Grid.ToggleFullscreen();
}
+ void FilterPanel_Click()
+ {
+ Grid.ToggleFilterPanel();
+ }
+
async Task ExportXlsxItem_Click()
{
await Grid.ExportToXlsxAsync(ExportFileName);
diff --git a/AyCode.Blazor.Components/Components/Grids/README.md b/AyCode.Blazor.Components/Components/Grids/README.md
index eab2d31..4e3f772 100644
--- a/AyCode.Blazor.Components/Components/Grids/README.md
+++ b/AyCode.Blazor.Components/Components/Grids/README.md
@@ -4,10 +4,10 @@ Core grid system built on DevExpress `DxGrid`. For the full technical reference
## Key Files
-- **`MgGridBase.cs`** — `MgGridBase`, the main abstract grid component. Extends `DxGrid` with SignalR CRUD, layout persistence, master-detail hierarchy, edit state tracking, fullscreen toggle.
+- **`MgGridBase.cs`** — `MgGridBase`, the main abstract grid component. Extends `DxGrid` with SignalR CRUD, layout persistence, master-detail hierarchy, edit state tracking, fullscreen toggle, and a quick-filter `FilterPanel` slot (renders `MgTagBox` controls above the grid in a styled `.mg-grid-filter-panel` container; toolbar Show/Hide toggle).
- **`MgGridWithInfoPanel.razor`** — `DxSplitter` wrapper: grid (left) + InfoPanel (right), fullscreen overlay, splitter size persistence.
- **`MgGridToolbarBase.cs`** — Extends `DxToolbar` with `Grid`, `RefreshClick`, and `ShowOnlyIcon` parameters.
-- **`MgGridToolbarTemplate.razor`** — Full toolbar template: New/Edit/Delete/Save/Cancel, row navigation, layout menu (Load/Save/Reset), column chooser, export, reload, fullscreen. Extensible via `ToolbarItemsExtended`.
+- **`MgGridToolbarTemplate.razor`** — Full toolbar template: New/Edit/Delete/Save/Cancel, row navigation, layout menu (Load/Save/Reset), column chooser, export, reload, filter-panel toggle (visible only when the grid has a `FilterPanel`), fullscreen. Extensible via `ToolbarItemsExtended`.
- **`MgGridDataColumn.cs`** — Extends `DxGridDataColumn` with InfoPanel parameters (`ShowInInfoPanel`, `InfoPanelOrder`, `InfoPanelDisplayFormat`) and `UrlLink` template with `{Property}` placeholder substitution via compiled accessors.
- **`MgGridInfoPanel.razor`** / **`.razor.cs`** — `MgGridInfoPanel` implementing `IInfoPanelBase`. Responsive column layout (1-4 columns with breakpoints), edit/view mode with typed editors, template system, sticky positioning via JS interop.
- **`MgGridSignalRDataSource.cs`** — `GridCustomDataSource` wrapping `AcSignalRDataSource`. Local cache for seen filter criteria, background refresh.
diff --git a/AyCode.Blazor.Components/Components/MgTagBox.cs b/AyCode.Blazor.Components/Components/MgTagBox.cs
index aea3e5a..85d7692 100644
--- a/AyCode.Blazor.Components/Components/MgTagBox.cs
+++ b/AyCode.Blazor.Components/Components/MgTagBox.cs
@@ -34,32 +34,31 @@ public class MgTagBox : DxTagBox
///
[CascadingParameter] public IMgGridBase? Grid { get; set; }
- private bool _initialized;
- private IEnumerable? _selected;
-
protected override void OnParametersSet()
{
base.OnParametersSet();
- // Own the selection internally so the consumer needs no @bind-Values.
- // COMPILE-VERIFY (DevExpress version specific): Blazor re-applies base.Values / base.ValuesChanged from the
- // consumer markup on every parameter set, so we re-assert ours here each time. First set adopts any initial Values.
- if (!_initialized)
- {
- _selected = Values;
- _initialized = true;
- }
-
- base.Values = _selected;
+ // Route the tag box's selection through our handler so a change applies the grid filter.
+ // (The consumer does not bind Values; the selection is owned internally — see OnSelectionChanged.)
base.ValuesChanged = EventCallback.Factory.Create>(this, OnSelectionChanged);
}
private void OnSelectionChanged(IEnumerable? values)
{
- _selected = values;
- base.Values = values; // reflect the new selection in the editor
+ // DevExpress tracks its parameters; setting Values imperatively (outside markup, e.g. from this event
+ // callback) must be wrapped in BeginUpdate/EndUpdate — otherwise DevExpress throws
+ // "A parameter value is specified outside a component's markup."
+ BeginUpdate();
+ try
+ {
+ Values = values;
+ }
+ finally
+ {
+ EndUpdate();
+ }
+
ApplyQuickFilter(values);
- StateHasChanged();
}
private void ApplyQuickFilter(IEnumerable? values)
@@ -68,8 +67,7 @@ public class MgTagBox : DxTagBox
var selected = values?.Where(v => v is not null).Cast