Click here to Skip to main content
Click here to Skip to main content

Multi-filtered WPF DataGrid with MVVM

By , 18 Aug 2012
 

Introduction  

This article demonstrates how to implement multiple filters for a WPF DataGrid using the MVVM paradigm and can probably be easily modified to provide multi-filtering on other collection controls like a ListBox. There are articles in CodeProject and other sites which describe general purpose filtering or auto-filtering in a DataGrid, but they use code behind rather than MVVM (see references). The goal of this article is to provide a simple example that can be adapted to meet your project needs with a minimum of refactoring. The impetus for this article was a requirement for custom filtering with multiple filters (the filter mechanism needed to include synonyms of selected filter words). The example present in this article does allow for custom filter logic.

This article is marked as intermediate article because it assumes the reader will have some experience with the MVVM design paradigm. Despite this the sample should be simple, readable code that could be easily followed by beginners too.

The sample code provides a single view with a DataGrid that displays a collection of Thing objects. A Thing is a simple data class which contains multiple properties. The DropDownLists at the top of the view each apply a filter when a value is selected. The filters can be applied in any order and when a filter is applied the red x button can be used to remove the filter. The Reset button removes all filters. 

The code sample is implemented with .NET 3.5 and uses the following:

The required libraries are bundled in a solution folder and should not need to be installed separately.   The solution has 3 projects but the two classes of interest are MainView.xaml and MainViewModel.cs, all others are there to support the example. 

Using the code 

The key to implementing multiple filters in WPF is to instantiate a CollectionViewSource instance rather than using the CollectionViewSource.GetDefaultView() static method. The difference between the two can be summed up as follows:

  • CollectionViewSource.GetDefaultView() returns an ICollectionView which allows a single filter using the Filter property. Each time this property is changed it resets the previous filter.
  • Instantiating a CollectionViewSource allows for multiple filters by providing a Filter event. Multiple filter event handlers can subscribe to this event and each filter is applied in the order it is subscribed to the event. To remove a filter involves unsubscribing from the event. 

Here are the key steps in the example each associated file:

  1. Load the data into the MainViewModel from the DataService (the example just loads an xml file) to populate the Things property
  2. Instantiate the CollectionViewSource object in the MainView and bind it to the collection of Thing objects which was loaded in step 1 (~ line 13 of MainView.xaml) (*)
    <Window.Resources>
       ...
       <CollectionViewSource Source="{Binding Things}" x:Key="X_CVS"/>
    </Window.Resources>
  3. Bind the CollectionViewSource instance to the DataGrid in the MainView (~ line 51 of MainView.xaml)
    ...
    <Custom:DataGrid ItemSource="{Binding Source={StaticResource X_CVS}}" 
           Margin="8" Grid.Row="1" AutoGenerateColumns="True" 
           IsReadOnly="True"></Custom:DataGrid>
    ...
  4. A reference to the CollectionViewSource instance is passed to the MainViewModel (~ line 18 of MainView.xaml.cs) where it is assigned to the local property CVS (~ line 265 of MainViewModel.cs)
  5. When a target filter is specified in a drop down list, a filter event handler is subscribed to the filter event of the CollectionViewSource
  6. When a filter is removed, the corresponding event handler is un-subscribed from the Filter event of the CollectionViewSource 

One thing to note about the filter methods as seen in the following method which filters each row based on the SelectedAuthor property of the MainViewModel:

private void FilterByAuthor(object sender, FilterEventArgs e)
{
    var src = e.Item as Thing;
    if (src == null)
        e.Accepted = false;
    else if (string.Compare(SelectedAuthor, src.Author) != 0)
        e.Accepted = false;
}

A row in the grid does not match the filter and is hidden by setting e.Accepted = false. Note that e.Accepted is not set to true if a match is found. Rather, only set e.Accepted to false for items that do not match. This enables filter results to be applied on top of existing filter results. If e.Accepted is set to true, it may negate any previous filter results.

References    

WPF’s CollectionViewSource by Bea Stollnitz is an article for multiple filters on a ListBox using the CollectionViewSource mechanism with code behind and was responsible for leading me down the path to this creating this example.

The following are several CodeProject articles with code-behind WPF DataGrid filtering:

History

18 Aug 2012:  updated the example code to include wpftoolkit.dll in the solution folder.

License

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

About the Author

M. Pastore
United States United States
Member
Neck deep in Social Network Analysis

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionMVVM with multiple ModelsmemberOctopus112620 May '13 - 10:34 
Thank you for this nice article. this is what i was looking for except i have about 3 models ...its only 1 model here(Things) .. my question is how do u populate the grid based on three models filter?
for example if we have Student, Course and Grade.
I wanted to populate these three comboboxes like
show student's course when a student is selected..
show his grade when his course is selected
show his grade on gridview
 
Thank you
AnswerRe: MVVM with multiple ModelsmemberM. Pastore20 May '13 - 11:21 
In the sample code, each instance of a Thing corresponds to a row in the DataGrid. So if Student, Course, and Grade are separate classes, then you can consider wrapping them into a parent class and then you would have 1 object corresponding to each row in the DataGrid. Note that there are many ways you could approach this. My suggestion would be to have the wrapper class be a View Model itself. But, a simple example could be as follows. Say Student, Course, Grade look like:
 
public class Student
{
    public string Name {get;set;}
    public int Age {get;set;}
}
 
public class Course
{
    public string Title {get;set;}
    public int Credits {get;set;}
}
 
public class Grade
{
    public double GPA {get;set;}
    public DateTime Submitted {get;set;}
}
 
Then a Session class would just wrap each of these as follows:
 
public class Session
{
    private Student _student;
    private Course _course;
    private Grade _grade;
    
    public Session (Student student, Course course, Grade grade)
    {
        _student = student;
        _course = course;
        _grade = grade;    
    }
 
    public string Name {get { return _student.name;}}
    public int Age {get { return _student.Age;}}
    public string Title {get { return _course.Title;}}
    public int Credits {get { return _course.Credits;}}
    public double GPA {get { return _grade.GPA;}}
    public DateTime Submitted {get { return _grade.Submitted;}}
}
 
Each Session would be bound to an individual row in the datagrid, and you could use use any of the properties in the filters as was done with the Thing class in the sample code.
GeneralRe: MVVM with multiple ModelsmemberOctopus112621 May '13 - 4:35 
Thank you so much for your amazing explanation:
My case has more of a chain structure where the second combo box(course) depends on the first (student id)and the third grid(GPA ,Datasubmited ) populates values related to the second combo(course selected).
Here is what I did
//for first combobox
In my sessionclass : that contains a reference to all the ThreeModels
1. I created a reference to a student model
2. Call a getAllStudents method in student class to get lists of students(loaddata)
3. Display all students
How do I AddStudentFilter to filter by student ID??
Here is what I tried
private void FilterByStudent(object sender, FilterEventArgs e)
{
var src = e.Item as StudentModel;
if (src == null)
e.Accepted = false;
else if (string.Compare(SelectedStudent, src.studentID) != 0)
e.Accepted = false;
}
 
//for second combobox
1. I created a reference to a Course model
2. Call a getAllCourses method in student class to get lists of students(loaddata)
3. Display all course
AddCourseFilter to filter courses related to the student ID selected??
private void FilterByCourse(object sender, FilterEventArgs e)
{
var src = e.Item as CourseModel;
if (src == null)
e.Accepted = false;
else if (string.Compare(SelectedCourse, src.courseid) != 0)
e.Accepted = false;
}
 

// for the grid  the idea is the grid should contain only the attributes of Grad class
1. I created a reference to a Grad model
2. Call a getAllStudents method in student class to get lists of students(loaddata)
3. Display all grade.
AddGradeFilter to filter by coursid and show the grade in a TABLE ??
The tables column is created from grade attribute.
 
private void FilterByGrade(object sender, FilterEventArgs e)
{
var src = e.Item as GradeModel;
if (src == null)
e.Accepted = false;
else if (string.Compare(SelectedGrade, src.gradeid) != 0)
e.Accepted = false;
}
 
// I call this method in the constructor: all these data methods gets data from the dummy service I created/
private void LoadData()
{
StudentModel.ObservabletudentList();
CourseModel.ObservableCourseList();
GradeModel.ObservableGradeList();

}
 
///
/// a message recieved from the View which enables a reference to the
/// instantiated CollectionViewSource to be used in the ViewModel.
///

private void Handle_ViewCollectionViewSourceMessageToken(ViewCollectionViewSourceMessageToken token)
{
CVS = token.CVS;
}
 
// My UI : which I think I am doing it wrong since I need to be able to bind it to Student, course, grade:
// but in my MainViewModel : I Filter them and add them all as private CollectionViewSource CVS
 





 
// two combos for student and course


// one grid for grade

 

Too long but i want to make sure i deliver it right.
 
Thank you.
Octopus1126
GeneralRe: MVVM with multiple ModelsmemberM. Pastore22 May '13 - 5:45 
If you want multiple filters Student, Course such that
when the Student filter is set, the items in the drop down list for Course
filter only apply to the student selected in the first filter, my suggestion
would be as follows:
 
When a student is selected in the Student filter, before the filter is applied
you can add logic to the ApplyFilter method in the example code similar to the following:
 
private void ApplyFilter(FilterField field)
{
    switch(field)
    {
        case FilterFiled.StudentId:
        
            // this would update the list of Courses in the Courses filter drop down list to only
            // those courses for the selected student
            Courses = getAllCourses(_selectedStudendId); 
        
            ApplyStudentIdFilter();
            break;
        case FilterFiled.Course:
        
            ApplyCourseFilter();
            break;
    
    }
}
 
After a student Id filter is selected, but before it is applied, the list of courses
in the drop down list for the course filter would be modified to only show courses
for the selected student.
 
You will also need to take into account that the filters now have a dependency on
one another: Course depends on StudentId so if StudentId is not set you may need to
disable the course filter. Ultimately this will depend on what your users want.
GeneralMy vote of 5memberKhaled Kokah23 Apr '13 - 21:46 
Great result and very simple.
QuestionErrormemberMember 45586618 Aug '12 - 1:44 
Error:
teg "DataGrid" don't exist in namespace XML "http://schemas.microsoft.com/wpf/2008/toolkit". 10. (MC3074) - \...\DataGrid_WPF\Multi-filtered WPF DataGrid with MVVM\MultiFilteredDataGrid_Source\MultiFilteredDataGrid_Source\MultiFilteredDataGridMVVM\View\MainView.xaml:51,1
Rand

AnswerRe: ErrormemberM. Pastore18 Aug '12 - 4:27 
Thanks for the feedback. I updated the example project so the WPFToolkit.dll is included in a solution folder.
 
Cheers,
 
Mike
GeneralThanks!!!memberMember 45586621 Aug '12 - 9:14 
Thanks!!!
Rand

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

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 18 Aug 2012
Article Copyright 2012 by M. Pastore
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid