Filtering Data in your View Model

Have you ever written an application, or are in the process of writing one, in which a large amount of data is being displayed on screen?  At some point you realize that you want the users to be able to filter the data, but you are not sure of the best way to do it.   In this article we will take an existing application with 20 records and add a control to filter the visible data.  For our sample application I have chosen to use flights, and add a filter on the price of the flight. The methodology demonstrated in this sample can be applied to a larger application in which you have multiple filter criteria, and live data from a real time or static data source.

The completed project is now available in C#, VB.NET, and C++ here.   Please continue reading to walk through implementing this in C#.

For starters download and open the sample application, located at the bottom of the post, which has a single page application showing a list of flight data:

StartView

There are two files we will be modifying in this sample application.  The first is the ItemsPage.xaml, and the second is the MainViewModel.cs.  ItemsPage.xaml is the Main View of the application that displays all of the flight information and MainViewModel.cs is the ViewModel that exposes properties and methods in which the ItemsPage.xaml is bound to.  The flight information is displayed in a GridView whose ItemsSource property is bound to the Flights property of the MainViewModel.  Currently the Flights property returns all of the flights.  The rest of this article will show how to modify MainViewModel to expose a FilteredFlights property, bind that to the GridView, add a control to filter on price, and finally a function that keeps the FilteredFlights property up to date based on the current price filter.

NOTE:   If you want to skip typing code yourself, you can rename the ItemsPageEnd.xaml and MainViewModelEnd.cs to ItemsPage.xaml and MainViewModel.cs respectively.  These files are not part of the project but should be in the directory for the project.

Create a Filtered Data Property

MainViewModel has a Flights property which is an ObservableCollection of FlightDataItem.   This property is initialized with all of the flights in the data source, which is FlightData.json.   To support filtering we will add a Property called FilteredFlights to our view model.  To do this open the MainViewModel.cs file and add the following code after the Flights property:

 private ObservableCollection<FlightDataItem> _filteredFlights;
  
 public ObservableCollection<FlightDataItem> FilteredFlights
 {
     get { return _filteredFlights; }
     set { _filteredFlights = value; NotifyPropertyChanged("FilteredFlights"); }
 }

 

In the MainViewModel constructor add the following line of code to initialize the FilteredFlights to contain all of the flights:

 FilteredFlights = Flights;

Bind the GridView to the Filtered Data Property

Now that their is a FilteredFlights property in our MainViewModel we will bind it to our GridView.  Open the ItemsPage.xaml file in XAML view and modify the ItemsSource property of the GridView to bind to FilteredFlights instead of flights:

 <GridView
    x:Name="itemGridView"
    AutomationProperties.AutomationId="ItemsGridView"
    AutomationProperties.Name="Items"
    TabIndex="1"
    Grid.RowSpan="2"
    Padding="116,136,116,46"
    ItemsSource="{Binding FilteredFlights}"
    SelectionMode="None"
    IsSwipeEnabled="false"
    IsItemClickEnabled="True">

At this point and time if you run the application you should see the same results as before.  However, the plumbing is now in place for us to add some more functionality to the view so we can filter the data.

Add a control to filter the data

Continuing in the ItemsPage.xaml file we will modify the layout so it can host a slider control for our price filter.  For starters we will give our main layout Grid two columns by adding column definitions:

 <!--
    This grid acts as a root panel for the page that defines two rows:
    * Row 0 contains the back button and page title
    * Row 1 contains the rest of the page layout
-->
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="344*"/>
        <ColumnDefinition Width="1023*"/>
    </Grid.ColumnDefinitions>

Next we will modify the position of the GridView.  We remove the Padding and  RowSpan property and add the Grid.Column,, Grid.Row, and Margin properties as shown below:

 <GridView
    x:Name="itemGridView"
    AutomationProperties.AutomationId="ItemsGridView"
    AutomationProperties.Name="Items"
    TabIndex="1"
    ItemsSource="{Binding FilteredFlights}"
    SelectionMode="None"
    IsSwipeEnabled="false"
    IsItemClickEnabled="True" Grid.Column="1" Margin="20,20,0,0" Grid.Row="1">

Add the layout element and controls for our price filter after the GridView.  Note the Value property of the Slider control is bound to the SelectedPrice property which we have not yet added to our view model.  We will do this in the next section.  Also note the binding Mode is TwoWay.

 <StackPanel Grid.Row="1">
    <TextBlock Text="Price Filter" FontSize="48" Margin="10" />
    <Slider Height="50" Margin="10" Minimum="100.00" Maximum="2265.00" Value="{Binding Path=SelectedPrice, Mode=TwoWay}" />
</StackPanel>

Finally, we need to fix the layout of the Back button and page title to span both of our columns:

 <!-- Back button and page title -->
<Grid Grid.ColumnSpan="2">

This concludes the changes needed in the view for filtering our data.  If you ran the application now, the slider will move, but the data will not yet change.  We will add the code to do this in the final section of this article.

Bringing it all together in the MainViewModel

In our final section we will add a function named RefreshFilteredData to filter our data, and the SelectedPrice property to  our MainViewModel.cs file.  The RefreshFilteredData function selects all items from the Flights collection whose price is less than the SelectedPrice.

 private void RefreshFilteredData()
 {
     var fr = from fobjs in Flights
             where fobjs.Price < SelectedPrice
             select fobjs;
  
     // This will limit the amount of view refreshes
     if (FilteredFlights.Count == fr.Count())
         return;
  
     FilteredFlights = new ObservableCollection<FlightDataItem>(fr);
 }

The following code is the definition of our SelectedPrice property.  When this property changes, which will be the case when the price slider moves because of our TwoWay binding, it will call the RefreshFilteredData function to update the FilteredFlights property of the MainViewModel.

 private double _selectedPrice;
  
 public double SelectedPrice
 {
     get { return _selectedPrice; }
     set { _selectedPrice = value; NotifyPropertyChanged("SelectedPrice"); RefreshFilteredData(); }
 }

Lastly, we need to initialize the SelectedPrice property by adding the following to the end of the LoadFlightData() function

 SelectedPrice = maxPrice;

This concludes the work needed to update our MainViewModel to support the data filtering.   At this point in time if you run the application and move the Slider control to the left, you should see the Flight data filtered to only show prices in the selected price range.  The screenshot below shows a sample when the slider is at the value 500:

EndView

Conclusions

This article illustrates the simple concept of adding data filtering to your applications.  We started with an application that showed an entire collection of Flights, and then modified the application so it can filter that data by price.  This is a simple example, however the underlying concepts apply to any application that wants to add data filtering.   If you want to take this further, try adding filters to the sample application for filtering on Departure and Destination City.   I hope you can take the concepts illustrated in this article and apply it to the projects you are working on.   Until next time, have fun coding!

Don’t forget to follow the Windows Store Developer Solutions team on Twitter @wsdevsol. Comments are welcome, both below and on twitter.

- Bret Bentzinger(Microsoft) @awehellyeah

FlightDataFilter.zip