Sorting, filtering and search
Sorting and filtering are index projections in front of your data: they reorder and hide row numbers and never copy an item. The user drives them from the header; your code reaches the same state through the view and grid.Api.Filters.
Sorting
A click on a header cycles ascending, descending, off; Shift+click adds the column as a further level. A column sorts only if it has a sort key — the typed factories set one, Column.Text needs .Sortable(key). The key is what is compared, so it can differ from what is shown.
Column.Text<Order>("Customer", o => o.Customer)
.WithKey("customer")
.Sortable(o => o.Customer); // compared as strings, in the sort culture
Column.Text<Order>("Order no.", o => $"A-{o.Id}")
.WithKey("number")
.Sortable(o => o.Id) // shown as text, sorted as a number
.SortWith<int>((a, b) => (a % 1000).CompareTo(b % 1000)); // or by your own ruleint amount = view.ColumnIdOfKey("amount");
int region = view.ColumnIdOfKey("region");
view.SortBy(region, SortDirection.Ascending);
view.SortBy(amount, SortDirection.Descending, additive: true); // second level, like Shift+click
var state = view.SortState; // (ElementId, Direction) per level
view.ClearSort();Text is compared in a sort culture, not ordinally, so umlauts and accented letters land where a reader expects them. The culture can be set per view, per grid through GridContext, or process-wide:
view.SortCulture = CultureInfo.GetCultureInfo("sv-SE"); // this view: Swedish collation
SortFilterSource.SortCulture = CultureInfo.GetCultureInfo("de-DE"); // process-wide defaultgrid.CanUserSortColumns = false takes the gesture away and leaves sorting by code.
The filter row
Under the headers, a drawn row of filter boxes — one per filterable column. Typing filters after a short pause (300 ms), not on every keystroke. Each column has an operator, chosen by its data type: text columns offer Contains (the default), StartsWith, EndsWith, Equals and NotEquals; number and date columns offer Equals (the default), NotEquals, GreaterThan, GreaterOrEqual, LessThan and LessOrEqual, and compare values, not text. While any filter is active, the corner at the left of the filter row clears all of them.
AsDataGrid() switches the filter row on, WithFilterRow() does it alone, and ShowFilterRow on the control toggles it at runtime. Every column with display text is filterable by default; .NotFilterable() takes a column out.
Value filters
The funnel in a header opens a checklist of the column's distinct values with their counts, like a spreadsheet's auto-filter. Very long lists are cut and say so — search inside the pane to reach the rest. For date columns the list nests year, month and day, or year, quarter, month and day (DateFilterGrouping), which the user can switch in the pane.
With a server source, the values and counts come from the server, not from the pages that happen to be loaded.
Filtering from code
int customer = view.ColumnIdOfKey("customer");
int amount = view.ColumnIdOfKey("amount");
int region = view.ColumnIdOfKey("region");
view.SetColumnFilter(customer, "nord"); // the filter-row text
view.SetFilterOperator(amount, FilterOperator.GreaterThan);
view.SetColumnFilter(amount, "1000");
view.SetValueFilter(region, ["North", "East"]); // the funnel's checklist
view.SetQuickFilter(o => ((Order)o).Status != Status.OnHold); // your own predicate, ANDed
view.ClearAllFilters(); // column, value and quick filterSetQuickFilter is for filters the grid does not draw — a toolbar toggle, a search box of your own. It is combined with the column filters by AND. HasAnyFilter, ActiveFilterCount and VisibleDataCount answer a status line.
Saving and restoring filters
GridFilterState is the complete filter state as a plain object: filter-row texts and operators, value lists and the search text, keyed by column key. It does not depend on column order or visibility, so it survives a user moving columns.
GridFilterState saved = grid.Api.Filters.Capture(); // texts, operators, value lists, search
grid.Api.Filters.Clear();
grid.Api.Filters.Apply(saved); // back, in one batch
var state = new GridFilterState();
state.Texts["customer"] = "nord"; // keyed by column key, not position
state.Values["region"] = ["North"];
grid.Api.Filters.Apply(state);
grid.Api.Filters.Changed += () => Log("filters changed");Columns without a key cannot be addressed and are left out. On the controls, FilterState is the same object as a bindable property. To keep filters together with columns, sorting and grouping across sessions, use layout persistence (Pro).
Search
Ctrl+F opens a search bar drawn over the grid. Matches are highlighted in the cells, the bar counts them, and its arrows move to the previous and next hit.
view.SetSearchTerm("berlin"); // what Ctrl+F types into the search bar
int hits = view.SearchMatchCount;
var next = view.MoveSearch(+1); // (Row, ElementId) of the next hit, or null
if (next is { } hit)
{
grid.Api.Commands.EnsureSearchMatchVisible(hit);
}Search reads what a cell shows. For a cell that draws something other than text — a composed cell, a badge — .Searchable(item => text) says what to match. An application with its own search handles Api.Events.SearchRequested; Ctrl+F then opens yours.
Quick search
Ctrl+K opens a palette that searches across the rows and jumps to the one you pick. WithQuickSearch tells it what to show for a hit:
view.WithQuickSearch(
id: o => $"A-{o.Id}",
title: o => o.Customer,
detail: o => $"{o.Region}, {o.Amount:N2}");
grid.OpenQuickSearch(); // what Ctrl+K doesIf the application subscribes to Api.Events.QuickSearchRequested, Ctrl+K raises that instead and opens nothing of its own.
