Back to Devexpress

Custom Function Based Filters

windowsforms-400995-common-features-expressions-custom-function-based-filters.md

latest23.5 KB
Original Source

Custom Function Based Filters

  • Jan 09, 2023
  • 11 minutes to read

Overview

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:

  • Is Black Friday Discount filter in the Discount column’s pop-up filter menu and filter editor.
  • Is Weekend filter in the Sales Date column’s pop-up filter menu and filter editor.
  • Not Begins With filter in the Name column’s filter editor.

Custom Function Operands

Filters can be based on a custom function that accepts one (unary function) or two (binary function) operands:

  • the first operand - a field value being processed
  • the second operand - a value against which field values are evaluated (users specify the second operand in the filter editor)

The filter editor supports binary and unary functions; the pop-up filter menus support unary functions only.

How to Implement and Display a Custom Function Based Filter

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:

  • DisplayName — a string value that specifies the filter’s caption.
  • Image — an Image, SvgImage or image URI that specifies the filter’s glyph.

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”).

csharp
public string GetOperandDisplayValue(object value, string displayText) {
    if (value is int)
        return string.Format("{0} days", value);
    return value?.ToString();
}
vb
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.

Use Data Annotation Attributes

For a data control bound to a Code First data source, annotate the data field with the CustomFunction attribute.

csharp
[CustomFunction(IsBlackFridayDiscountFunction.FunctionName)]
public double Discount { get; set; }
vb
<CustomFunction(IsBlackFridayDiscountFunction.FunctionName)>
Public Property Discount() As Double

Handle Events

Unary Functions

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.

csharp
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
}
vb
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

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.

csharp
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
}
vb
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

Example - How to Create a Custom Date-Time Filter

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).

csharp
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
}
vb
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