Click here to Skip to main content
15,885,309 members
Articles / Desktop Programming / Windows Forms

FilterableDataGridView (C#.NET)

Rate me:
Please Sign up or sign in to vote.
4.80/5 (4 votes)
29 Nov 2011CPOL4 min read 36.3K   11   11
A DataGridView control with built-in Filtering

Update 

The article was updated to the version 1.1. The links are already pointing to the new versions of the files. See details below.  

FilterableDataGridView 

While developing my project, I was left without Internet access for a week, and I couldn't search for an easy and already written solution for my problem, so I decided to create my own solution.

I had a DataGridView on one of my forms with a DataSource linked to an SQLserver through LINQ to SQL and I wanted to be able to filter the result rows, based on user input. Since the clients (the users of my program) will connect to the database through the Internet, some of them via poor connections, I didn't want to make a new query with each change of the filter. I found out that the DataGridViewRow has a Visible property, and it looked like an efficient way to set this according to the filters. So I created a new class inheriting from the DataGridView, and added some extra functionality to support filtering.

My solution consists of two classes:

  1. FilterItem
  2. FilterableDataGridView

1. FilterItem

This class represents a filter which can be applied to a FilterableDataGridView. It's basically two strings, one for storing the filter text, and the other to select the columns for the filter check. (This was a necessary requirement to be able to select the columns, since I didn't want it to search in my hidden ID column for example).

But the interesting part of this class is an event which gets fired every time the filter changes, and makes it possible that changing the filter text will apply the filter again without any additional method calls. See usage below.

The code:

C#
using System;
using System.Runtime.Serialization;

namespace Rankep.FilterableDataGridView
{
    /// <summary>
    /// Class representing a filter
    /// </summary>
    [Serializable()]
    public class FilterItem : ISerializable
    {
        /// <summary>
        /// The delegate behind the filter change event
        /// </summary>
        public delegate void FilterChangedHandler();
        
        /// <summary>
        /// Event for notification when the filter changes
        /// </summary>
        public event FilterChangedHandler FilterChanged;

        /// <summary>
        /// The filtering text
        /// </summary>
        private string _filter;
        /// <summary>
        /// Columns to filter
        /// </summary>
        private string _filterColumn;

        /// <summary>
        /// Gets or sets the filtering text
        /// </summary>
        public string Filter
        {
            get
            {
                return _filter;
            }
            set
            {
                _filter = value;
                //Check subscribers
                if (FilterChanged != null)
                    FilterChanged(); //Fire event
            }
        }
        /// <summary>
        /// Gets or sets the filtered columns (Columns should be delimited with '|')
        /// </summary>
        public string FilterColumn
        {
            get
            {
                return _filterColumn;
            }
            set
            {
                _filterColumn = value;
                //Check subscribers
                if (FilterChanged != null)
                    FilterChanged(); //Fire event
            }
        }

        /// <summary>
        /// Constructor
        /// </summary>
        public FilterItem()
        {
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="filter">Filtering text</param>
        /// <param name="column">Filtered columns</param>
        public FilterItem(string filter, string column)
        {
            _filter = filter;
            _filterColumn = column;
        }

        /// <summary>
        /// Constructor for deserialization
        /// </summary>
        /// <param name="info"></param>
        /// <param name="context"></param>
        public FilterItem(SerializationInfo info, StreamingContext context)
        {
            _filter = (string)info.GetValue("Filter", typeof(string));
            _filterColumn = (string)info.GetValue("FilterColumn", typeof(string));
        }

        /// <summary>
        /// Method supporting serialization
        /// </summary>
        /// <param name="info"></param>
        /// <param name="context"></param>
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("Filter", _filter);
            info.AddValue("FilterColumn", _filterColumn);
        }
    }

}

2. FilterableDataGridView

The class inherits from DataGridView. It contains a collection of FilterItems as a public property named Filters, you can add your filters to this list. The collections type is ObservableCollection<>, which is only available from .NET 4.0, so this sets the minimum requirements of the whole project.
When the property’s value is set, the object subscribes to the collection’s CollectionChanged event, it’s handled the following way:

  • If a new FilterItem is added, it subscribes to its FilterChanged event.
  • If an item is removed, it unsubscribes from its event.
  • Finally the Filter() method is called.

The Filter method checks every row for every filter and decides (sets) if the row should be visible or not.

Version 1.1  

First of all, many thanks to Kabwla.Phone for the great suggestions, most of the change is that he wrote in his comments. 

  • If the Filters collection is changed, the event handler is released, so the old collection can be garbage collected.
  • BeginFilterUpdate() and EndFilterUpdate() methods are added 
  • The same FilterItem cannot be added more than once 
  • Bug fix: the Filter method now skips the new uncommitted row 
  • The sample is updated also 

The code (v1.1):  

C#
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Collections.ObjectModel;
using System.Linq;

/* 
 * Many thanks to Kabwla.Phone from codeproject.com for suggestions to make the control better.
 */

namespace Rankep.FilterableDataGridView
{
    /// <summary>
    /// The class extends the functionality of a DataGridView with filtering
    /// </summary>
    [Serializable()]
    [ToolboxBitmap(typeof(DataGridView))]
    public partial class FilterableDataGridView : DataGridView
    {
        private object syncroot = new object();

        /// <summary>
        /// Indicates whether the filters are in updating mode.
        /// Call BeginFilterUpdate() to enter updating mode, and EndFilterUpdate() to exit.
        /// </summary>
        public bool IsFilterUpdating { get; private set; }

        /// <summary>
        /// Indicates whether the content should be filtered after updating mode is exited.
        /// </summary>
        public bool IsFilterDirty { get; private set; }

        /// <summary>
        /// A collection to store the filters
        /// </summary>
        /// <remarks>
        /// ObservableCollection requires .NET 4.0
        /// </remarks>
        private ObservableCollection<FilterItem> _filters;

        /// <summary>
        /// Property to get or set the collection of the filters
        /// </summary>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public ObservableCollection<FilterItem> Filters
        {
            get
            {
                return _filters;
            }
            set
            {
                //Remove eventhandler so the original collection is 'free' and can be garbage collected.
                if (_filters != null)
                {
                    _filters.CollectionChanged -= _filters_CollectionChanged;
                }

                _filters = value;

                if (_filters != null)
                {
                    //remove again, it shold not be linked, however to be sure. 
                    _filters.CollectionChanged -= _filters_CollectionChanged;

                    // Subscribe to the CollectionChanged event, so the filtering can be done automatically
                    _filters.CollectionChanged += _filters_CollectionChanged;
                }
            }
        }

        /// <summary>
        /// The event handler of the filters CollectionChanged event, 
        /// so the filtering can be automatic when the collection changes
        /// </summary>
        /// <param name="sender">sender</param>
        /// <param name="e">Event arguments</param>
        void _filters_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            //Indicates whether the collection really changed
            bool changed = true;

            //If a new element is added
            if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
            {
                foreach (FilterItem fi in e.NewItems)
                {
                    //If the collection already contains this FilterItem
                    if (_filters.Count(x => x.Equals(fi)) > 1)
                    {
                        lock (syncroot)
                        {
                            //Disable the eventhandler while removing the item
                            _filters.CollectionChanged -= _filters_CollectionChanged;
                            //Remove the newly added FilterItem from the collection
                            _filters.RemoveAt(e.NewItems.IndexOf(fi));
                            //Enable the eventhandler
                            _filters.CollectionChanged += _filters_CollectionChanged;
                            //The collection actually didn't change, there's no need to refilter
                            changed = false;
                        }
                    }
                    else
                    {
                        //subscribe to its event, so filtering will be done automatically every time when the filter changes
                        fi.FilterChanged += Filter;
                    }
                }
            }
            //If the filter is removed
            else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
            {
                foreach (FilterItem fi in e.OldItems)
                {
                    //unsubscribe from its event
                    fi.FilterChanged -= Filter;
                }
            }

            //Finally filter the list
            if (changed)
                Filter();
        }

        /// <summary>
        /// Constructor
        /// </summary>
        public FilterableDataGridView()
        {
            InitializeComponent();

            Filters = new ObservableCollection<FilterItem>();
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="container"></param>
        public FilterableDataGridView(IContainer container)
        {
            container.Add(this);

            InitializeComponent();

            Filters = new ObservableCollection<FilterItem>();
        }

        /// <summary>
        /// Sets the IsFilterUpdating property to true, 
        /// so you can perform multiple changes 
        /// and the filtering will be done only after EndFilterUpdate() is called
        /// </summary>
        public void BeginFilterUpdate()
        {
            IsFilterUpdating = true;
        }

        /// <summary>
        /// Sets the IsFilterUpdating property to false,
        /// and performs the filtering if it is necessary
        /// </summary>
        public void EndFilterUpdate()
        {
            IsFilterUpdating = false;
            if (IsFilterDirty)
                Filter();
        }

        /// <summary>
        /// Method to filter the DataGridView
        /// It gets called whenever the filters collection changes or a filter changes.
        /// Explicit call is possible, however not necessary.
        /// </summary>
        public void Filter()
        {
            if (IsFilterUpdating)
            {
                IsFilterDirty = true;
            }
            else
            {
                IsFilterDirty = false;
                //Set the selected cell to null, so every cell(/row) can be hidden
                this.CurrentCell = null;
                //Check every row in the DataGridView
                foreach (DataGridViewRow row in Rows)
                {
                    //Skip the NewRow
                    if (row.Index != this.NewRowIndex)
                    {
                        bool visible = true;
                        //Check every FilterItem
                        foreach (FilterItem fi in _filters)
                        {
                            //The char used to delimit the columns from each other
                            // it is also used to denote an OR relation ship between the filter texts
                            char c = '|';
                            //Split the string to column names
                            List<string> columns = new List<string>(fi.FilterColumn.Split(c));
                            List<string> filters = new List<string>(fi.Filter.Split(c));
                            bool atLeastOneContains = false;

                            //Check every columns
                            foreach (string column in columns)
                            {
                                foreach (string filter in filters)
                                {
                                    if (row.Cells[column].Value != null && row.Cells[column].Value.ToString().ToUpper().Contains(filter.ToUpper()))
                                    {
                                        //If the column contains any of the filter texts, the filter is satisfied
                                        atLeastOneContains = true;
                                        break;
                                    }
                                }
                                if (atLeastOneContains)
                                {
                                    //If the column contains the filter text, the filter is satisfied
                                    break;
                                }
                            }
                            //If none of the columns contain the text, the row can't be visible
                            if (!atLeastOneContains)
                            {
                                visible = false;
                                break;
                            }
                        }
                        //Set the Visible property the Row
                        row.Visible = visible;
                    }
                }
            }
        }
    }
}

Usage

The FilterItem Properties

  • Filter: Set a string that has to be found in the rows values. Upper or lower case doesn't matter. The ‘|’ character will translate as OR, thus any of the strings on the sides of ‘|’ can satisfy the filtering condition. For an AND expression, please use multiple FilterItems, since a row has to meet the expressions of all FilterItems present in the FilterableDataGridView Filters collection. Regular expressions are not supported, yet.
  • FilterColumn: List the columns where the search should be done, use '|' to separate column names.

E.g.:

C#
//every row, which contains the string "John" in the column called "name"
new FilterItem("John", "name"); 

//every row, which contains the string "John" in the column called "name" or "address" or "phone"
new FilterItem("John", "name|address|phone"); 

//every row, which contains the string "John" or "Jane" in the column called "name" or "address" or "phone"
new FilterItem("John|Jane", "name|address|phone");  

Using theFilterableDataGridView

Let's say you have a TextBox (named textbox1) and you want the FilterableDataGridView to filter when the text changes in the textbox.

  1. Add a FilterableDataGridView to your Form, from the toolbox. Make sure to implement the proper using directive:
    C#
    using Rankep.FilterableDataGridView; 
  2. Create a FilterItem which you will use to update the filter. (Don't create a new one every time, or if you do remember to delete the old ones from the list, because all of the Filters added to the FilterableDataGridView. Filters list will be checked and will have to be satisfied.)

    Example:

    C#
    public partial class Form1 : Form
        {
            //A FilterItem for the text in the TextBox
            private FilterItem textboxFilter;
            ...
        }
  3. Assign a new FilterItem to it.
    Example:
    C#
    textboxFilter = new FilterItem("", "Name|Address|Tel");
  4. Subscribe to the textbox1 TextChanged event, and change the textboxFilter.Filter to the Text property of the Textbox. (Check if the FilterableDataGridView.Filters contains the FilterItem, if not add it).

    Example:

    C#
    private void textBox1_TextChanged(object sender, EventArgs e)
            {
                //Change the filter Text
                textboxFilter.Filter = textBox1.Text;
    
                //If the FilterableDataGridView doesn't contain the filter yet
                if (!filterableDataGridView1.Filters.Contains(textboxFilter))
                    //then add it
                    filterableDataGridView1.Filters.Add(textboxFilter);
            }

Using the precompiled .dll component:

  1. Copy the FilterableDataGridView.dll file to your project directory.
  2. Open your project in Visual Studio
  3. Right click on the Toolbox in the category where you would like to add the component.
  4. Select Choose Items…
  5. Browse…
  6. Open the FilterableDataGridView.dll
  7. Make sure that the checkbox in front of FilterableDataGridView is checked, and click OK.
  8. The component has now appeared in the toolbox, and you can use it as any other component. Detailed usage is described above. 

Usage to update the collection with more than one FilterItem at same time: 

C#
filterableDataGridView1.BeginFilterUpdate();
try
{
    filterableDataGridView1.Filters.Add(filter);
    filterableDataGridView1.Filters.Add(filter2);
    filterableDataGridView1.Filters.Remove(filter3);
    ///...
}
finally
{
    filterableDataGridView1.EndFilterUpdate();
}

In this case the Filter() method will be only called with the EndFilterUpdate(), instead of being called every time the collection is changed. 

Thank you for reading, feel free to use it, copy it, share it, improve it, ...

Any questions, advice is welcome in the comments.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Student
Poland Poland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questionwith mysql database Pin
Khalesar3-Jul-14 4:23
Khalesar3-Jul-14 4:23 
AnswerRe: with mysql database Pin
Khalesar4-Jul-14 1:18
Khalesar4-Jul-14 1:18 
Questionhanging with large amount of data Pin
Ehsan Faramarzi3-Apr-14 2:49
Ehsan Faramarzi3-Apr-14 2:49 
QuestionHi Pin
Aydin Homay4-Aug-13 2:30
Aydin Homay4-Aug-13 2:30 
Questionchange the color Pin
SeyedMorteza24-Dec-12 19:40
SeyedMorteza24-Dec-12 19:40 
QuestionI just noticed... Pin
Kabwla.Phone29-Nov-11 0:08
Kabwla.Phone29-Nov-11 0:08 
SuggestionRequires BeginUpdate/EndUpdate methods. Pin
Kabwla.Phone28-Nov-11 21:57
Kabwla.Phone28-Nov-11 21:57 
SuggestionSuggestions for cleanup. Pin
Kabwla.Phone28-Nov-11 21:44
Kabwla.Phone28-Nov-11 21:44 
QuestionNice, what is the advantage over the rowfilter property on a Bindingsource? Pin
Kabwla.Phone28-Nov-11 21:26
Kabwla.Phone28-Nov-11 21:26 
AnswerRe: Nice, what is the advantage over the rowfilter property on a Bindingsource? Pin
Akos Orban29-Nov-11 3:31
Akos Orban29-Nov-11 3:31 
GeneralRe: Nice, what is the advantage over the rowfilter property on a Bindingsource? Pin
Kabwla.Phone29-Nov-11 4:22
Kabwla.Phone29-Nov-11 4:22 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.