Gigi Labs

Please follow Gigi Labs for the latest articles.

Wednesday, October 16, 2013

C# WPF: Filter ListBox As You Type, Using MVVM

Hellooooo! :)

In this article, I'm going to show how you can set things up so that typing in a WPF TextBox instantly filters the items in a ListBox, within an MVVM setting.

This is an advanced article, and to understand it, you'll need to know your C#, WPF and data binding as well as a little touch of MVVM and LINQ.

So, let's get to it. Create a new WPF Application in SharpDeveloper or whichever IDE you prefer.

In your Window's XAML view, replace the default Grid element with the following:

     <DockPanel>
        <TextBox DockPanel.Dock="Top" />
        <ListBox />
    </DockPanel>

Simple enough? The TextBox is where you type your text, and the ListBox will show a list of items.

Let us now create a ViewModel for our Window. Create a new class (right click on project, Add -> New Item...) and name it MainVM. At the beginning of the MainVM class, add the following member variables:

        private List<String> names;
        private String filter;

Right after that, add the following properties to expose them:

        public List<String> FilteredNames
        {
            get
            {
                return (from name in names where name.Contains(filter) select name).ToList<String>();
            }
        }

        public String Filter
        {
            get
            {
                return this.filter;
            }
            set
            {
                this.filter = value;
            }
        }

Notice how we aren't exposing our names variable as it is; we are returning only those names that contain the filter text (which will be set via the TextBox later). You might prefer to use StartsWith() instead of Contains() - that's up to you.

If you're using SharpDevelop, don't forget to add the following at the top for the Lists and the LINQ:

using System.Collections.Generic;
using System.Linq;

In the MainVM() constructor, let's initialise our variables:

            this.names = new List<string>() { "Jerry""Joey""Roger""Raymond""Jessica""Mario""Jonathan" };
            this.filter = "";

In your Window's codebehind (Window1.xaml.cs if you're in SharpDevelop, or MainWindow.xaml.cs if you're using Visual Studio), set up the viewmodel as the datacontext for the window by adding this line at the end of the constructor:

             this.DataContext = new MainVM();

Back in the window's XAML, we can now change our controls and add bindings in order to get our list to show:

        <TextBox DockPanel.Dock="Top" Text="{Binding Path=Filter}" />
        <ListBox ItemsSource="{Binding Path=FilteredNames, Mode=OneWay}" />

Here's the result:


Great, the list is showing. But as you can see, the list isn't being filtered by the text in the TextBox. To get this working, there are two things we need to do. First, we need to update the TextBox's binding to use an UpdateSourceTrigger as follows:

<TextBox DockPanel.Dock="Top" Text="{Binding Path=Filter, UpdateSourceTrigger=PropertyChanged}" />

This causes the underlying property to be updated with every keypress, rather than when the TextBox loses focus. This will give the idea that the list is being updated in real-time.

The second thing we need to do is actually let the WPF binding know that FilteredNames needs to be updated when the Filter property is set. WPF allows us to do this by implementing INotifyPropertyChanged. We can actually implement INotifyPropertyChanged ourselves, or use the functionality by means of an MVVM library. Let's see how both these methods work.

Method 1: Implement INotifyPropertyChanged

Add the following code at the end of your MainVM class:

        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(String propertyName)
        {
            PropertyChanged(thisnew PropertyChangedEventArgs(propertyName));
        }

Then make your MainVM class implement INotifyPropertyChanged:

     public class MainVM : INotifyPropertyChanged

Change your Filter property's set accessor as follows to let the binding know that it has to refresh the FilteredNames:

            set
            {
                this.filter = value;
                NotifyPropertyChanged("FilteredNames");
            }

Finally, add the following at the top so that the compiler knows what an INotifyPropertyChanged is:

using System.ComponentModel;

Hit F5 and try it out to see that it works:

Method 2: Use MVVM Light

There's this library called MVVM Light written by a dude called Laurent Bugnion that packages some stuff we normally need for MVVM. It's not comfortable to have to keep an event in all our viewmodels, so instead we can inherit from a common base class.

First, download and install MVVM Light to some local folder. Then, locate Mvvm Light Toolkit\Binaries\WPF4\GalaSoft.MvvmLight.WPF4.dll and add a reference to it (right click project, Add Reference, then select the .NET Assembly Browser tab and locate the .dll via the Browse... button.

At the top of your MainVM.cs file, put the following to allow you to make use of this library:

using GalaSoft.MvvmLight;

Next, let the MainVM class inherit from ViewModelBase, a common base class for viewmodels available in MVVM Light:

     public class MainVM : ViewModelBase

Now, you only need to update your Filter property's set accessor as follows in order to make things work:

            set
            {
                this.filter = value;
                RaisePropertyChanged("FilteredNames");
            }

RaisePropertyChanged() is a method inherited from ViewModelBase, and does pretty much the same thing as NotifyPropertyChanged() from Method 1 above.

Press F5 to give it a shot:


Summary

In this article we saw how easy it is to filter a ListBox as you type into a TextBox. We exploited data binding in an MVVM manner, and used INotifyPropertyChanged (either directly or through MVVM Light) to reflect changes in the ListBox.

This is not necessarily the most efficient way of doing this, but it's simple to implement and is pretty fast even if you have several hundred items in your ListBox.

I hope you found this useful, and check back for more articles! :)

2 comments:

Note: Only a member of this blog may post a comment.