Dapfor .Net Grid Introduction

Part1: Data Types
Part2: Data Binding
Part3: Event-driven model
Part4: Data formatting and presentation
Part5: Threadsafety
Part6: Threadsafe BindingList
Part7: Appearance and painting
Part8: Cell highlighting
Part9: Data filtering
Part10: Data sorting
Part11: Data grouping
Part12: Headers and columns
Part13: Editors
Part14: Performance. Practical recommendations
Part15: Real-time blotter
Part16: Currency converter
Part17: Instrument browser
Part18: Order book
Part19: Basket Viewer



.Net Grid Tutorial Part9: Data filtering

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.


    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)


    //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 an be placed in a column as a visual filter.

    Custom filter in the VS designer

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


    //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:


    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;
    }
    }

    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.


    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.