Gigi Labs

Please follow Gigi Labs for the latest articles.
Showing posts with label mvvm light. Show all posts
Showing posts with label mvvm light. Show all posts

Thursday, October 17, 2013

C# WPF: Master/Detail Layout using MVVM

Welcome! :)

My recent article "C# WPF: Control Panel using MVVM" showed how you can make a specific master/detail layout suited for control panels - you select an option from the sidebar, and the interface on the right changes based on it. As I mentioned in the article, this approach is okay when the options are few and predefined. If you're working with displaying actual data, though, you need to work a little differently.

This is another advanced article dealing with MVVM, so prerequisites to understand the content include knowing C#, WPF, data binding, and a basic idea of MVVM. You will also need to download and install MVVM Light - a popular, lightweight MVVM library which we also used in yesterday's article to get some common MVVM functionality out of the box.

Let's start off by creating a new WPF application in SharpDevelop (or Visual Studio, if you prefer). Once that is done, right click on the project and select "Add Reference". From the ".NET Assembly Browser" tab, use the "Browse..." button to locate the "Mvvm Light Toolkit\Binaries\WPF4GalaSoft.MvvmLight.WPF4.dll" file from your MVVM Light installation. These instructions pertain to SharpDevelop using .NET 4, so if you're on Visual Studio or using a different version of the .NET Framework, adapt accordingly.

Next, let's set up a little master/detail layout to our Window that we will later populate. Replace the default <Grid> section with the following:

     <DockPanel>
        <ListBox DockPanel.Dock="Left" Width="100" />
        <Grid Height="100">
            <Grid.RowDefinitions>
                <RowDefinition />
                <RowDefinition />
                <RowDefinition />
                <RowDefinition />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="80" />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <TextBlock Text="Name:" Grid.Row="0" Grid.Column="0" />
            <TextBlock Text="Surname:" Grid.Row="1" Grid.Column="0" />
            <TextBlock Text="Age:" Grid.Row="2" Grid.Column="0" />
            <TextBlock Text="Location:" Grid.Row="3" Grid.Column="0" />
            <TextBox Grid.Row="0" Grid.Column="1" />
            <TextBox Grid.Row="1" Grid.Column="1" />
            <TextBox Grid.Row="2" Grid.Column="1" />
            <TextBox Grid.Row="3" Grid.Column="1" />
        </Grid>
    </DockPanel>

This gives us the following layout:


Not the prettiest thing, but that's beside the point. On the left, we have a ListBox where we'll display a list of names. On the right, we'll display the details for the selected person.

Next, let's create our ViewModel. Right click the project, Add -> New Item... and add a new class called MainVM.cs. In your Window's codebehind (Window1.xaml.cs in SharpDevelop, or MainWindow.xaml.cs in Visual Studio), set up this ViewModel as the DataContext by adding the following line at the end of its constructor:

             this.DataContext = new MainVM();

Add a new class called Person.cs. Set it up as follows:

     public class Person
    {
        public String Name { get; set; }
        public String Surname { get; set; }
        public int Age { get; set; }
        public String Location { get; set; }

        public String NameAndSurname
        {
            get
            {
                return String.Format("{0} {1}", this.Name, this.Surname);
            }
        }
      
        public Person(String name, String surname, int age, String location)
        {
            this.Name = name;
            this.Surname = surname;
            this.Age = age;
            this.Location = location;
        }
    }

This Person class represents the information we'll be displaying in our application. We can now go back to our ViewModel and begin to set up the properties that will actually be bound in the view (Window).

At the top of MainVM.cs, add the following:

using System.Collections.Generic;
using GalaSoft.MvvmLight;

Make MainVM inherit from MVVM Light's ViewModelBase:

    public class MainVM : ViewModelBase

We can now add member variables to represent our list of people and also the one that is currently selected:

        private List<Person> persons;
        private Person selectedPerson;

Add in the corresponding properties, and don't forget the RaisePropertyChanged() to let the data binding update the user interface when the properties change. RaisePropertyChanged() is something we get from MVVM Light by inheriting from ViewModelBase - see yesterday's article if you're feeling lost.

        public List<Person> Persons
        {
            get
            {
                return this.persons;
            }
            set
            {
                this.persons = value;
                RaisePropertyChanged("Persons");
            }
        }
      
        public Person SelectedPerson
        {
            get
            {
                return this.selectedPerson;
            }
            set
            {
                this.selectedPerson = value;
                RaisePropertyChanged("SelectedPerson");
            }
        }

In MainVM's constructor, let's add some data we can work with:

            this.persons = new List<Person>()
            {
                new Person("Chuck", "Norris", 21, "The Boogeyman's closet"),
                new Person("Monica", "Lewinski", 70, "Take a guess..."),
                new Person("Steve", "Ballmer", 55, "Crying somewhere"),
                new Person("Captain", "Jack", 30, "The Black Pearl")
            };

In the Window's XAML view, we can now add data binding to the ListBox as follows:

         <ListBox DockPanel.Dock="Left" Width="100" ItemsSource="{Binding Path=Persons}"
                 DisplayMemberPath="NameAndSurname" SelectedItem="{Binding Path=SelectedPerson}" />

So our ListBox is now getting its items from the Persons property we have just defined, and for each item (of type Person it displays the NameAndSurname property. We also bind the ListBox's SelectedItem property to the SelectedPerson property we just defined. We'll use that in a minute, but first, let's hit F5 to see that we're all good so far:


See, the names now appear in the ListBox on the left. We can now bind the fields on the right to properties of the SelectedPerson:

            <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Path=SelectedPerson.Name}" />
            <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Path=SelectedPerson.Surname}" />
            <TextBox Grid.Row="2" Grid.Column="1" Text="{Binding Path=SelectedPerson.Age}" />
            <TextBox Grid.Row="3" Grid.Column="1" Text="{Binding Path=SelectedPerson.Location}" />

Once you run the application and select one of the names on the left, you'll see the details appear on the right (I resized the window a little so that things fit a little better):


Now, this approach might work if you can afford to load all your data in memory - i.e. you don't have a lot of items, and they don't contain a lot of data. When you're dealing with larger data sets, it is probably best to take the following approach instead:

  1. Bind the ListBox to a list of objects that contain minimal information necessary for the master view to work - in this case, the name, surname, and an ID corresponding to a primary key for that record in the database.
  2. Add a SelectedPersonDetails property, in which you keep full details for the selected record.
  3. In the SelectedPerson's set modifier, implement code to fill SelectedPersonDetails with details from the database based on the selected ID.
  4. Bind the detail fields to properties of SelectedPersonDetails.

In this article, we have seen a simple way of implementing a master/detail view using the MVVM design pattern. We did this by maintaining properties representing the master view's items and selected items, and binding to them. Fields in the detail view could then be bound to properties of the selected item. In case a lot of data is involved, it is recommended to populate the list with basic data and then retrieve details lazily (on demand) as needed.

Thanks for reading; come back again! :)

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(this, new 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! :)