windowsforms-400995-common-features-expressions-custom-function-based-filters.md
You can display a filter based on a custom function in pop-up filter menus and filter editors. For example, the following image shows Black Friday Discount filter based on the corresponding custom function:
Note
Run the XtraGrid demo to try out the following custom function-based filters:
Filters can be based on a custom function that accepts one (unary function) or two (binary function) operands:
The filter editor supports binary and unary functions; the pop-up filter menus support unary functions only.
To create a custom function that can be displayed as a filter, implement the ICustomFunctionDisplayAttributes interface. It extends the ICustomFunctionOperatorBrowsable interface with the following properties:
Tip
In v22.1 and newer, you can implement the extended version of this interface: ICustomFunctionDisplayAttributesEx. Its GetOperandDisplayValue method allows you to set a public name for filter operands. For instance, the following sample code adds the word “days” to the operand value (for instance, “3 days” instead of “3”).
public string GetOperandDisplayValue(object value, string displayText) {
if (value is int)
return string.Format("{0} days", value);
return value?.ToString();
}
Public Function GetOperandDisplayValue(ByVal value As Object, ByVal displayText As String) As String
If TypeOf value Is Integer Then
Return String.Format("{0} days", value)
End If
Return value?.ToString()
End Function
Next, use the CriteriaOperator.RegisterCustomFunction or CriteriaOperator.RegisterCustomFunctions method to register the implemented function.
Do one of the following to display the corresponding filter in filter menus and the filter editor.
For a data control bound to a Code First data source, annotate the data field with the CustomFunction attribute.
[CustomFunction(IsBlackFridayDiscountFunction.FunctionName)]
public double Discount { get; set; }
<CustomFunction(IsBlackFridayDiscountFunction.FunctionName)>
Public Property Discount() As Double
Unary functions accept the single operand - the field value. Unary function-based filters do not allow users to specify a parameter.
The code below shows how to implement Black Friday Discount function. This is a unary function that evaluates to true if the operand is more than or equals 0.15.
Note
Run the XtraGrid demo and click Open Solution for the complete example.
using DevExpress.Data.Filtering;
IsBlackFridayDiscountFunction.Register();
gridView1.QueryCustomFunctions += OnQueryCustomFunctions;
void OnQueryCustomFunctions(object sender, Data.Filtering.CustomFunctionEventArgs e) {
if(e.PropertyName == "Discount")
e.Add(IsBlackFridayDiscountFunction.FunctionName);
}
public class IsBlackFridayDiscountFunction : ICustomFunctionDisplayAttributes {
public const string FunctionName = "IsBlackFridayDiscount";
static readonly IsBlackFridayDiscountFunction Instance = new IsBlackFridayDiscountFunction();
IsBlackFridayDiscountFunction() { }
public static void Register() {
CriteriaOperator.RegisterCustomFunction(Instance);
}
public static bool Unregister() {
return CriteriaOperator.UnregisterCustomFunction(Instance);
}
#region ICustomFunctionOperatorBrowsable Members
public FunctionCategory Category {
get { return FunctionCategory.Math; }
}
public string Description {
get { return "The discount amount is 15% or more."; }
}
public bool IsValidOperandCount(int count) {
return count == 1;
}
public bool IsValidOperandType(int operandIndex, int operandCount, Type type) {
return DevExpress.Data.Summary.SummaryItemTypeHelper.IsNumericalType(type);
}
public int MaxOperandCount {
get { return 1; }
}
public int MinOperandCount {
get { return 1; }
}
#endregion
#region ICustomFunctionDisplayAttributes
public string DisplayName {
get { return "Is Black Friday Discount"; }
}
public object Image {
get { return "bo_price"; }
}
#endregion
#region ICustomFunctionOperator Members
//The single operand (operands[0]) is the field value being processed.
public object Evaluate(params object[] operands) {
double discount = Convert.ToDouble(operands[0]);
return discount >= 0.15;
}
public string Name {
get { return FunctionName; }
}
public Type ResultType(params Type[] operands) {
return typeof(bool);
}
#endregion
}
Imports DevExpress.Data.Filtering
IsBlackFridayDiscountFunction.Register()
AddHandler gridView1.QueryCustomFunctions, AddressOf OnQueryCustomFunctions
Private Sub OnQueryCustomFunctions(ByVal sender As Object, ByVal e As CustomFunctionEventArgs)
If e.PropertyName = "Discount" Then
e.Add(IsBlackFridayDiscountFunction.FunctionName)
End If
End Sub
Public Class IsBlackFridayDiscountFunction
Implements ICustomFunctionDisplayAttributes
Public Const FunctionName As String = "IsBlackFridayDiscount"
Private Shared ReadOnly Instance As New IsBlackFridayDiscountFunction()
Private Sub New()
End Sub
'
Public Shared Sub Register()
CriteriaOperator.RegisterCustomFunction(Instance)
End Sub
Public Shared Function Unregister() As Boolean
Return CriteriaOperator.UnregisterCustomFunction(Instance)
End Function
#Region "ICustomFunctionOperatorBrowsable Members"
Public ReadOnly Property Category() As FunctionCategory Implements ICustomFunctionOperatorBrowsable.Category
Get
Return FunctionCategory.Math
End Get
End Property
Public ReadOnly Property Description() As String Implements ICustomFunctionOperatorBrowsable.Description
Get
Return "The discount amount is 15% or more."
End Get
End Property
Public Function IsValidOperandCount(ByVal count As Integer) As Boolean Implements ICustomFunctionOperatorBrowsable.IsValidOperandCount
Return count = 1
End Function
Public Function IsValidOperandType(ByVal operandIndex As Integer, ByVal operandCount As Integer, ByVal type As Type) As Boolean Implements ICustomFunctionOperatorBrowsable.IsValidOperandType
Return DevExpress.Data.Summary.SummaryItemTypeHelper.IsNumericalType(type)
End Function
Public ReadOnly Property MaxOperandCount() As Integer Implements ICustomFunctionOperatorBrowsable.MaxOperandCount
Get
Return 1
End Get
End Property
Public ReadOnly Property MinOperandCount() As Integer Implements ICustomFunctionOperatorBrowsable.MinOperandCount
Get
Return 1
End Get
End Property
#End Region
#Region "ICustomFunctionDisplayAttributes"
Public ReadOnly Property DisplayName() As String Implements ICustomFunctionDisplayAttributes.DisplayName
Get
Return "Is Black Friday Discount"
End Get
End Property
Public ReadOnly Property Image() As Object Implements ICustomFunctionDisplayAttributes.Image
Get
Return "bo_price"
End Get
End Property
#End Region
#Region "ICustomFunctionOperator Members"
'The single operand (operands[0]) is the field value being processed.
Public Function Evaluate(ParamArray ByVal operands() As Object) As Object Implements ICustomFunctionOperator.Evaluate
Dim discount As Double = Convert.ToDouble(operands(0))
Return discount >= 0.15
End Function
Public ReadOnly Property Name() As String Implements ICustomFunctionOperator.Name
Get
Return FunctionName
End Get
End Property
Public Function ResultType(ParamArray ByVal operands() As Type) As Type Implements ICustomFunctionOperator.ResultType
Return GetType(Boolean)
End Function
#End Region
End Class
Binary functions accept the second operand - a user-specified parameter. Binary function-based filters can only be displayed in the filter editor.
The code below shows how to implement Not Begins With function. This is a binary function that evaluates to true if the first operand (the field value) not starts with the second operand (the value specified in the filter editor).
Note
Run the XtraGrid demo and click Open Solution for the complete example.
using DevExpress.Data.Filtering;
NotBeginsWithFunction.Register();
CriteriaOperator.QueryCustomFunctions += OnQueryCustomUIFunctions;
static void OnQueryCustomUIFunctions(object sender, CustomFunctionEventArgs e) {
if(e.PropertyType == typeof(string))
e.Add(NotBeginsWithFunction.FunctionName);
}
public class NotBeginsWithFunction : ICustomFunctionDisplayAttributes {
public const string FunctionName = "NotBeginsWith";
static readonly NotBeginsWithFunction Instance = new NotBeginsWithFunction();
NotBeginsWithFunction() { }
//
public static void Register() {
CriteriaOperator.RegisterCustomFunction(Instance);
}
public static bool Unregister() {
return CriteriaOperator.UnregisterCustomFunction(Instance);
}
#region ICustomFunctionOperatorBrowsable Members
public FunctionCategory Category {
get { return FunctionCategory.Text; }
}
public string Description {
get { return "Selects items that do not start with the specified string."; }
}
public bool IsValidOperandCount(int count) {
return count == 2;
}
public bool IsValidOperandType(int operandIndex, int operandCount, Type type) {
return type == typeof(string);
}
public int MaxOperandCount {
get { return 2; }
}
public int MinOperandCount {
get { return 2; }
}
#endregion
#region ICustomFunctionDisplayAttributes
public string DisplayName {
get { return "Not Begins With"; }
}
public object Image {
get { return null; }
}
#endregion
#region ICustomFunctionOperator Members
//The first operand (operands[0]) is the field value being processed.
//The second operand (operands[1]) is the value specified in the filter editor.
public object Evaluate(params object[] operands) {
if(operands[0] != null && operands[1] != null) {
string str1 = operands[0].ToString(); string str2 = operands[1].ToString();
return !str1.StartsWith(str2, StringComparison.InvariantCultureIgnoreCase);
}
return false;
}
public string Name {
get { return FunctionName; }
}
public Type ResultType(params Type[] operands) {
return typeof(bool);
}
#endregion
}
Imports DevExpress.Data.Filtering
NotBeginsWithFunction.Register()
CriteriaOperator.QueryCustomFunctions += OnQueryCustomUIFunctions;
Private Shared Sub OnQueryCustomUIFunctions(ByVal sender As Object, ByVal e As CustomFunctionEventArgs)
If e.PropertyType Is GetType(String) Then
e.Add(NotBeginsWithFunction.FunctionName)
End If
End Sub
Public Class NotBeginsWithFunction
Implements ICustomFunctionDisplayAttributes
Public Const FunctionName As String = "NotBeginsWith"
Private Shared ReadOnly Instance As New NotBeginsWithFunction()
Private Sub New()
End Sub
'
Public Shared Sub Register()
CriteriaOperator.RegisterCustomFunction(Instance)
End Sub
Public Shared Function Unregister() As Boolean
Return CriteriaOperator.UnregisterCustomFunction(Instance)
End Function
#Region "ICustomFunctionOperatorBrowsable Members"
Public ReadOnly Property Category() As FunctionCategory Implements DevExpress.Data.Filtering.ICustomFunctionOperatorBrowsable.Category
Get
Return FunctionCategory.Text
End Get
End Property
Public ReadOnly Property Description() As String Implements DevExpress.Data.Filtering.ICustomFunctionOperatorBrowsable.Description
Get
Return "Selects items that do not start with the specified string."
End Get
End Property
Public Function IsValidOperandCount(ByVal count As Integer) As Boolean Implements DevExpress.Data.Filtering.ICustomFunctionOperatorBrowsable.IsValidOperandCount
Return count = 2
End Function
Public Function IsValidOperandType(ByVal operandIndex As Integer, ByVal operandCount As Integer, ByVal type As Type) As Boolean Implements DevExpress.Data.Filtering.ICustomFunctionOperatorBrowsable.IsValidOperandType
Return type Is GetType(String)
End Function
Public ReadOnly Property MaxOperandCount() As Integer Implements DevExpress.Data.Filtering.ICustomFunctionOperatorBrowsable.MaxOperandCount
Get
Return 2
End Get
End Property
Public ReadOnly Property MinOperandCount() As Integer Implements DevExpress.Data.Filtering.ICustomFunctionOperatorBrowsable.MinOperandCount
Get
Return 2
End Get
End Property
#End Region
#Region "ICustomFunctionDisplayAttributes"
Public ReadOnly Property DisplayName() As String Implements ICustomFunctionDisplayAttributes.DisplayName
Get
Return "Not Begins With"
End Get
End Property
Public ReadOnly Property Image() As Object Implements ICustomFunctionDisplayAttributes.Image
Get
Return Nothing
End Get
End Property
#End Region
#Region "ICustomFunctionOperator Members"
'The first operand (operands[0]) is the field value being processed.
'The second operand (operands[1]) is the value specified in the filter editor.
Public Function Evaluate(ParamArray ByVal operands() As Object) As Object Implements DevExpress.Data.Filtering.ICustomFunctionOperator.Evaluate
If operands(0) IsNot Nothing AndAlso operands(1) IsNot Nothing Then
Dim str1 As String = operands(0).ToString()
Dim str2 As String = operands(1).ToString()
Return Not str1.StartsWith(str2, StringComparison.InvariantCultureIgnoreCase)
End If
Return False
End Function
Public ReadOnly Property Name() As String Implements DevExpress.Data.Filtering.ICustomFunctionOperator.Name
Get
Return FunctionName
End Get
End Property
Public Function ResultType(ParamArray ByVal operands() As Type) As Type Implements DevExpress.Data.Filtering.ICustomFunctionOperator.ResultType
Return GetType(Boolean)
End Function
#End Region
End Class
This example shows how to create a custom function ICustomFunctionOperator to filter dates starting from today plus a specified number of days (14 by default).
using DevExpress.Data.Filtering;
public partial class Form1 : DevExpress.XtraEditors.XtraForm {
// ...
private void Form1_Load(object sender, EventArgs e) {
IsTodayPlusNDaysFunction.Register();
gridView1.QueryCustomFunctions += GridView1_QueryCustomFunctions;
}
private void GridView1_QueryCustomFunctions(object sender, DevExpress.XtraGrid.Views.Grid.CustomFunctionEventArgs e) {
if(e.PropertyName == "DateTime")
e.Add(IsTodayPlusNDaysFunction.FunctionName);
}
}
public class IsTodayPlusNDaysFunction : ICustomFunctionDisplayAttributes {
public const string FunctionName = "IsTodayPlusNDaysFunction";
static readonly IsTodayPlusNDaysFunction Instance = new IsTodayPlusNDaysFunction();
public static int DaysCount { get; set; } = 14;
IsTodayPlusNDaysFunction() { }
public static void Register() {
CriteriaOperator.RegisterCustomFunction(Instance);
}
public static bool Unregister() {
return CriteriaOperator.UnregisterCustomFunction(Instance);
}
#region ICustomFunctionOperatorBrowsable Members
public FunctionCategory Category {
get { return FunctionCategory.DateTime; }
}
public string Description {
get { return String.Concat("Today plus ", DaysCount, "Days"); }
}
public bool IsValidOperandCount(int count) {
return count == 1;
}
public bool IsValidOperandType(int operandIndex, int operandCount, Type type) {
return DevExpress.Data.Summary.SummaryItemTypeHelper.IsDateTime(type);
}
public int MaxOperandCount {
get { return 1; }
}
public int MinOperandCount {
get { return 1; }
}
#endregion
#region ICustomFunctionDisplayAttributes
public string DisplayName {
get { return String.Concat("Today +", DaysCount, " Days"); }
}
public object Image {
get { return null; }
}
#endregion
#region ICustomFunctionOperator Members
public object Evaluate(params object[] operands) {
DateTime dateTime = Convert.ToDateTime(operands[0]);
DateTime from = DateTime.Today;
DateTime to = from.AddDays(DaysCount).AddHours(23).AddMinutes(59).AddSeconds(59);
return (from <= dateTime && dateTime <= to);
}
public string Name {
get { return FunctionName; }
}
public Type ResultType(params Type[] operands) {
return typeof(bool);
}
#endregion
}
Imports DevExpress.Data.Filtering
Partial Public Class Form1
Inherits DevExpress.XtraEditors.XtraForm
' ...
Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs)
IsTodayPlusNDaysFunction.Register()
AddHandler gridView1.QueryCustomFunctions, AddressOf GridView1_QueryCustomFunctions
End Sub
Private Sub GridView1_QueryCustomFunctions(ByVal sender As Object, ByVal e As DevExpress.XtraGrid.Views.Grid.CustomFunctionEventArgs)
If e.PropertyName = "DateTime" Then
e.Add(IsTodayPlusNDaysFunction.FunctionName)
End If
End Sub
End Class
Public Class IsTodayPlusNDaysFunction
Implements ICustomFunctionDisplayAttributes
Public Const FunctionName As String = "IsTodayPlusNDaysFunction"
Private Shared ReadOnly Instance As New IsTodayPlusNDaysFunction()
Public Shared Property DaysCount() As Integer
= 14
IsTodayPlusNDaysFunction()
public static void Register()
CriteriaOperator.RegisterCustomFunction(Instance)
public static Boolean Unregister()
Return CriteriaOperator.UnregisterCustomFunction(Instance)
' #Region "ICustomFunctionOperatorBrowsable Members"
public FunctionCategory Category
Get
Return FunctionCategory.DateTime
End Get
public String Description
Get
Return String.Concat("Today plus ", DaysCount, "Days")
End Get
public Boolean IsValidOperandCount(Integer count)
Return count = 1
public Boolean IsValidOperandType(Integer operandIndex, Integer operandCount, Type type)
Return DevExpress.Data.Summary.SummaryItemTypeHelper.IsDateTime(type)
public Integer MaxOperandCount
Get
Return 1
End Get
public Integer MinOperandCount
Get
Return 1
End Get
' #End Region
' #Region "ICustomFunctionDisplayAttributes"
public String DisplayName
Get
Return String.Concat("Today +", DaysCount, " Days")
End Get
public Object Image
Get
Return Nothing
End Get
' #End Region
' #Region "ICustomFunctionOperator Members"
public Object Evaluate(params Object() operands)
Dim dateTime As Date = Convert.ToDateTime(operands(0))
Dim [from] As Date = Date.Today
Dim [to] As Date = [from].AddDays(DaysCount).AddHours(23).AddMinutes(59).AddSeconds(59)
Return ([from] <= dateTime AndAlso dateTime <= [to])
public String Name
Get
Return FunctionName
End Get
public Type ResultType(params Type() operands)
Return GetType(Boolean)
' #End Region
End Class