Tag Archives: collection view

Data binding with a ListBox and ComboBox in WPF

In the last post, we saw how to use data binding to a List to allow stepping through the elements of the List, displaying each item in separate TextBoxes. We also saw how a collection view provides an interface to the underlying List, and allows moving around the List as well as other operations like sorting and filtering.

However, when dealing with a list of objects, usually we would like a view that allows us to see several items in the list at the same time, and allows us to scroll through this display and select which item we want to deal with. To that end, we’ll have a look at how to bind a List to a ListBox and a ComboBox in WPF.

The program we use as illustration is an expanded version of the one from the previous post. The interface of the new program looks like this:

As before, we are looking at a list of books. Each book has an author, title, price and subject category. The available categories are shown in the ComboBox, while the titles of the books are displayed in a ListBox just above the buttons. Each category is represented by a unique integer code (so ‘physics’ has the code 2 in the picture). It is this numerical code that is stored in each Book object, so the textual representation of a given category is contained in a separate class called BookCategory. (Yes, I realize this example would be better done using a database, but for the purposes of illustrating data binding to lists, it serves its purpose.)

The buttons have the same functions as in the previous post (the First, Previous, Next and Last buttons move through the list of books, Add adds a new book to the list, Sort sorts the books by price and Cheap selects books with a price under 20.00. Nothing new here.

We’ve used data binding to keep the various parts of the interface synchronized. As we step through the list using one of the navigation buttons, the displays in the TextBoxes and ComboBox update to display the data in the current book, and the highlighted line in the ListBox also keeps in step with out current selection. If we select a book by clicking on a line in the ListBox, the rest of the display shows the data for that book. If we select a new category in the ComboBox, the Code box will update as well, and vice versa. (We haven’t put in any validation, so if you enter an invalid Code number, you won’t get an error message.)

The interface was built in Expression Blend as usual, so if you want to see the XAML for that, download the code (link at the end of the post) and have a look. We’ll look here at the code (all in C#) that does the binding between the lists and controls.

First, we’ll look at the classes that store the data that will be inserted into the lists. The Book class is slightly expanded from the previous post in order to accommodate the category code:

  class Book : INotifyPropertyChanged
  {
    public event PropertyChangedEventHandler PropertyChanged;
    protected void Notify(string propName)
    {
      if (this.PropertyChanged != null)
      {
        PropertyChanged(this, new PropertyChangedEventArgs(propName));
      }
    }

    string author;

    public string Author
    {
      get { return author; }
      set
      {
        if (author.Equals(value)) { return; }
        author = value;
        Notify("Author");
      }
    }

    string title;

    public string Title
    {
      get { return title; }
      set { if (title.Equals(value)) { return; }
        title = value;
        Notify("Title");
      }
    }

    decimal price;

    public decimal Price
    {
      get { return price; }
      set { if (price == value) { return; }
        price = value;
        Notify("Price");
      }
    }

    int categoryCode;

    public int CategoryCode
    {
      get { return categoryCode; }
      set { if (categoryCode == value) { return; }
        categoryCode = value;
        Notify("CategoryCode");
      }
    }

    public Book() { }
    public Book(string author, string title, decimal price, int code)
    {
      this.author = author;
      this.title = title;
      this.price = price;
      this.categoryCode = code;
    }
  }

Note, by the way, that the string returned in the Notify() call must match the name of the property in the code.

Next, we look at the BookCategory class:

  class BookCategory : INotifyPropertyChanged
  {
    public event PropertyChangedEventHandler PropertyChanged;
    protected void Notify(string propName)
    {
      if (this.PropertyChanged != null)
      {
        PropertyChanged(this, new PropertyChangedEventArgs(propName));
      }
    }

    int code;

    public int Code
    {
      get { return code; }
      set
      {
        code = value;
        Notify("Code");
      }
    }

    string category;

    public string Category
    {
      get { return category; }
      set
      {
        category = value;
        Notify("Category");
      }
    }

    public BookCategory(int code, string category)
    {
      Code = code;
      Category = category;
    }
  }

Again, no surprises here; it has the same form as the Book class.

Now we look at the binding code, contained in MainWindow.xaml.cs. The constructor is

    public MainWindow()
    {
      this.InitializeComponent();
      InitializeLibrary();
      InitializeCategoryList();
      UpdateButtonStates();
    }

After the standard InitializeComponent() call, we initialize the list of Books (the library) that is displayed in the ListBox and then the list of categories that is displayed in the ComboBox.

Here’s InitializeLibrary():

    ObservableCollection<Book> library;
    private void InitializeLibrary()
    {
      library = new ObservableCollection<Book>();
      library.Add(new Book("Feynman", "Feynman Lectures on Physics", BookPrice(), 2));
      library.Add(new Book("Asimov", "I, Robot", BookPrice(), 1));
      library.Add(new Book("Christie", "Death on the Nile", BookPrice(), 3));
      library.Add(new Book("Taylor", "From Sarajevo to Potsdam", BookPrice(), 4));
      LayoutRoot.DataContext = library;

      Binding listBoxBinding = new Binding();
      BindingOperations.SetBinding(booksListBox, ListBox.ItemsSourceProperty, listBoxBinding);
      booksListBox.DisplayMemberPath = "Title";
      booksListBox.SelectedValuePath = "CategoryCode";
      booksListBox.IsSynchronizedWithCurrentItem = true;
    }

We’re just hard-coding the first four books in the list. In more advanced code, you’d probably read them from a file and/or offer the user a way of entering the data in the interface (or, of course, read them from a database). Here we create a List of Books and set it as the data context.

Next we create a Binding and attach it to the list box (booksListBox is the ListBox that displays the books). The ItemsSourceProperty is set to this binding. Note that we don’t specify a Path for the binding itself. This causes the ListBox to bind to the entire data set in the data context. (Well, actually, it’s binding to the collection view that has been automatically wrapped around the List, as we saw in the previous post.) If we stopped at this point and ran the program, the ListBox would display a list of entires, each of which would say “ListBinding1.Book”. This is because, in the absence of any further instructions on how to display each element in the List, the ListBox calls the ToString() method of each object in the List. If no ToString() has been provided explicitly (we haven’t here), the default ToString() just prints out the data type of the object, which in this case is the Book class in the ListBinding1 namespace. In order to get something more informative to show up in the ListBox, we need to tell it which part of the Book object to display. That’s what the DisplayMemberPath is for. We set this to the name of the property of the Book that we want to show up; in this case, the Title. Note that DisplayMemberPath is a property of the ListBox and not of the binding. This can be a bit confusing, but if you think about it, it does make sense. The binding is concerned only with which element to display; the mechanics of what to display are handled by the UI control, in this case the ListBox.

The SelectedValuePath is a bit more subtle. List controls such as ListBoxes allow one property of the current item to be used for display, while reserving another property as the actual value of the item. The current item selected in a ListBox is the SelectedItem, and the value of this item is the SelectedValue. The SelectedValuePath tells the ListBox which property of the SelectedItem should be used for the SelectedValue. Thus SelectedValuePath is the name of a property and is not a value itself; the SelectedValue is the actual value of that property for the SelectedItem.

Finally we tell the ListBox to keep synchronized with the CurrentItem as specified in the collection view. If you don’t do this, the SelectedItem in the ListBox won’t change as you step through the Books in the collection view by using the navigation buttons.

If you ran the program at this point, you should see the titles displayed in the ListBox, and the highlighted item keep step with the CurrentItem as you use the buttons to move around the list. The ComboBox, however, will still be inert since we haven’t linked it into the program yet.

Now we look at the category list and how to bind it to the ComboBox. We use an ObservableCollection for this list as well, although in the current program there is no provision for adding or deleting categories, so we could have used an ordinary List with the same result. The code is:

    ObservableCollection<BookCategory> CategoryList;
    private void InitializeCategoryList()
    {
      CategoryList = new ObservableCollection<BookCategory>();
      CategoryList.Add(new BookCategory(1, "Science Fiction"));
      CategoryList.Add(new BookCategory(2, "Physics"));
      CategoryList.Add(new BookCategory(3, "Mystery"));
      CategoryList.Add(new BookCategory(4, "History"));
      Binding comboBinding = new Binding();
      comboBinding.Source = CategoryList;
      BindingOperations.SetBinding(categoryComboBox, ComboBox.ItemsSourceProperty, comboBinding);
      categoryComboBox.DisplayMemberPath = "Category";
      categoryComboBox.SelectedValuePath = "Code";

      Binding codeComboBinding = new Binding();
      codeComboBinding.Path = new PropertyPath("CategoryCode");
      BindingOperations.SetBinding(categoryComboBox, ComboBox.SelectedValueProperty, codeComboBinding);
    }

As with the book list, we are hard-coding a few categories for illustration; in real life you’d read these from a file or a database.

We begin by binding CategoryList to the combo box. Since CategoryList isn’t the data context, we need to specify it as the Source of the binding. As with the book list, we get the ComboBox to display the textual form of the category while using the numerical Code as its SelectedValue.

The semi-magical bit comes in the last 3 lines of code. Here we introduce a second binding for the ComboBox. This is a binding for the ComboBox’s SelectedValue, and we bind it to the CategoryCode property in the library (the list of Books). Note that we don’t specify a Source for this binding, so it will use the data context, which is the same library as the ListBox uses. By binding the ComboBox’s SelectedValue to the CategoryCode in the CurrentItem in the library, we synchronize the category displayed by the ComboBox to the category in the CurrentItem (the current Book being displayed). This means that if we change the category by typing a new number into the Code TextBox, then because that TextBox is bound to the CategoryCode in the Book, it will update that property. This in turn will cause the ComboBox’s SelectedValue to change. Then, due to the first binding between the CategoryList and the ComboBox, the new SelectedValue will be used to look up the corresponding DisplayMember, and the display of the ComboBox will be updated. The process also works in reverse: if you select a new category from the ComboBox, the same chain of events (in reverse) will cause the CategoryCode in the Book to change, which in turn will cause the Code TextBox to update.

There are only a couple of other minor changes that were made in this code compared to that in the previous post. The UpdateButtonStates() method has an extra line to ensure that the currently selected item in the ListBox is always visible, using the ScrollIntoView() method in ListBox:

    private void UpdateButtonStates()
    {
      CollectionView libraryView = GetLibraryView();
      previousButton.IsEnabled = libraryView.CurrentPosition > 0;
      nextButton.IsEnabled = libraryView.CurrentPosition < libraryView.Count - 1;
      booksListBox.ScrollIntoView(booksListBox.SelectedItem);
    }

Finally, the event handler for the Add button is modified to provide a category for a new book:

    private void addButton_Click(object sender, RoutedEventArgs e)
    {
      int newBookNum = library.Count + 1;
      library.Add(new Book("Author " + newBookNum, "Title " + newBookNum,
        BookPrice(), rand.Next(1, CategoryList.Count)));
      CollectionView libraryView = GetLibraryView();
      libraryView.MoveCurrentToLast();
      UpdateButtonStates();
    }

We generate the category randomly from those available in CategoryList.

Note that we didn’t need to change any of the logic for things like moving around the list, adding to the list, sorting or filtering. All of that is handled by the collection view, and the binding between the ListBox and ComboBox and the collection view works in exactly the same way as between the more cumbersome TextBox representation we used in the previous post.

Code available here.

Advertisements

Collection views in WPF

We’ve seen (for simple binding and also using converters to convert from one data type to another) how to use data binding to keep a dependency property of a control synchronized with the value of a field in an object. These examples, however, worked only when the data to which we were binding consisted of a single object. In real life, we often wish to bind to a list of data objects, so we’ll start having a look at that here.

We’ll use as an example the case where our data consists of a list of books. The interface displays the properties of a single book at a time, but we wish to be able to step through the list and display the properties of each book in order. As a bonus, we would also like to sort the books, and apply a filter to the list to pick out the cheapest books. The interface we’ll be using looks like this:

The buttons have the following effects:

  • First: go to the first book in the list.
  • Previous: go to the book immediately preceding the current book.
  • Next: go to the book immediately following the current book.
  • Last: go to the last book in the list.
  • Add: add a new book to the list.
  • Sort: sort the books in ascending order by price.
  • Cheap: display only those books with a price under 20.00.

The interface was constructed in Expression Blend and uses standard techniques. We won’t go into the interface here, but if you want a look, download the code (link at end of the post) and look at the MainWindow.xaml file.

Before we get into the code, we need to explain some of the theory behind data binding with lists. As you can see from the interface, the controls (TextBoxes) bind to the properties of only one book at a time, so somehow we need a way of extracting this information from the overall list. Also, we need a way of keeping track of where we are in the list so that the Previous and Next buttons work properly.

If all we were concerned with was creating a data structure which stores a list of books, we would create a Book class containing author, title and price fields, and then create a list of Books by using WPF’s built-in generic List type, as in List<Book>. We might then think that we’d be faced with having to write a lot of code to keep track of a position in the list (as well as do the sorting and filtering that we want in this case). However, WPF provides a magical interface called ICollectionView the implementation of which does all this for us. So what exactly is a collection view?

A collection view, as its name implies, provides a customizable view of a collection, such as a list. The job of the collection view is to allow us to move around the list, as well as performing other tasks such as sorting and filtering. As it’s a view, it provides a read-only interface to the list, so it’s not possible to change anything in the original list by means of the collection view.

A collection view is automatically created for us when we bind to a List object. That is, the act of defining a List object as a data context will create a collection view, and it is in fact this view that we bind to rather than the original List object itself. Once the view has been created, we can manipulate our position in the list, as well as sort and filter the list, by using methods of the view, rather than methods of the original list. At all times, it is the CurrentItem of the view that is used as the single data item within the list to which controls are bound. Thus changing the CurrentItem (for example, by stepping through the list) changes which item is bound to a control, with the result that the values displayed by the control change as we change the CurrentItem.

Let’s see how all this works in practice. First, we define the Book class, which must implement INotifyPropertyChanged since ultimately it is a Book object to which the controls bind. At this level, data binding works just the same way as in our earlier examples where we considered binding to a single object. The Book class should thus look familiar if you’ve read the earlier posts:

  class Book : INotifyPropertyChanged
  {
    public event PropertyChangedEventHandler PropertyChanged;
    protected void Notify(string propName)
    {
      if (this.PropertyChanged != null)
      {
        PropertyChanged(this, new PropertyChangedEventArgs(propName));
      }
    }

    string author;

    public string Author
    {
      get { return author; }
      set
      {
        if (author.Equals(value)) { return; }
        author = value;
        Notify("Author");
      }
    }

    string title;

    public string Title
    {
      get { return title; }
      set { if (title.Equals(value)) { return; }
        title = value;
        Notify("Title");
      }
    }

    decimal price;

    public decimal Price
    {
      get { return price; }
      set { if (price == value) { return; }
        price = value;
        Notify("Price");
      }
    }

    public Book() { }
    public Book(string author, string title, decimal price)
    {
      this.author = author;
      this.title = title;
      this.price = price;
    }
  }

Note that we’re using C#’s decimal data type to store the price, since we don’t want any round-off errors.

Next, we’ll look at creating a list of Books in which we can store a number of Books. However, rather than use the List generic type, we use an ObservableCollection instead. The reason for this is that this class implements the INotifyCollectionChanged interface, which means that any changes to the list (such as adding or deleting items) notify any controls bound to the list so they can update themselves. Apart from that difference, we can treat an ObservableCollection pretty much like a List. The relevant code from the MainWindow class is:

    public MainWindow()
    {
      this.InitializeComponent();
      InitializeLibrary(4);
      LayoutRoot.DataContext = library;
      UpdateButtonStates();
    }

    ObservableCollection<Book> library;
    private void InitializeLibrary(int numBooks)
    {
      library = new ObservableCollection<Book>();
      for (int i = 1; i <= numBooks; i++)
      {
        library.Add(new Book("Author " + i, "Title " + i, BookPrice()));
      }
    }

    Random rand = new Random();
    private decimal BookPrice()
    {
      decimal price = rand.Next(0, 5000) / 100m;
      return price;
    }

If you look in MainWindow.xaml, you’ll see that each of the three TextBoxes has its Text property bound to one of the fields in Book (that is, the Author TextBox is bound to the “Author” property, and so on).

We initialize the list of books by calling InitializeLibrary(). This merely generates a number of books with an author of “Author <number>” and a title of “Title <number>”; the price is a random value between 0.01 and 50.00.

On line 5, we define library as the data context of LayoutRoot (which is the Grid layout on which the interface is built). Since library is an ObservableCollection<Book>, this automatically creates a collection view which is wrapped around the list, and which will be used to determine which Book from the library is used as the current item to which the controls are bound. For example, if the current item is set to the first item in the list, then it is the first Book (the one with Author 1, etc) that is used in the binding to the three TextBoxes.

If we ran the code at this point (without even defining click event handlers for the buttons), the TextBoxes would display the properties from the first Book in library. In order to add functionality to the buttons and allow us to step through the list of Books in the library, we need to acquire and use the collection view.

First, how do we get hold of the collection view in the first place? There is a static method which allows this to be done:

    private CollectionView GetLibraryView()
    {
      return (CollectionView)CollectionViewSource.GetDefaultView(library);
    }

We need to provide an explicit cast to CollectionView, since the GetDefaultView() method is defined only as returning something that implements the ICollectionView interface. The CollectionView class is a WPF class that implements this interface and is in fact what GetDefaultView() returns. CollectionView also provides a few properties that aren’t defined in the ICollectionView interface, so it’s useful to deal with the class explicitly rather than just with the interface.

Note that GetDefaultView() does not create the collection view; that is done when the library is assigned as the data context. GetDefaultView() merely retrieves the collection view object that already exists. Thus successive calls to GetDefaultView() don’t erase any of the changes to the view that other method calls might make.

The first method that uses some of the view properties is the UpdateButtonStates() method that we call from the constructor above:

    private void UpdateButtonStates()
    {
      CollectionView libraryView = GetLibraryView();
      previousButton.IsEnabled = libraryView.CurrentPosition > 0;
      nextButton.IsEnabled = libraryView.CurrentPosition < libraryView.Count - 1;
    }

The view maintains a CurrentPosition marker which points at the location of the CurrentItem. Note that this is not necessarily the same as the position of the item in the original list, since a view can sort or filter the list items and generate a new order for the original items. All of this is done without affecting the data or its order in the original list; the view maintains a separate catalogue of the elements of the list and their new order.

UpdateButtonStates() disables the Previous button if CurrentPosition is 0. It also disables the Next button if the CurrentPosition is at the end of the view of the list. (Note that the Count property is not provided by ICollectionView; it is something that is added in the CollectionView class, which is one reason why we deal with the class explicitly.)

We can now add event handlers for the four buttons that move around the list:

    private void firstButton_Click(object sender, RoutedEventArgs e)
    {
      CollectionView libraryView = GetLibraryView();
      libraryView.MoveCurrentToFirst();
      UpdateButtonStates();
    }

    private void previousButton_Click(object sender, RoutedEventArgs e)
    {
      CollectionView libraryView = GetLibraryView();
      libraryView.MoveCurrentToPrevious();
      UpdateButtonStates();
    }

    private void nextButton_Click(object sender, RoutedEventArgs e)
    {
      CollectionView libraryView = GetLibraryView();
      libraryView.MoveCurrentToNext();
      UpdateButtonStates();
    }

    private void lastButton_Click(object sender, RoutedEventArgs e)
    {
      CollectionView libraryView = GetLibraryView();
      libraryView.MoveCurrentToLast();
      UpdateButtonStates();
    }

These should all be fairly obvious, since we’re using the methods from CollectionView to move around the list.

Next, we look at how we can sort the list. The event handler for the Sort button is:

    private void sortButton_Click(object sender, RoutedEventArgs e)
    {
      CollectionView libraryView = GetLibraryView();
      if (libraryView.SortDescriptions.Count != 0)
      {
        libraryView.SortDescriptions.Clear();
        sortButton.Content = "Sort";
      }
      else
      {
        libraryView.SortDescriptions.Add(new SortDescription("Price", ListSortDirection.Ascending));
        sortButton.Content = "Clear";
      }
      libraryView.MoveCurrentToFirst();
      UpdateButtonStates();
    }

A collection view maintains a list of SortDescriptions, which are applied in order. In this case, the Sort button is a toggle between a sorted and unsorted list. If there are SortDescriptions present (which would result in the list being sorted), we clear any existing SortDescriptions, which restores the list to its original order. If there are no SortDescriptions, then we add one which sorts the books by their price, in ascending order. In each case, we change the button’s caption to show what pressing it again will do.

Note that we don’t need to call a Sort() method to actually do the sorting; the simple act of adding a SortDescription to the view causes sorting to take place. If we had added a second SortDescription, then any books with the same price would be sorted according to the second SortDescription, and so on for further SortDescriptions. Remember that sorting affects only the order of the Books in the view; it does not change the order of the data in the original list.

Filtering is done by applying a Predicate (a condition which must be either true or false) to the list of Books, and retaining only those Books for which the Predicate is true. Again, note that filtering affects only the view of the list, and does not delete anything from the original list. Thus we could apply one filter that selects books with a price under 20.00, and then apply another filter for books over 20.00. Each filter would alter the view of the list, but not the list itself. The code for the filter is:

    private void filterButton_Click(object sender, RoutedEventArgs e)
    {
      CollectionView libraryView = GetLibraryView();
      if (libraryView.Filter != null)
      {
        libraryView.Filter = null;
        filterButton.Content = "Cheap";
      }
      else
      {
        libraryView.Filter = new Predicate<object>(book => ((Book)book).Price < 20);
        filterButton.Content = "All";
      }
      libraryView.MoveCurrentToFirst();
      UpdateButtonStates();
    }

Again, we are treating the filter button as a toggle between a filtered and unfiltered list. We’ve used the Predicate generic type defined in recent versions of C# (see here). The argument to the Predicate constructor tests if the Book’s price is less than 20. Again, all we need do is define the filter; we don’t need to call any explicit Filter() method.

Finally, we look at adding an element to the original list. Since a collection view doesn’t allow us to change the original list, we need to do this by dealing with the ObservableCollection<Book> object directly. Once we’ve added the new Book, we can then re-acquire the collection view for the new list. Because we defined the library as an ObservableCollection<Book>, any additions we make will be automatically notified to controls bound to the library, so the displays will be updated without the need for any further code.

    private void addButton_Click(object sender, RoutedEventArgs e)
    {
      int newBookNum = library.Count + 1;
      library.Add(new Book("Author " + newBookNum, "Title " + newBookNum, BookPrice()));
      CollectionView libraryView = GetLibraryView();
      libraryView.MoveCurrentToLast();
      UpdateButtonStates();
    }

This little program should give you a fair idea of what the collection view can do. There are a few other features as well, so have a stroll through the documentation to see what you can do. One thing this post doesn’t address, however, is how to display more than one element from a list at the same time, so we’ll get to that later.

Code available here.