DxGridDataColumn.FilterMenuTemplate Property
Specifies a template used to display the column’s filter menu.
Namespace: DevExpress.Blazor
Assembly:
DevExpress.Blazor.v24.2.dll
NuGet Package:
DevExpress.Blazor
Declaration
[Parameter]
public RenderFragment<GridDataColumnFilterMenuTemplateContext> FilterMenuTemplate { get; set; }
Property Value
Use the FilterMenuTemplate
property to define a template for the column filter menu.
The FilterMenuTemplate
accepts a GridDataColumnFilterMenuTemplateContext object as the context
parameter. This parameter allows you to do the following:
Note
The GetDataItemsAsync() method cannot obtain unique values from a column whose data items do not implement IComparable type. If you call the method for such a column, an error may occur.
To define a common column filter menu template for all Grid columns, use the DxGrid.DataColumnFilterMenuTemplate property.
Run Demo: Column Filter Menu 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;
<style>
.spaced-content {
padding: 0.75rem;
}
</style>
<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);
}
@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;
}
}
}
For more information about templates in the Grid component, refer to the following topic: Templates in Blazor Grid.
See Also