Filtering is an important element that simplifies navigation over large data volumes for end users by displaying only the requested data. The main requirements to filtering are:

  • Convenient API for implementing simple filters and composite filters that filter data by different criteria at different hierarchy levels.
  • Filtered rows should preserve their state (position, expansion, selection, place in hierarchy and existence of children) so that they could be shown in the positions where they were hidden upon change of filtering criteria.
  • High filtering performance to prevent GUI slowdown when large data volumes are filtered.
  • Filtering of dynamically updated data, i.e. data objects should be checked for compliance with filtering criteria upon every change. It’s best to implement it through notifications from INotifyPropertyChanged and IBindingList interfaces.
  • Data filtering should work both for classical data presentation (with or without hierarchy) and for grouping. Groups that contain only filtered rows should be filtered out as well. Programming API should remain unchanged and as simple to implement as possible.
  • Data filtering should be thread-safe without regard to threads used by data objects.
  • Data filtering should work without regard to the method of object placement in the grid. By calling Grid.Rows.Add method or a binging list, the grid follows all requirements and provides a simple and convenient interface.

The grid provides a simple IFilter interface with a single IFilter.IsFiltered(Row row) method for filter implementation. This method is called every time when new data is added to the grid or when the grid gets a notification from INotifyPropertyChanged or IBindingList interfaces or when Row.Update() method is called. This implementation works without regard of hierarchy or data grouping and provides the programmer with a simple and convenient interface.

C# Copy imageCopy
public class Product : INotifyPropertyChanged
{
    private double price;

    public double Price
    {
        get { return price; }
        set
        {
            //If the price is not the same, change it and notify about price changing
            if (price != value)
            {
                price = value;
                //The event can be raised from any thread. The grid will synchronize thread with GUI without blocking the calling thread.
                //While painting, sorting or filtering the grid can ask this object in the GUI (!) thread to return the price value.
                if(PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("Price"));
                }
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

public void CreateFilter(Grid grid)
{
    //Set a filter that hides all rows, that contain products with price less than 10
    grid.Filter = new Filter(delegate(Row row)
    {
        //There are three ways to get price: 
        //1. From the Cell through the Value property, which returns a double value: row["Price"].Value
        //2. Through the IDataAccessor and IDataField: row.DataAccessor["Price"].Value
        //3. From the data object itself: ((Product)row.DataObject).Price

        if ((double)row["Price"].Value < 10)
        {
            //Filter the row
            return true;
        }

        //The row is not filtered
        return false;
    });
}

//Populate the grid
public void PopulateGrid(Grid grid)
{
    Product product1 = new Product();
    Product product2 = new Product();

    grid.Rows.Add(product1);
    grid.Rows.Add(product2);

    //Update the product's price
    //Data objects will notify the Grid, and it will display only the product2. 
    //The product1 will be hidden
    product1.Price = 9;
    product2.Price = 11;
}