Data filtering actually controls visibility of individual grid rows basing on rules set by the programmer. These rules should be convenient and easy to use, should not depend on data sorting or grouping and should be equally applied to static and real-time data. Data filtering should not depend on hierarchy level or on data being expanded or collapsed by parent rows.

The grid provides three complementary methods of data filtering.

  • Row.Filtered - the easiest method of displaying or hiding data. However, it is doesn't support even-driven data model and therefore it is not useful for more complex applications. With this method the programmer has to control row visibility manually and to consider that data can be sorted or grouped and can have complex hierarchical structure.

  • IFilter interface - the most efficient and convenient filtering method. The programmer has to inherit IFilter interface and to implement IsFiltered method. This method uses row as argument for which the grid determines whether filtering is required.

    C# Copy imageCopy
    grid.Filter = new Filter(delegate(Row row)
    {
        //Filter all rows in which the value in column 'Price' less than 1000
        double price = (double) row["Price"].Value;
        return price < 1000;
    });

    This method is called in the following cases:

    - when a filter is replaced, i.e. when Grid.Filter property is called.

    - when the filter notifies the grid of changes in filtering conditions via IFilter.FilterUpdated event.

    - when new data is added to the grid only for rows that are associated with newly added data.

    - when data is updated (i.e. when Row.Update() methods are called and when the grid gets notifications from INotifyPropertyChanged or IBindingList interface)

    C# Copy imageCopy
    //Quote class implementing INotifyPropertyChanged interface
    class Quote : INotifyPropertyChanged
    {
        private readonly int _quantity;
        private double _price;
    
        public Quote(int quantity, double price)
        {
            _quantity = quantity;
            _price = price;
        }
    
        public int Quantity
        {
            get { return _quantity; }
        }
    
        public double Price
        {
            get { return _price; }
            set
            {
                if (_price != value)
                {
                    _price = value;
                    if(PropertyChanged != null)
                    {
                        PropertyChanged(this, new PropertyChangedEventArgs("Price"));
                    }
                }
            }
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
    }
    
    //Set some filter:
    grid.Filter = new Filter(delegate(Row row)
    {
        double price = (double) row["Price"].Value;
        return price < 1000;
    });
    
    //Populate grid with some values:
    List<Quote> collection = new List<Quote>();
    for (int i = 0; i < 5; ++i)
    {
        collection.Add(new Quote(i, 1000 + 100 * i));
    }
    grid.DataSource = collection;
    
    
    //All 5 rows are displayed right now. Change price of some quote:
    collection[4].Price = 900;
    
    //The row becomes invisible. .Net grid displays only 4 rows
  • Graphical filters in columns. This feature enables the programmer to implement any graphical filter that can be used for interactive data filtering. Any graphical control placed in dropdown box can be used as a filter. Implementation of such filter is based on familiar UITypeEditor that can provide any user control. First let's see how a user control can be placed in a column as a visual filter.

    Custom filter in the VS designer
    C# Copy imageCopy
    //User control for editing a filter.
    public partial class CustomFilterControl : UserControl
    {
        public CustomFilterControl()
        {
            InitializeComponent();
        }
    
        private void InitializeComponent() { ... }
    }
    
    //Editor, using the arbitrary control to edit the filter
    class CustomFilter : UITypeEditor
    {
        private readonly CustomFilterControl _control = new CustomFilterControl();
    
        //Drop-down style
        public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
        {
            return UITypeEditorEditStyle.DropDown;
        }
    
        //This method is called when the user wants to edit column filter
        public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
        {
            //Get standard service to display custom control in a dropdown control holder
            IWindowsFormsEditorService service = (IWindowsFormsEditorService) provider.GetService(typeof (IWindowsFormsEditorService));
            if(service != null)
            {
                //Display the control
                service.DropDownControl(_control);
            }
    
            return value;
        }
    }
    
    //Setup the filter
    grid.Headers[0]["Price"].Filter = new CustomFilter();

    Now, when we understand the concept of using visual controls as filters, let's show how can we create the filter itself and to set filtering conditions using already described IFilter interface. To do this we need the editor in UITypeEditir.EditValue() method to return an object of a class that implements IFilter interface. When such object is created, we can send any condition to its builder and this condition will be used for all grid rows.

    C# Copy imageCopy
    //User control for editing a filter.
    public partial class CustomFilterControl : UserControl
    {
        private IWindowsFormsEditorService _service;
        private CustomFilter _filterEditor;
        private IFilter _currentFilter;
    
        public CustomFilterControl()
        {
            InitializeComponent();
        }
    
        public IFilter CurrentFilter
        {
            get { return _currentFilter; }
        }
    
        public void EditFilter(IWindowsFormsEditorService service, CustomFilter filterEditor)
        {
            if (service != null)
            {
                //Display the control
                _filterEditor = filterEditor;
                _service = service;
                service.DropDownControl(this);
            }
        }
    
        //Called when the user clicks on the 'Ok' button
        private void buttonOk_Click(object sender, EventArgs e)
        {
            if (_service != null)
            {
                //Create a filter and keep it in the _currentFilter variable
                double minValue = (double)minValueCtrl.Value;
                double maxValue = (double)maxValueCtrl.Value;
                _currentFilter = new Ui.Filter(delegate(Row row)
                {
                    double price = (double) row["Price"].Value;
                    if (minValue > 0 && price < minValue)
                    {
                        //filter the row, if the value less than the minValue.
                        return true;
                    }
                    if (maxValue > 0 && price > maxValue)
                    {
                        //filter the row, if the value less than the minValue.
                        return true;
                    }
                    return false;
                });
    
                //Close the dropdown control
                _service.CloseDropDown();
            }
        }
    }

    Implementation of the CustomFilter:

    C# Copy imageCopy
    class CustomFilter : UITypeEditor
    {
        private readonly CustomFilterControl _control = new CustomFilterControl();
    
        public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
        {
            return UITypeEditorEditStyle.DropDown;
        }
    
        public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
        {
            IWindowsFormsEditorService service = (IWindowsFormsEditorService) provider.GetService(typeof (IWindowsFormsEditorService));
            if(service != null)
            {
                //Display the control in a drop down box
                _control.EditFilter(service, this);
    
                //return a IFilter object
                value = _control.CurrentFilter;
            }
    
            return value;
        }
    }
    Custom filter in column

    As it has already been mentioned above, IFilter interface has IFilter.FilterUpdated event. The editor can implement IFilter interface by itself or return it via EditValue method, but the grid will always subscribe to notifications resulting in a very interesting effect. When a graphic filter is edited, the control can fire notifications of changes in filtering conditions and the grid will automatically use these new conditions for rows.

    C# Copy imageCopy
    public partial class CustomFilterControl : UserControl
    {
        ...
    
        private void buttonOk_Click(object sender, EventArgs e)
        {
            if (_service != null)
            {
                //Change values in the filter. The grid will be notified at each value changing
                _filterEditor.UpdateFilter((double)minValueCtrl.Value, (double)maxValueCtrl.Value);
    
                //Close the dropdown control
                _service.CloseDropDown();
            }
        }
    }
    
    
    public class CustomFilter : UITypeEditor, IFilter
    {
        private double _maxValue;
        private double _minValue;
    
        ...
    
        public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
        {
            IWindowsFormsEditorService service = (IWindowsFormsEditorService) provider.GetService(typeof (IWindowsFormsEditorService));
            if(service != null)
            {
                //Display the control in the drop down box
                _control.EditFilter(service, this);
    
                //return a IFilter object
                value = this;
            }
    
            return value;
        }
    
        public void UpdateFilter(double minValue, double maxValue)
        {
            _minValue = minValue;
            _maxValue = maxValue;
            if (FilterUpdated != null)
            {
                FilterUpdated(this, EventArgs.Empty);
            }
        }
    
        public bool IsFiltered(Row row)
        {
            ...
        }
    
        public event EventHandler<EventArgs> FilterUpdated;
    }

    Visually it can look as follows:

    Dynamic column filters

Combined use of visual filters and a filter set via Grid.Filter property.

The grid supports combined operation of such filters basing on AND principle. If any of the filters instructs the grid to hide a row, this row becomes invisible. In event-driven model, if data objects implement INoifyPropertyChanged interface or are located in IBindingList collection, the grid receives notifications from these interfaces and checks visibility of rows that display changing data.

It's also worth mentioning that data can also be sorted or grouped by one or several columns. The grid fully hides the complexity of data filtering implementation. For example, let's look at grouped data. Each group can contain one or more visible rows. If all rows in a group become hidden, the group itself also becomes invisible, which is hard to achieve by manual data filtering. When event-driven model is used, filtering becomes a trivial task for the programmer, and therefore the programmer can concentrate on more important aspects of the application.