C# Code, Tutorials and Full Visual Studio Projects

Excel like AutoFilter in WPF

Posted by on Oct 13, 2011 in Code Snippets, WPF | 3 comments

Excel like AutoFilter in WPF

If you use Microsoft Excel I’m sure you’ve seen the AutoFilter feature where you can filter the data in each column based on the
contents of that column. You can just click on the header of the column and select or unselect the items you wish to view. Were going to make that same type of UI using WPF.

Download the Source Code





Here is a screenshot of what this app looks like.

Excel AutoFilter in C# WPF

Excel AutoFilter in C# WPF

Using WPF and the Datagrid we can mimic this behavior to let our users have this type of filtering.

We will be using a simple Customer class in this sample.

public class Customer : INotifyPropertyChanged 
{
    public event PropertyChangedEventHandler PropertyChanged;
    private string name;
    private string country;
        
    public string Country
    {
        get { return country; }
        set
        {
            country = value;
            if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Country"));
        }
    }

    public string Name
    {
        get { return name; }
        set
        {
            name = value;
            if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Name"));
        }
    }
}

We will make it so the Country field of the Customer class allows Excel like sorting.

Lets jump to the XAML as that is where the real magic is. To get the little filter button in the column heading we need to modify the column heading in our DataGrid.

<DataGridTextColumn Binding="{Binding Country}" >
    <!--Modify the header to add the filter-->
    <DataGridTextColumn.Header>
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="Country" />
            <Button Name="btnCountryFilter" Margin="3,0,0,0" Click="btnCountryFilter_Click">
                <Button.Template>
                    <ControlTemplate>
                        <Image Source="/Images/filter.png" Width="10" Height="10" />
                    </ControlTemplate>
                </Button.Template>
            </Button>
        </StackPanel>
    </DataGridTextColumn.Header>
</DataGridTextColumn>

So you can see it’s just a simple button that shows an image. When you click it calls a EventHandler that opens a Popup.

private void btnCountryFilter_Click(object sender, RoutedEventArgs e)
{
    popCountry.IsOpen = true;
}

Here is the Popup XAML

<!--Add the popup that appears when the filter is clicked-->
<Popup Name="popCountry" Placement="Bottom" PlacementTarget="{Binding ElementName=btnCountryFilter}" StaysOpen="False" Width="200">
    <Border Background="White" BorderBrush="Gray" BorderThickness="1,1,1,1">
        <StackPanel Margin="5,5,5,15">
            <StackPanel Orientation="Horizontal" Margin="0,0,0,15">
                <Button Margin="0,0,0,0" Name="btnSelectAll" Click="btnSelectAll_Click">
                    <Button.Template>
                        <ControlTemplate>
                            <TextBlock Text="Select All" Foreground="Blue" Cursor="Hand" />
                        </ControlTemplate>
                    </Button.Template>
                </Button>

                <Button Margin="10,0,0,0" Name="btnUnselectAll" Click="btnUnselectAll_Click">
                    <Button.Template>
                        <ControlTemplate>
                            <TextBlock Text="Select None" Foreground="Blue" Cursor="Hand" />
                        </ControlTemplate>
                    </Button.Template>
                </Button>
            </StackPanel>

            <ListBox x:Name="lstCountries" BorderThickness="0">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <CheckBox IsChecked="{Binding IsChecked}" Content="{Binding Item}" Checked="ApplyFilters" Unchecked="ApplyFilters" />
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </StackPanel>
    </Border>
</Popup>

The Popup shows two buttons at the top to select all the items and to unselect all the items; it also contains a ListBox that displays each filter as a CheckBox.

In the Code Behind we have the following constructor:

private ObservableCollection<Customer> customers = new ObservableCollection<Customer>();
private ObservableCollection<CheckedListItem<string>> customerFilters = new ObservableCollection<CheckedListItem<string>>();
private CollectionViewSource viewSource = new CollectionViewSource();

public MainWindow()
{
    InitializeComponent();

    //Make sample data
    customers.Add(new Customer() { Name = "John Smith", Country = "CA" });
    customers.Add(new Customer() { Name = "Jake Shields", Country = "US" });
    customers.Add(new Customer() { Name = "Shelly Brown", Country = "AU" });
    customers.Add(new Customer() { Name = "Sidney Wright", Country = "CA" });
    customers.Add(new Customer() { Name = "Bart Simpson", Country = "US" });

    //Create a list of filters
    foreach (string cust in customers.Select(w => w.Country).Distinct().OrderBy(w => w))
    {
        customerFilters.Add(new CheckedListItem<string> { Item = cust, IsChecked = true });
    }

    viewSource.Filter += viewSource_Filter;

    viewSource.Source = customers;

    grdData.ItemsSource = viewSource.View;

    lstCountries.ItemsSource = customerFilters;

    DataContext = this;
}

We create a list of our customers and a CollectionViewSource. That CollectionViewSource is set to use the Customers as the source. This allows us an easy mechanism to filter the items
when the user checks or unchecks a filter.

Notice we iterate through the list of values in the Country property of the Customer and add each one to the customerFilters collection. This gives us a collection of unique items to display
in that ListBox in the Popup. The CheckedListItem class there is just a wrapper to hold the checked/unchecked state of each item. You can read more about that here: How to Create a CheckedListBox in WPF.

Here is the filter code:

private void viewSource_Filter(object sender, FilterEventArgs e)
{
    Customer cust = (Customer)e.Item;

    int count = customerFilters.Where(w => w.IsChecked).Count(w => w.Item == cust.Country);

    if (count == 0)
    {
        e.Accepted = false;
        return;
    }

    e.Accepted = true;
}

The filter just looks to see if an item is not checked. If it can’t be found it doesn’t accept the item.

Here is the code on the Select All and Select None buttons.


private void btnSelectAll_Click(object sender, RoutedEventArgs e)
{
    foreach (CheckedListItem<string> item in customerFilters)
    {
        item.IsChecked = true;
    }
}

private void btnUnselectAll_Click(object sender, RoutedEventArgs e)
{
    foreach (CheckedListItem<string> item in customerFilters)
    {
        item.IsChecked = false;
    }
}

When a user checks or unchecks an item the ApplyFilters method is called. This method just refreshes the CollectionViewSource.


private void ApplyFilters(object sender, RoutedEventArgs e)
{
    viewSource.View.Refresh();
}

3 Comments

Join the conversation and post a comment.

  1. Christo

    Thanks man, this is awesome.. I enjoy you’re posts a lot!
    Keep up the good work!

  2. Karthik

    Real cool……..

Leave a Comment

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>