Architecture Overview

The filtering system is split into two clearly separated responsibilities:

LayerClassResponsibility
UI BuilderBeepFilterRenders filter UI, accepts user input, emits FilterConfiguration via events — never touches data.
Data ApplierFilterEngine<T>Evaluates a FilterConfiguration against an IEnumerable<T> — no UI involved.
ConsumerBeepGridPro / BeepListBoxSubscribes to FilterApplied, calls FilterEngine<T>, updates visible rows/items.
Architecture Diagram
+-----------------------------+
|         BeepFilter          |  UI only: takes EntityStructure,
|       (UI Builder)          |  shows filter widgets (TagPills, etc.)
|                             |  Raises FilterApplied event -----+
+-----------------------------+                                  |
                                                                 | FilterConfiguration
                                              +------------------v-----------+
                                              |    BeepGridPro / BeepListBox |
                                              |  (Consumer)                  |
                                              |  Uses FilterEngine<T>        |
                                              +------------------------------+
Key Design Principle: BeepFilter never sees your data. It generates a FilterConfiguration and fires an event. The consuming control decides how and when to apply it. This makes BeepFilter reusable with any data source.

BeepFilter Control

Inherits BaseControl. Hosts one active IFilterPainter which handles all rendering and layout. Switching FilterStyle hot-swaps the painter at runtime.

Key Private Fields

FieldTypePurpose
_filterStyleFilterStyleCurrent interaction pattern (default: TagPills).
_activePainterIFilterPainter?Live painter instance; replaced when style changes.
_activeFilterFilterConfigurationLiving filter being built by the user.
_hitAreasList<FilterHitArea>All clickable regions registered by the painter.
_keyboardHandlerFilterKeyboardHandler?Handles keyboard shortcuts (Add, Clear, Navigate).
_suggestionProviderFilterSuggestionProvider?Provides recent/frequent value suggestions.
_validationHelperFilterValidationHelper?Validates criteria type/operator compatibility.
_autocompletePopupFilterAutocompletePopup?Created on demand for value input autocomplete.
_inlineValueEditorBeepTextBox?Inline text editor that appears over a criterion.

Properties

PropertyTypeDefaultDescription
FilterStyleFilterStyleTagPillsChanges the painter; triggers layout recalculation + FilterStyleChanged event.
DisplayModeFilterDisplayModeAlwaysVisibleWhether filter bar is always visible, collapsible, shown on hover, or modal.
PositionFilterPositionTopPosition hint for parent layout (Top, Bottom, Left, Right, Floating).
ActiveFilterFilterConfigurationempty listThe current filter being built; consumer reads this in FilterApplied handler.
EntityStructureEntityStructurenullProvides column names and types for the field selector dropdowns.
DataSourceobjectnullOptional data source forwarded to FilterSuggestionProvider for value hints.
IsExpandedbooltrueControls collapsed/expanded state for Collapsible display mode.
FilterCountint0Read-only count of enabled criteria; useful for badge display.

Initialization Sequence

BeepFilter constructor pipeline
BeepFilter()
  : base()
    |- InitializeComponent()          // Size=600x45, DoubleBuffered, ApplyThemeToChilds=false
    +- InitializeFilterControl()
         |- UpdatePainter()            // Creates TagPillsFilterPainter (default)
         |- _activeFilter = new FilterConfiguration { Criteria = [] }
         |- InitializePhase1Components()
         |     |- _keyboardHandler = new FilterKeyboardHandler(this)
         |     |- _suggestionProvider = new FilterSuggestionProvider()
         |     +- _validationHelper  = new FilterValidationHelper()
         +- RecalculateLayout()

Filter Styles

FilterStyle controls the interaction pattern — how the user builds criteria. Visual colors, borders, and spacing always come from BeepStyling / BeepControlStyle, not from the style enum.

ValuePainter ClassVisual PatternBest For
TagPills TagPillsFilterPainter Horizontal row of rounded pill chips with × remove button; drag handles for reordering. Simple, at-a-glance active filters bar.
GroupedRows GroupedRowsFilterPainter One row per criterion with AND/OR connectors; Add Filter / Add Group buttons; indented subgroups. Multi-condition structured queries.
QueryBuilder QueryBuilderFilterPainter Visual logic tree with If/Else branches; Prompt vs Logic toggle. Complex nested conditions and business rules.
DropdownMultiSelect DropdownMultiSelectFilterPainter Field / Operator / Value dropdowns; multi-select checkboxes; active count badge. User-friendly quick filtering with fixed column sets.
InlineRow InlineRowFilterPainter Single compact row: Column | Operator | Value. Minimal spacing. Space-constrained panels; table header filter rows.
SidebarPanel SidebarPanelFilterPainter Vertical list of collapsible filter categories with checkboxes/toggles. Apply/Clear at bottom. E-commerce / catalog browsing style.
QuickSearch QuickSearchFilterPainter Single search bar with column selector. Real-time filtering. Filter count badge. Fast free-text search across all columns.
AdvancedDialog AdvancedDialogFilterPainter Modal or slide-in panel with tabs; Save/Load config; result count preview. Power users; reusable named filter sets.

Switching Styles at Runtime

C# — runtime style switch
// Painter is hot-swapped; existing ActiveFilter is preserved
beepFilter.FilterStyle = FilterStyle.GroupedRows;

// Get description from factory
string desc = FilterPainterFactory.GetStyleDescription(FilterStyle.QuickSearch);
// "Single search bar with instant filtering - quick and easy"

bool ready = FilterPainterFactory.IsFullyImplemented(FilterStyle.AdvancedDialog); // true

Data Models

FilterCriteria

One condition in the filter: ColumnName Operator Value.

PropertyTypeDescription
ColumnNamestringColumn / property to filter on.
OperatorFilterOperatorComparison operator (one of 17).
ValueobjectPrimary comparison value.
Value2objectUpper bound for Between / NotBetween.
CaseSensitiveboolCase-sensitive text comparison. Default false.
IsEnabledboolSkip this criterion when false without removing it. Default true.
C# — creating criteria
// Simple
var c1 = new FilterCriteria("Country", FilterOperator.Equals, "Germany");

// Range
var c2 = new FilterCriteria("Price", FilterOperator.Between, 10.0, 50.0);

// Case-sensitive text
var c3 = new FilterCriteria("Code", FilterOperator.StartsWith, "ABC", caseSensitive: true);

// Disabled (remembered but not evaluated)
c3.IsEnabled = false;

FilterConfiguration

A named, serialisable collection of FilterCriteria with AND / OR logic.

PropertyTypeDescription
NamestringHuman-readable name for saving / loading.
DescriptionstringOptional description.
CriteriaList<FilterCriteria>Ordered list of conditions.
LogicFilterLogicAnd (all must match) or Or (any match). Default And.
IsActiveboolWhether this configuration is currently applied.
CreatedDateDateTimeAuto-set on construction.
ModifiedDateDateTimeShould be updated on every change.
C# — building a configuration
var config = new FilterConfiguration("Q1 EU Customers")
{
    Logic = FilterLogic.And
};
config.AddCriteria(new FilterCriteria("Region",  FilterOperator.Equals,      "EU"));
config.AddCriteria(new FilterCriteria("Revenue", FilterOperator.GreaterThan, 50000));

// Serialize / deserialize (System.Text.Json compatible)
string json   = JsonSerializer.Serialize(config);
var    loaded = JsonSerializer.Deserialize<FilterConfiguration>(json);

Filter Operators

17 operators covering string, numeric, date, null-check, regex, and set membership comparisons.

OperatorDisplay NameApplicable ToNotes
Equals=All typesExact match; case-insensitive for strings unless CaseSensitive=true.
NotEquals≠All typesInverse of Equals.
ContainscontainsStringSubstring search. Case-insensitive by default.
NotContainsdoes not containStringInverse of Contains.
StartsWithstarts withStringPrefix match.
EndsWithends withStringSuffix match.
GreaterThan>Numeric, DateStrict greater than.
GreaterThanOrEqual≥Numeric, Date
LessThan<Numeric, DateStrict less than.
LessThanOrEqual≤Numeric, Date
BetweenbetweenNumeric, DateInclusive. Requires both Value and Value2.
NotBetweennot betweenNumeric, DateInverse of Between.
IsNullis emptyAll typesMatches null or empty string.
IsNotNullis not emptyAll typesInverse of IsNull.
Regexmatches patternStringFull .NET regex pattern.
Inis one ofAll typesValue should be IEnumerable or comma-separated string.
NotInis not one ofAll typesInverse of In.

FilterLogic

ValueMeaning
FilterLogic.AndAll enabled criteria must match (default — most restrictive).
FilterLogic.OrAt least one enabled criterion must match.

FilterEngine<T>

Generic, reflection-based engine that evaluates a FilterConfiguration against any IEnumerable<T>. Property info is cached on construction for performance.

Core Methods

MethodReturnsDescription
ApplyFilter(FilterConfiguration config) IEnumerable<T> Filters data source using all enabled criteria. Returns full source if config is null or has no enabled criteria.
ApplyQuickFilter(string text, string? prop) IEnumerable<T> Fast substring search. Pass null for prop to search all properties.
ApplyFilterGetIndices(FilterConfiguration config) List<int> Returns 0-based indices of matched items. Useful for grid row hiding without rebuilding collections.
C# — applying a filter in a consumer
// Works with any typed collection
var engine = new FilterEngine<Customer>(customers);

// Works with ExpandoObject (BeepGridPro uses this internally)
var engine = new FilterEngine<ExpandoObject>(gridRows);

// Option A: get filtered rows
var visible = engine.ApplyFilter(config).ToList();

// Option B: get matching indices (faster for large virtual grids)
var indices = engine.ApplyFilterGetIndices(config);
HideRowsNotIn(indices);

// Quick search across all properties
var results = engine.ApplyQuickFilter("Smith", propertyName: null);

Painter System

IFilterPainter Interface

Every filter style is rendered by a dedicated painter. Painters return FilterLayoutInfo (all element rectangles) and paint using BeepStyling helpers for colors and borders.

MemberTypeDescription
FilterStyleFilterStyleThe style this painter handles.
SupportsAnimationsboolWhether the painter supports animated transitions.
SupportsDragDropboolWhether drag-and-drop reordering is supported.
CalculateLayout(BeepFilter, Rectangle)FilterLayoutInfoMeasures and positions all UI elements; registers hit areas.
Paint(Graphics, BeepFilter, FilterLayoutInfo)voidRenders the filter UI using the supplied layout.

Painter Implementations

ClassStyleDragDropSpecial Features
TagPillsFilterPainterTagPillsYesPill height 32px, corner radius 16, max 10/row; wraps to new row; remove × per pill.
GroupedRowsFilterPainterGroupedRowsNoAND/OR connector labels; Add Filter + Add Group buttons; indented subgroups.
QueryBuilderFilterPainterQueryBuilderNoVisual If/Else tree; Prompt vs Logic mode toggle.
DropdownMultiSelectFilterPainterDropdownMultiSelectNoThree-column layout: field | operator | value; count badge.
InlineRowFilterPainterInlineRowNoSingle-height, minimal padding; + / − buttons at row end.
SidebarPanelFilterPainterSidebarPanelNoVertical collapsible sections; fixed Apply/Clear footer.
QuickSearchFilterPainterQuickSearchNoSearch icon + text box + column dropdown; real-time debounce.
AdvancedDialogFilterPainterAdvancedDialogNoModal layout; tabs; Save/Load named presets; record count preview.

FilterLayoutInfo

All geometry calculated by a painter — no pixel math in the control itself.

PropertyDescription
ContainerRect / ContentRect / HeaderRect / FooterRectPrimary layout regions.
TagRects[] / RemoveButtonRects[]Per-criterion pill and × button rectangles (TagPills).
RowRects[] / ConnectorRects[] / DragHandleRects[]GroupedRows / row-style layout arrays.
FieldDropdownRects[] / OperatorDropdownRects[] / ValueDropdownRects[]Dropdown column rectangles.
AddFilterButtonRect / AddGroupButtonRect / ClearAllButtonRectAction button rectangles.
SaveButtonRect / LoadButtonRect / ApplyButtonRectPersistence / submit button rectangles.
SearchInputRect / SearchIconRectQuickSearch widget rectangles.
CountBadgeRectFilter count badge position.

Hit Testing

Painters register FilterHitArea objects during CalculateLayout(). Mouse events resolve to a typed FilterHitAreaType which drives all interaction logic without duplicating coordinate math.

TypeTriggered When
FilterTagClick on a pill/row (select or begin edit).
RemoveButtonClick on × button of a criterion.
EditButtonClick on pencil/edit icon.
AddFilterButtonClick “+ Add filter” button.
AddGroupButtonClick “+ Add group” button (GroupedRows).
LogicConnectorClick AND/OR toggle connector.
FieldDropdownClick column selector dropdown.
OperatorDropdownClick operator selector dropdown.
ValueInputClick value text field.
ValueDropdownClick value dropdown.
DragHandleMouse-down on pill drag grip (TagPills).
CollapseButtonClick section header collapse chevron (SidebarPanel).
SearchInputClick into search text box (QuickSearch).
ClearAllButtonClick “Clear All” button.
SaveButton / LoadButtonPersistence actions in AdvancedDialog.
ApplyButtonClick “Apply” → raises FilterApplied event.

Events

Core Filter Events

EventArgs TypeWhen Raised
FilterAddedEventArgsA new FilterCriteria is added to ActiveFilter.
FilterRemovedFilterRemovedEventArgsA criterion is removed; args carry the removed criterion.
FilterModifiedFilterChangedEventArgsA criterion is edited (operator or value changed).
FiltersClearedEventArgsAll criteria cleared via “Clear All”.
FilterChangedEventArgsAny change to ActiveFilter (fired after all of the above).
FilterEditRequestedFilterEditEventArgsUser clicks edit on a criterion (open inline editor or dialog).
FilterAppliedEventArgsUser clicks Apply. Consuming control reads ActiveFilter here.
FilterCanceledEventArgsUser cancels dialog interaction.

Group & Interaction Events

EventDescription
GroupAddedA filter group (subgroup) is added to the GroupedRows or QueryBuilder view.
GroupRemovedA filter group is removed; args carry group identifier.
FieldSelectionRequestedUser clicks field/column dropdown; host can provide a custom picker.
OperatorSelectionRequestedUser clicks operator dropdown.
ValueInputRequestedUser activates value input; host can show a date picker or custom dialog.
FilterDragStartedDrag-and-drop reorder begins (TagPills with DragDrop enabled).
FilterStyleChangedPainter swapped due to FilterStyle property change.
ConfigurationSaveRequestedUser wants to save current config by name (AdvancedDialog).
ConfigurationLoadRequestedUser wants to load a saved config (AdvancedDialog).

Helper Components

FilterSuggestionProvider

Analyses the data source and provides smart value suggestions: recent values, most frequent values, and partial-text matches. Results are cached for 5 minutes.

C# — suggestions
var provider = new FilterSuggestionProvider();
provider.SetDataSource(myList);  // invalidates cache

var suggestions = provider.GetSuggestions(
    columnName: "Country", partialValue: "Ge", maxResults: 10);
// Returns: Recent (star), Frequent (fire), Partial-match
// Each item: Value, DisplayText, MatchCount, Relevance, Type

FilterValidationHelper

Validates operator/type compatibility and value requirements before criteria are committed.

C# — validation
var helper = new FilterValidationHelper();
var result = helper.Validate(criteria, DbFieldCategory.Numeric);
if (!result.IsValid)
    ShowError(result.ErrorMessage);
// Guards: null column, operator/type mismatch,
//         missing Value2 for Between, type incompatibility

FilterKeyboardHandler

Attached to the BeepFilter control; provides keyboard shortcuts for adding, clearing, and navigating criteria.

FilterAutocompletePopup

Popup created on demand when a value input is focused. Shows FilterSuggestion items. Dismissed on Escape or selection.

FilterIconProvider

Returns icon glyphs (Bootstrap Icons / strings) for operator symbols and filter actions, keeping painter code free of icon literals.

FilterPainterMetrics / IFilterPainterMetricsProvider

Provides computed layout metrics (heights, widths, paddings) derived from BeepStyling so all painters share a single source of truth for spacing.

Enums Reference

FilterDisplayMode

ValueBehavior
AlwaysVisibleFilter bar is always shown.
CollapsibleCan be collapsed; controlled by IsExpanded.
OnHoverShown only when the user hovers over the host area.
ModalAppears in a floating overlay (AdvancedDialog style).

FilterPosition

ValueHint For
TopAbove data view (default).
BottomBelow data view.
LeftLeft sidebar (SidebarPanel style).
RightRight sidebar.
FloatingDetached floating panel.

FilterHitAreaType (18 values)

None, FilterTag, RemoveButton, EditButton, AddFilterButton, AddGroupButton, LogicConnector, FieldDropdown, OperatorDropdown, ValueInput, ValueDropdown, DragHandle, CollapseButton, SearchInput, ClearAllButton, SaveButton, LoadButton, ApplyButton.

Usage Patterns

Pattern 1: Standalone BeepFilter

C# — standalone, apply to any data source
var filter = new BeepFilter
{
    EntityStructure = myEntityStructure,
    FilterStyle     = FilterStyle.TagPills,
    DataSource      = myList,         // optional: enables suggestions
    Dock            = DockStyle.Top
};

filter.FilterApplied += (s, e) =>
{
    var engine  = new FilterEngine<Product>(allProducts);
    var results = engine.ApplyFilter(filter.ActiveFilter);
    listBox1.DataSource = results.ToList();
};

panel.Controls.Add(filter);

Pattern 2: Wired to BeepGridPro

C# — filter drives a grid
var beepFilter = new BeepFilter
{
    EntityStructure = grid.EntityStructure,
    FilterStyle     = FilterStyle.GroupedRows,
    Dock            = DockStyle.Top
};

beepFilter.FilterApplied += (s, e) =>
{
    // Grid's ActiveFilter setter calls FilterEngine<ExpandoObject> internally
    grid.ActiveFilter = beepFilter.ActiveFilter;
};

// Or use the grid's built-in integration:
grid.ShowAdvancedFilterDialog();   // opens AdvancedDialog style
grid.ApplyQuickFilter("Smith");    // internal QuickSearch

Pattern 3: Programmatic Filter (no UI)

C# — direct FilterEngine usage
var config = new FilterConfiguration();
config.AddCriteria(new FilterCriteria("Status", FilterOperator.Equals,            "Active"));
config.AddCriteria(new FilterCriteria("Score",  FilterOperator.GreaterThanOrEqual, 80));
config.Logic = FilterLogic.And;

var engine  = new FilterEngine<Employee>(employees);
var visible = engine.ApplyFilter(config).ToList();

// Get just the indices (faster for large virtual lists)
var indices = engine.ApplyFilterGetIndices(config);

Pattern 4: Save / Load Named Configurations

C# — persist filter presets
// Save
beepFilter.ConfigurationSaveRequested += (s, e) =>
{
    var config = beepFilter.ActiveFilter;
    config.Name = "EU High-Value Q1";
    File.WriteAllText("filters/eu-q1.json", JsonSerializer.Serialize(config));
};

// Load
beepFilter.ConfigurationLoadRequested += (s, e) =>
{
    var json   = File.ReadAllText("filters/eu-q1.json");
    var config = JsonSerializer.Deserialize<FilterConfiguration>(json)!;
    beepFilter.ActiveFilter = config;
    beepFilter.Invalidate();
};

BeepQueryandFilter (Legacy)

Older, simpler control in the root Controls namespace. Uses a TableLayoutPanel with one row per field from an EntityStructure.

MemberDescription
CreateQueryControls(EntityStructure)Generates one label + input per field; range inputs for range operators.
SubmitClickedEvent raised when user clicks Submit; consumer reads AppFilter list.
Prefer BeepFilter for new code. BeepQueryandFilter predates the painter architecture; it does not support themes, painter styles, or the FilterEngine<T> pipeline.

File Organisation

FilePurpose
FilterStyle.csFilterStyle, FilterDisplayMode, FilterPosition enums.
FilterOperator.csFilterOperator, FilterLogic enums + FilterOperatorExtensions.GetDisplayName().
FilterCriteria.csFilterCriteria and FilterConfiguration model classes.
FilterEngine.csGeneric FilterEngine<T> with ApplyFilter, ApplyQuickFilter, ApplyFilterGetIndices.
IFilterPainter.csIFilterPainter, FilterHitArea, FilterHitAreaType, FilterLayoutInfo.
BaseFilterPainter.csAbstract base painter; shared paint/layout helpers.
FilterPainterFactory.csFilterStyle → IFilterPainter factory with descriptions.
FilterPainterMetrics.csShared layout metrics interface + default implementation.
BeepFilter.csMain control: constructor, painter management, paint override, mouse handling.
BeepFilter.Properties.csAll public properties and change handlers.
BeepFilter.Events.csAll event definitions and On* raising methods.
BeepFilter.HitTest.csMouse coordinate → FilterHitAreaType dispatch.
BeepFilter.Layout.csRecalculateLayout() dispatcher → active painter.
FilterKeyboardHandler.csKeyboard shortcut handler attached to the control.
FilterSuggestionProvider.csRecent/frequent value suggestion engine with 5-min cache.
FilterValidationHelper.csOperator/type compatibility validation → FilterValidationResult.
FilterAutocompletePopup.csOn-demand popup for value autocomplete during typing.
FilterIconProvider.csIcon glyph provider for operators and actions.
Painters/TagPillsFilterPainter.csTagPills: pill height 32px, corner radius 16, max 10/row, drag-drop reorder.
Painters/GroupedRowsFilterPainter.csGroupedRows: AND/OR connectors, subgroups.
Painters/QueryBuilderFilterPainter.csQueryBuilder: If/Else tree branching.
Painters/DropdownMultiSelectFilterPainter.csDropdownMultiSelect: 3-column dropdown, multi-select checkboxes.
Painters/InlineRowFilterPainter.csInlineRow: compact single-row per criterion.
Painters/SidebarPanelFilterPainter.csSidebarPanel: vertical collapsible categories.
Painters/QuickSearchFilterPainter.csQuickSearch: search bar with real-time debounce.
Painters/AdvancedDialogFilterPainter.csAdvancedDialog: modal, tabs, Save/Load, result count.
BeepQueryandFilter.csLegacy query control (TableLayoutPanel, AppFilter based).

Best Practices

  1. Never apply filters inside BeepFilter. Always subscribe to FilterApplied and use FilterEngine<T> in the consuming control.
  2. Disable criteria instead of removing them. Set IsEnabled = false to temporarily skip without losing the configuration.
  3. Use ApplyFilterGetIndices() for large grids. Returning indices avoids creating a new collection and maps directly to row-hiding in virtual grids.
  4. Set EntityStructure before FilterStyle. Painters read column definitions during CalculateLayout(); setting style first may paint an empty field list.
  5. Provide DataSource for smart suggestions. FilterSuggestionProvider can only generate recent/frequent hints when BeepFilter.DataSource is set.
  6. Call RecalculateLayout() + Invalidate() after programmatic changes. Internal layout recalculates only on user interaction and property changes, not on direct ActiveFilter.Criteria.Add() calls.
  7. Serialize FilterConfiguration with System.Text.Json. All properties are plain types — no custom converters needed for basic save/load.
  8. Avoid BeepQueryandFilter in new code. It does not integrate with the theming system, painter architecture, or FilterEngine<T> pipeline.