blazor-404417-components-grid-data-shaping-filter-data-filter-menu.md
The column filter menu displays a list of unique values, allowing users to filter data. A corner grip helps users resize the menu.
Use the following properties to display filter menu buttons in column headers:
DxGrid.FilterMenuButtonDisplayModeSpecifies when the Grid displays filter menu buttons in column headers.DxGridDataColumn.FilterMenuButtonDisplayModeSpecifies when the column displays the filter menu button.
Note
The Grid cannot create filter item lists for columns associated with certain data types (for instance, arrays and images). If you do not implement a filter menu template for such a column, the column filter menu displays the following text instead of filter items: “No filters are available for this column”.
Once you apply a filter to a column, other filter menus hide values that do not match specified filter criteria. Hold down Shift and click the filter button to display all values. Focus a column header and press Alt+Arrow Down or Shift+Alt+Arrow Down to open the filter menu.
When a user clicks the menu’s Clear button, the filter menu changes the current operator type to Default.
When a user clicks a filter menu button, the Grid creates a list of default filter items. The table below lists settings that you can specify in the EditSettings render fragment:
|
Editor Settings
|
Applicable Data Types
|
Supported Properties
| | --- | --- | --- | |
|
Any data type
|
Data, TextFieldName, ValueFieldName
| |
|
Any data type
|
CheckedDisplayText, ValueChecked, UncheckedDisplayText, ValueUnchecked, IndeterminateDisplayText, ValueIndeterminate
|
The following code snippet uses the DxComboBoxSettings object to specify the text used to display the Category column’s filter menu items:
<DxGrid Data="GridData"
FilterMenuButtonDisplayMode="GridFilterMenuButtonDisplayMode.Always"
@* ... *@
VirtualScrollingEnabled="true">
<Columns>
<DxGridDataColumn FieldName="OrderDate" Width="140px" />
<DxGridDataColumn FieldName="ProductName" MinWidth="100" />
<DxGridDataColumn FieldName="CategoryId" Caption="Category" Width="130px">
<EditSettings>
<DxComboBoxSettings Data="Categories" ValueFieldName="CategoryId" TextFieldName="CategoryName" />
</EditSettings>
</DxGridDataColumn>
<DxGridDataColumn FieldName="UnitPrice" DisplayFormat="c2" Width="140px" />
<DxGridDataColumn FieldName="Quantity" Width="110px" />
<DxGridDataColumn FieldName="Discount" DisplayFormat="p0" Width="110px" />
<DxGridDataColumn FieldName="Total"
UnboundType="GridUnboundColumnType.Decimal"
UnboundExpression="[UnitPrice] * [Quantity] * (1 - [Discount])"
DisplayFormat="c2"
Width="110px">
<FilterMenuTemplate>
<Grid_Filtering_ColumnFilterMenu_CustomRange FilterContext="context" Items="TotalPriceIntervals" />
</FilterMenuTemplate>
</DxGridDataColumn>
<DxGridDataColumn FieldName="Shipped"
UnboundType="GridUnboundColumnType.Boolean"
UnboundExpression="[ShippedDate] <> Null"
Width="100px" />
</Columns>
</DxGrid>
You can also handle the CustomizeFilterMenu event to customize the filter menu items for all data types (for example, to concatenate values, apply formatting, or replace blank values). The event fires before the filter dropdown appears.
Note that each filter menu item cannot hold a complex filter condition and can only filter data by a single column value. To implement complex criteria, create a filter menu template.
The following code snippet shows how you can modify text in filter menu items. This example adds IDs to customer names.
<DxGrid Data="@customers"
FilterMenuButtonDisplayMode="GridFilterMenuButtonDisplayMode.Always"
CustomizeFilterMenu="OnCustomizeFilterMenu">
<Columns>
<DxGridDataColumn FieldName="ContactName" Width="180px" />
<DxGridDataColumn FieldName="Company" />
<DxGridDataColumn FieldName="Country" Width="140px" />
</Columns>
</DxGrid>
@code {
DateTime data { get; set; }
private Customer[]? customers;
protected override async Task OnInitializedAsync() {
customers = await CustomerData.GetData();
}
void OnCustomizeFilterMenu (GridCustomizeFilterMenuEventArgs e) {
if (e.DataColumn.FieldName == "ContactName") {
e.DataItems.ForEach(di => {
int? CustomerID = customers.Where(c =>
c.ContactName == di.Value.ToString()).FirstOrDefault()?.ID;
di.DisplayText = di.DisplayText + " (ID " + CustomerID + ")";
});
}
}
}
To apply a complex filter condition in a filter menu (for instance, a date range), create a filter menu template. You can create a template for an individual column (FilterMenuTemplate) or for all columns in the Grid (DataColumnFilterMenuTemplate).
You can choose how to display DateTime and DateTime? values. The default view displays dates hierarchically (HierarchicalDateView). You can also display dates as a plain list (ListView). Neither option affects default content rendering (that is, paddings, alignment, etc.).
<DxGrid Data="GridData"
FilterMenuButtonDisplayMode="GridFilterMenuButtonDisplayMode.Always">
<Columns>
<DxGridDataColumn FieldName="OrderDate">
<FilterMenuTemplate>
@context.ListView
</FilterMenuTemplate>
</DxGridDataColumn>
<DxGridDataColumn FieldName="ProductName" MinWidth="100" />
<DxGridDataColumn FieldName="UnitPrice" DisplayFormat="c2" />
<DxGridDataColumn FieldName="Quantity" />
<DxGridDataColumn FieldName="Discount" DisplayFormat="p0" />
<DxGridDataColumn FieldName="Total"
UnboundType="GridUnboundColumnType.Decimal"
UnboundExpression="[UnitPrice] * [Quantity] * (1 - [Discount])"
DisplayFormat="c2" />
</Columns>
</DxGrid>
The template’s context parameter also contains the FilterCriteria property. Use this property to specify filter criteria applied to the column and handle the FilterCriteriaChanged event to react to filter criteria changes.
View Example: Implement a date range filter
The following code snippet creates custom ranges for the Order Date and Total column’s filter menu:
<DxGrid Data="GridData"
FilterMenuButtonDisplayMode="GridFilterMenuButtonDisplayMode.Always">
<Columns>
<DxGridDataColumn FieldName="OrderDate" Width="140px">
<FilterMenuTemplate>
<DateRange FilterContext="context" />
</FilterMenuTemplate>
</DxGridDataColumn>
<DxGridDataColumn FieldName="ProductName" MinWidth="100" />
<DxGridDataColumn FieldName="CategoryId" Caption="Category" Width="130px">
<EditSettings>
<DxComboBoxSettings Data="Categories" ValueFieldName="CategoryId" TextFieldName="CategoryName" />
</EditSettings>
</DxGridDataColumn>
<DxGridDataColumn FieldName="UnitPrice" DisplayFormat="c2" Width="140px" />
<DxGridDataColumn FieldName="Quantity" Width="110px" />
<DxGridDataColumn FieldName="Discount" DisplayFormat="p0" Width="110px" />
<DxGridDataColumn FieldName="Total"
UnboundType="GridUnboundColumnType.Decimal"
UnboundExpression="[UnitPrice] * [Quantity] * (1 - [Discount])"
DisplayFormat="c2"
Width="110px">
<FilterMenuTemplate>
<CustomRange FilterContext="context" Items="TotalPriceIntervals" />
</FilterMenuTemplate>
</DxGridDataColumn>
<DxGridDataColumn FieldName="Shipped"
UnboundType="GridUnboundColumnType.Boolean"
UnboundExpression="[ShippedDate] <> Null"
Width="100px" />
</Columns>
</DxGrid>
@code {
static IReadOnlyList<CustomRangeFilterItem> TotalPriceIntervals { get; } = CreateTotalPriceIntervals();
object GridData { get; set; }
IReadOnlyList<Category> Categories { get; set; }
protected override async Task OnInitializedAsync() {
Categories = (await NwindDataService.GetCategoriesAsync()).ToList();
var invoices = await NwindDataService.GetInvoicesAsync();
var products = await NwindDataService.GetProductsAsync();
GridData = invoices.Join(products, i => i.ProductId, p => p.ProductId, (i, p) => {
return new {
ProductName = i.ProductName,
CategoryId = p.CategoryId,
OrderDate = i.OrderDate,
UnitPrice = i.UnitPrice,
Quantity = i.Quantity,
Discount = i.Discount,
ShippedDate = i.ShippedDate
};
});
}
static IReadOnlyList<CustomRangeFilterItem> CreateTotalPriceIntervals() {
var prop = new OperandProperty("Total");
var result = new List<CustomRangeFilterItem>();
var step = 100M;
for(var i = 0; i < 10; i++) {
var start = step * i;
var end = start + step;
result.Add(new() {
Criteria = prop >= start & prop < end,
DisplayText = $"from {start:c} to {end - 0.01M:c}"
});
}
result.Add(new() {
Criteria = prop > 1000,
DisplayText = $"> {1000:c}"
});
return result;
}
}
@using DevExpress.Data.Filtering;
@using DevExpress.Data.Filtering.Helpers;
<DxFormLayout CssClass="spaced-content" ItemCaptionAlignment="ItemCaptionAlignment.All">
<DxFormLayoutItem Caption="From" ColSpanSm="12">
<DxDateEdit T="DateTime?"
Enabled="DateEditEnabled"
Date="StartDate"
DateChanged="StartDate_Changed"
MinDate="StartDateEdit_MinDate"
MaxDate="StartDateEdit_MaxDate"
ClearButtonDisplayMode="DataEditorClearButtonDisplayMode.Auto" />
</DxFormLayoutItem>
<DxFormLayoutItem Caption="To" ColSpanSm="12">
<DxDateEdit T="DateTime?"
Enabled="DateEditEnabled"
Date="EndDate"
DateChanged="EndDate_Changed"
MinDate="EndDateEdit_MinDate"
MaxDate="EndDateEdit_MaxDate"
ClearButtonDisplayMode="DataEditorClearButtonDisplayMode.Auto" />
</DxFormLayoutItem>
</DxFormLayout>
@code {
[Parameter]
public GridDataColumnFilterMenuTemplateContext FilterContext { get; set; }
DateTime? StartDate { get; set; }
DateTime? EndDate { get; set; }
DateTime MinDate { get; set; } = DateTime.MinValue;
DateTime MaxDate { get; set; } = DateTime.MaxValue;
DateTime StartDateEdit_MinDate => MinDate;
DateTime StartDateEdit_MaxDate => EndDate != null && EndDate.Value >= StartDateEdit_MinDate ? EndDate.Value : MaxDate;
DateTime EndDateEdit_MinDate => StartDate != null && StartDate.Value <= EndDateEdit_MaxDate ? StartDate.Value : MinDate;
DateTime EndDateEdit_MaxDate => MaxDate;
bool DateEditEnabled { get; set; }
protected override async Task OnInitializedAsync() {
(StartDate, EndDate) = LoadDateRangeValues(FilterContext.FilterCriteria, FilterContext.DataColumn.FieldName);
var items = await FilterContext.GetDataItemsAsync();
var allDates = items.Select(i => Convert.ToDateTime(i.Value)).ToList();
if(allDates.Any()) {
MinDate = allDates.Min();
MaxDate = allDates.Max();
DateEditEnabled = true;
}
}
void StartDate_Changed(DateTime? value) {
StartDate = value;
if(StartDate > EndDate)
EndDate = StartDate;
UpdateCriteria();
}
void EndDate_Changed(DateTime? value) {
EndDate = value;
if(StartDate > EndDate)
StartDate = EndDate;
UpdateCriteria();
}
void UpdateCriteria() {
FilterContext.FilterCriteria = CreateDateRangeCriteria(StartDate, EndDate, FilterContext.DataColumn.FieldName);
}
static CriteriaOperator CreateDateRangeCriteria(DateTime? startDate, DateTime? endDate, string fieldName) {
CriteriaOperator left = null;
CriteriaOperator right = null;
var prop = new OperandProperty(fieldName);
if(startDate != null)
left = prop >= startDate;
if(endDate != null)
right = prop < ConvertEndDateToOperandDate(endDate);
return left & right;
}
static DateTime? ConvertEndDateToOperandDate(DateTime? endDate) => endDate?.Date.AddDays(1);
static (DateTime? startDate, DateTime? endDate) LoadDateRangeValues(CriteriaOperator criteria, string fieldName) {
CriteriaOperator left = null;
CriteriaOperator right = null;
if(criteria is GroupOperator groupOp && groupOp.OperatorType == GroupOperatorType.And && groupOp.Operands.Count == 2) {
left = groupOp.Operands[0];
right = groupOp.Operands[1];
} else {
left = right = criteria;
}
return (
ExtractRangeDate(left, fieldName, BinaryOperatorType.GreaterOrEqual),
ConvertOperandDateToEndDate(ExtractRangeDate(right, fieldName, BinaryOperatorType.Less))
);
}
static DateTime? ExtractRangeDate(CriteriaOperator criteria, string fieldName, BinaryOperatorType opType) {
var canExtract = criteria is BinaryOperator binaryOp &&
binaryOp.OperatorType == opType &&
binaryOp.LeftOperand is OperandProperty prop &&
binaryOp.RightOperand is OperandValue opValue &&
prop.PropertyName == fieldName &&
opValue.Value is DateTime;
if(canExtract)
return (DateTime)((OperandValue)((BinaryOperator)criteria).RightOperand).Value;
return null;
}
static DateTime? ConvertOperandDateToEndDate(DateTime? endDate) => endDate?.Date.AddDays(-1);
}
.spaced-content {
padding: 0.75rem;
}
@using DevExpress.Data.Filtering;
@using DevExpress.Data.Filtering.Helpers;
<DxListBox TData="CustomRangeFilterItem"
TValue="CriteriaOperator"
Data="Items"
ValueFieldName="Criteria"
TextFieldName="DisplayText"
Values="SelectedValues"
ValuesChanged="SelectedValues_Changed"
SelectionMode="ListBoxSelectionMode.Multiple"
ShowCheckboxes="true"
CssClass="h-100" />
@code {
[Parameter]
public GridDataColumnFilterMenuTemplateContext FilterContext { get; set; }
[Parameter]
public IReadOnlyList<CustomRangeFilterItem> Items { get; set; }
IEnumerable<CriteriaOperator> SelectedValues { get; set; }
protected override void OnInitialized() {
SelectedValues = LoadSelectedValues(
FilterContext.FilterCriteria,
Items.Select(i => i.Criteria).ToHashSet()
);
}
void SelectedValues_Changed(IEnumerable<CriteriaOperator> value) {
SelectedValues = value;
FilterContext.FilterCriteria = CreateCriteria(SelectedValues);
}
static CriteriaOperator CreateCriteria(IEnumerable<CriteriaOperator> values) {
var orderedValues = values.OrderBy(v => v.ToString()).ToArray();
if (orderedValues.Length == 0)
return null;
return new GroupOperator(GroupOperatorType.Or, orderedValues);
}
static IEnumerable<CriteriaOperator> LoadSelectedValues(CriteriaOperator criteria, IReadOnlySet<CriteriaOperator> possibleCriterias) {
if(possibleCriterias.Contains(criteria))
return new[] { criteria };
if(criteria is GroupOperator groupOp && groupOp.OperatorType == GroupOperatorType.Or && groupOp.Operands.All(i => possibleCriterias.Contains(i)))
return groupOp.Operands;
return Array.Empty<CriteriaOperator>();
}
public record CustomRangeFilterItem {
public CriteriaOperator Criteria { get; init; }
public string DisplayText { get; init; }
public static IReadOnlyList<CustomRangeFilterItem> CreateIntervals(string fieldName, int step, int stepsCount, bool isMult, string numberDisplayFormat) {
var prop = new OperandProperty(fieldName);
var result = new List<CustomRangeFilterItem>();
var start = 0;
var end = 0;
var firstStepIndex = isMult ? 0 : -1;
for(var i = firstStepIndex; i < stepsCount; ++i) {
start = isMult ? step * i : (int)Math.Pow(step, i);
end = isMult ? start + step : (int)Math.Pow(step, i + 1);
result.Add(new CustomRangeFilterItem() {
Criteria = prop >= start & prop < end,
DisplayText = $"from {string.Format(numberDisplayFormat, start)} to {string.Format(numberDisplayFormat, end)}"
});
}
result.Add(new CustomRangeFilterItem() {
Criteria = prop >= end,
DisplayText = $"from {string.Format(numberDisplayFormat, end)}"
});
return result;
}
}
}
Filtering is always case-insensitive for most data sources except for a GridDevExtremeDataSource.
If a GridDevExtremeDataSource uses LINQ to Objects, it also ignores word case; otherwise, filtering is case-sensitive. Use the StringToLower option to enable/disable case sensitivity in the GridDevExtremeDataSource:
@inherits OwningComponentBase
@inject Microsoft.Extensions.Configuration.IConfiguration Configuration
<DxGrid Data="@Data" PageSize="10">
<Columns>
<DxGridDataColumn FieldName="State" Width="5%" />
<DxGridDataColumn FieldName="Area" MinWidth="100" />
<DxGridDataColumn FieldName="City" Caption="County" MinWidth="100" />
<DxGridDataColumn FieldName="Name" Caption="Location" MinWidth="100" />
<DxGridDataColumn FieldName="Year" DisplayFormat="0" Width="10%" />
<DxGridDataColumn FieldName="Bedrooms" Width="10%" />
<DxGridDataColumn FieldName="Population" DisplayFormat="#,0" MinWidth="80" Width="10%" />
</Columns>
</DxGrid>
@code {
RentInfoDataService RentInfoDataService { get; set; }
object Data { get; set; }
protected override void OnInitialized() {
// Refer to https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.components.owningcomponentbase
RentInfoDataService = ScopedServices.GetRequiredService<RentInfoDataService>();
var connectionString = ConnectionStringUtils.GetGridLargeDataConnectionString(Configuration);
if(string.IsNullOrEmpty(connectionString)) return;
var dataSource = new GridDevExtremeDataSource<AreaRentInfo>(RentInfoDataService.GetAreaRentInfo());
dataSource.CustomizeLoadOptions = (loadOptions) => {
loadOptions.PrimaryKey = new[] { "Oid" };
loadOptions.PaginateViaPrimaryKey = true;
loadOptions.StringToLower = true;
};
Data = dataSource;
}
}
using System;
using System.Runtime.InteropServices;
namespace BlazorDemo.Data {
public partial class AreaRentInfo {
public AreaRentInfo() {
}
public Guid Oid { get; set; }
public string State { get; set; }
public string City { get; set; }
public string Area { get; set; }
public string Name { get; set; }
public int Population { get; set; }
public int Year { get; set; }
public int Bedrooms { get; set; }
public decimal Rent { get; set; }
}
}
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BlazorDemo.Data;
namespace BlazorDemo.Services {
public partial class RentInfoDataService {
public IQueryable<AreaRentInfo> GetAreaRentInfo() {
// Return your data here
}
}
}
// ...
builder.Services.AddScoped<RentInfoDataService>();
Take the following filter menu limitations into account:
null values are not considered unique. Handle the CustomizeFilterMenu event to process these values.The Grid component automatically synchronizes filter changes across the following UI elements:
Of these elements, only the filter panel and filter builder can display all filter conditions. Other elements have limitations, for example, column filter menus do not support the Contains operator, and filter row cells cannot display the Betweencondition. To ensure the entire filter expression is visible, add a filter panel to the Grid. The panel also provides access to the filter builder dialog.
Note
The Grid updates a column’s FilterRowOperatorType once users apply filters to column data using another UI element. For instance, after a user selects a filter menu value, the column’s FilterRowOperatorType switches to Equals. The operator type remains Equals even after the filter is cleared.
You can extend filter row UI and display filter operators for each column. Refer to the following example for additional information: Incorporate a selector for filter row operator type.
void OnCustomizeFilterMenu (GridCustomizeFilterMenuEventArgs e) {
if (e.DataColumn.FieldName == "TargetColumn") {
e.DataItems.ForEach(di => {
di.DisplayText = di.Value == null ? "Custom Null Text" : di.DisplayText;
});
}
}
void OnCustomizeFilterMenu(GridCustomizeFilterMenuEventArgs e) {
if (e.DataColumn.FieldName == "Values") {
e.DataItems.ForEach(di => {
var menuItemText = di.DisplayText;
di.DisplayText = menuItemText.Replace(",", "");
});
}
}