Windows Presentation Foundation

Hello, WPF

WPF from Scratch
Navigation Applications
Content Model
Layout
Controls
Data Binding
Dependency Properties
Resources
Styles and Control Templates
Graphics
Application Deployment
Where Are We?

Layout

Introduction
Layout Basics
DockPanel
StackPanel
Grid
Canvas
Viewbox
Text Layout
Common Layout Properties
When Content Doesn't Fit
Custom Layout
Where Are We?

Controls

Introduction
What Are Controls?
Handling Input
Built-In Controls
Where Are We?

Data Binding

Introduction
Without Data Binding
Data Binding
Binding to List Data
Data Sources
Master-Detail Binding
Where Are We?

Styles and Control Templates

Introduction
Without Styles
Inline Styles
Named Styles
Element-Typed Styles
Data Templates and Styles
Triggers
Control Templates
Where Are We?

Resources

Introduction
Creating and Using Resources
Resources and Styles
Binary Resources
Global Applications
Where Are We?

Graphics

Introduction
Graphics Fundamentals
Shapes
Brushes and Pens
Transformations
Visual-Layer Programming
Video and 3-D
Where Are We?

Animation

Animation Fundamentals
Timelines
Storyboards
Key Frame Animations
Creating Animations Procedurally
Where Are We?

Custom Controls

Introduction
Custom Control Basics
Choosing a Base Class
Custom Functionality
Templates
Default Visuals
Where Are We?

ClickOnce Deployment

A Brief History of Windows Deployment
ClickOnce: Local Install
The Pieces of ClickOnce
Publish Properties
Deploying Updates
ClickOnce: Express Applications
Choosing Local Install versus Express
Signing ClickOnce Applications
Programming for ClickOnce
Security Considerations
Where Are We?

Binding to List Data

4.3. Binding to List Data

So far, you've seen several examples of binding a control to a single object. However, a more traditional use of binding is to a list of objects. For example, imagine a new type that our object data source can create that presents a list of Person objects, as in Example 4-19.

Example 4-19. Declaring a custom list type
using System.Collections.Generic; // List<T>
...
namespace PersonBinding {
  // XAML doesn't (yet) have a syntax
  // for generic class instantiation
  class People : List<Person> {}

}

We can hook this new list data source and bind to it in exactly the same way as if we were binding to a single object data source, as in Example 4-20.

Example 4-20. Declaring a collection in XAML
<!-- Window1.xaml -->
<?Mapping XmlNamespace="local" ClrNamespace="PersonBinding" ?>
<Window ... xmlns:local="local">

  <Window.Resources>
    <local:People x:Key="Family">
      <local:Person Name="Tom" Age="9" />
      <local:Person Name="John" Age="11" />
      <local:Person Name="Melissa" Age="36" />
    </local:People>

    <local:AgeToForegroundConverter
      x:Key="AgeToForegroundConverter" />
  </Window.Resources>
  <Grid DataContext="{StaticResource Family}">
    ...
    <TextBlock ...>Name:</TextBlock>

    <TextBox Text="{Binding Path=Name}" ... />
    <TextBox
      Text="{Binding Path=Age}"
      Foreground="{Binding Path=Age, Converter=...}" ... />
    <Button ...>Birthday</Button>
  </Grid>

</Window>

In Example 4-20, we've created an instance of the People collection and populated it with three Person objects. However, running it will still look just like Figure 4-6.

4.3.1. Current Item

While the text box properties can only be bound to a single object at a time, the binding engine is giving them the current item in the list of possible objects they could bind against, as illustrated in Figure 4-6.

By default, the first item in the list starts as the current item. Since the first item in our list example is the same as the only item to which we were binding before, things look and act in exactly the same way as shown in Figure 4-11, except for the Birthday button.

Figure 4-11. Binding to a list data source


4.3.1.1. Getting the current item

Recall the current Birthday button click event handler (Example 4-21).

Example 4-21. Finding a custom object declared in XAML
public partial class Window1 : Window {
  ...
  void birthdayButton_Click(object sender, RoutedEventArgs e) {
    Person person = (Person)this.FindResource("Tom"));
    ++person.Age;
    MessageBox.Show(...);
  }
}

Our Birthday button has always been about celebrating the birthday of the current person, but so far the current person has always been the same, so we could just shortcut things and go directly to the single Person object. Now that we've got a list of objects, this no longer works (unless you consider a message box containing the word "InvalidCastException" acceptable behavior). Further, casting to People, our collection class, won't tell us which Person is currently being shown in the UI, because it has no idea about such things (nor should it). For this information, we're going to have to go to the broker between the data-bound control and the collection of items, the view.

The job of the view is to provide services on top of the data, including sorting, filtering and, most importantly for our purposes at the moment, control of the current item. A view is an implementation of a data-specific interface, which, in our case, is going to be the ICollectionView interface. We can access a view over our data with the static GetdefaultView method of the BindingOperations class, as in Example 4-22.

Example 4-22. Getting a collection's view

public partial class Window1 : Window {
  ...
  void birthdayButton_Click(object sender, RoutedEventArgs e) {
    People people = (People)this.FindResource("Family");
    ICollectionView view =
      BindingOperations.GetDefaultView(people);
    Person person = (Person)view.CurrentItem;

    ++person.Age;
    MessageBox.Show(...);
  }
}

To retrieve the view associated with the Family collection, Example 4-22 makes a call to the GetdefaultView method of BindingOperations, which provides us with an implementation of the ICollectionView interface. With it, we can grab the current item, cast it into an item from our collection (the CurrentItem property returns an object), and use it for display.

4.3.1.2. Navigating between items

In addition to getting the current item, we can also change which item is current using the MoveCurrentTo methods of the ICollectionView interface, as in Example 4-23.

Example 4-23. Navigating between items via the view
public partial class Window1 : Window {
  ...
  ICollectionView GetFamilyView(  ) {
    People people = (People)this.FindResource("Family");
    return BindingOperations.GetDefaultView(people);
  }

  void birthdayButton_Click(object sender, RoutedEventArgs e) {
    ICollectionView view = GetFamilyView(  );
    Person person = (Person)view.CurrentItem;

    ++person.Age;
    MessageBox.Show(...);
  }

  void backButton_Click(object sender, RoutedEventArgs e) {
    ICollectionView view = GetFamilyView(  );
    view.MoveCurrentToPrevious(  );
    if( view.IsCurrentBeforeFirst ) {
      view.MoveCurrentToFirst(  );
    }
  }

  void forwardButton_Click(object sender, RoutedEventArgs e) {
    ICollectionView view = GetFamilyView(  );
    view.MoveCurrentToNext(  );
    if( view.IsCurrentAfterLast ) {
      view.MoveCurrentToLast(  );
    }
  }
}

The ICollectionView methods MoveCurrentToPrevious and MoveCurrentToNext change which item is currently selected by going backward and forward through the collection. If we walk off the end of the list in one direction or the other, the IsCurrentBeforeFirst or IsCurrentAfterLast properties will tell us that. The MoveCurrentToFirst and MoveCurrentToLast help us recover after walking off the end of the list and would be useful for implementing the Back and Forward buttons shown in Figure 4-12, as well as First and Last buttons (which would be an opportunity for you to apply what you've learned...).

Figure 4-12 shows the effect of moving forward from the first Person in the collection, including the color changes based on the Person object's Age property (which still works in exactly the same way).

Figure 4-12. Navigating between items in a list data source


4.3.2. List Data Targets

Of course, there's only so far we can push the user of list data without providing them a control that can actually show more than one item at a time, such as the ListBox control in Example 4-24.

Example 4-24. Binding a list element to a list data source
<!-- Window1.xaml -->

<?Mapping XmlNamespace="local" ClrNamespace="PersonBinding" ?>
<Window ... xmlns:local="local">
  <Window.Resources>
    <local:People x:Key="Family">...</local:People>
    <local:AgeToForegroundConverter
      x:Key="AgeToForegroundConverter" />

  </Window.Resources>
  <Grid DataContext="{StaticResource Family}">
    ...
    <ListBox
      ItemsSource="{Binding}"
      IsSynchronizedWithCurrentItem="True" ... />
    <TextBlock ...>Name:</TextBlock>

    <TextBox Text="{Binding Path=Name}" ... />
    ...
</Window>

In Example 4-24, the ItemsSource property of the ListBox is a Binding with no path, which is the same as saying "bind to the entire current object." Notice that there's no source, either, so the binding works against the first non-null data context it finds. In this case, the first non-null data context is the one from the Grid, the same one as shared between both name and age text boxes. Also, we are setting the IsSynchronizedWithCurrentItem property to true so that as the selected item of the listbox changes, it updates the current item in the view and vice versa.

With our item's source binding in place, we should expect to see all three Person objects in the listbox, as shown in Figure 4-13.

Figure 4-13. Person objects being displayed in a ListBox without help


As you might have noticed, everything is not quite perfect in Figure 4-13. What's happening is that when you bind against a whole object, data binding is doing its best to display each Person object. Without special instructions, it'll use a type converter to get a string representation. For name and age, which are of built-in types with built-in conversions, this works just fine, but it doesn't work very well at all for a custom type without a visual rendering, as is the case with our Person type.

4.3.3. Data Templates

The right way to solve this problem is with a data template. A data template is a tree of elements to expand in a particular context. For example, for each Person object, we'd really like to be able to concatenate the name and age together in a string like the following:

Tom (age:9)


We can think of this as a logical template that looks like this:

Name (age:Age)

To define this template for items in the listbox, we create a DataTemplate element, as in Example 4-25.

Example 4-25. Using a data template
<ListBox ... ItemsSource="{Binding}">
  <ListBox.ItemTemplate>
    <DataTemplate>
      <StackPanel Orientation="Horizontal">

        <TextBlock TextContent="{Binding Path=Name}" />
        <TextBlock TextContent=" (age: " />
        <TextBlock
          TextContent="{Binding Path=Age}"
          Foreground="

            {Binding
              Path=Age,
              Converter={StaticResource AgeToForegroundConverter}}" />
        <TextBlock TextContent=")" />
      </StackPanel>
    </DataTemplate>

  </ListBox.ItemTemplate>
</ListBox>

In this case, the ListBox control has an ItemTemplate property, which accepts an instance of a DataTemplate object. The DataTemplate allows us to specify a single child element to repeat for every item that the ListBox control binds against. In our case, we're using a StackPanel to gather together four TextBlock controls in a row, two for text bound to properties on each Person object and two for the constant text. Notice that we're also binding the Foreground to the Age property using the AgeToForegroundConverter so that Age properties show up in black or red, so that the listbox is consistent with the age text box.

With the use of the data template, our experience goes from Figure 4-13 to Figure 4-14.

Figure 4-14. Person objects being displayed in a ListBox with a data template


Notice that the listbox shows all of the items in the collection and keeps the view's idea of current item synchronized with it as the selection moves or the back and forward buttons are pressed (actually, you can't really "notice" this based on the screenshot in Figure 4-14, but trust me, that's what happens). In addition, as data changes in Person objects, the listbox, and the text boxes are all kept in sync, including the Age color.

4.3.3.1. Typed data templates

In Example 4-25, we explicitly set the data template for items in our listbox. However, if a Person object showed up in a button or in some other element, we'd have to specify the data template for those Person objects separately. On the other hand, if you'd like a Person object to have a specific template no matter where it shows up, you can do so with a typed data template, as in Example 4-26.

Example 4-26. A typed data template
<Window.Resources>
  <local:AgeToForegroundConverter
    x:Key="AgeToForegroundConverter" />
  <local:People x:Key="Family">...</local:People>
  <DataTemplate DataType="{x:Type local:Person}">

    <StackPanel Orientation="Horizontal">
      <TextBlock TextContent="{Binding Path=Name}" />
      <TextBlock TextContent=" (age: " />
      <TextBlock TextContent="{Binding Path=Age}" ... />
      <TextBlock TextContent=")" />
    </StackPanel>

  </DataTemplate>
</Window.Resources>
...
<!-- no need for an ItemTemplate setting -->
<ListBox ItemsSource="{Binding}" ...>

In Example 4-26, we've hoisted the data template definition into a resources block and tagged it with a type using the DataType property. Now, unless told otherwise, whenever WPF sees an instance of the Person object, it will apply the appropriate data template. This is a handy way to make sure that data is displayed in a consistent way throughout your application without worrying about just where it shows.

4.3.4. List Changes

Thus far, we've got a list of objects that we can edit in place and navigate among, even highlighting certain data values with ease and providing an automatic look for data that wasn't shipped with a rendering from the manufacturer. In the spirit of how far we've come, you might suspect that providing an Add button would be a breeze, as in Example 4-27.

Example 4-27. Adding an item to a data bound collection
public partial class Window1 : Window {
  ...
  void addButton_Click(object sender, RoutedEventArgs e) {
    People people = (People)this.FindResource("Family");
    people.Add(new Person("Chris", 35));
  }
}


The problem with this implementation is that while the view can figure out the existence of new items on the fly as you move to it, the listbox itself has no idea that something new has been added to the collection, as shown in Figure 4-15.

Figure 4-15. The ListBox doesn't know the collection has gotten bigger


In interacting with the state of the application shown in Figure 4-15, I ran the application, pressed the Add button and used the Forward button to navigate to it. However, even though the new person is shown in the text boxes, the listbox still has no idea something was added. Likewise, it wouldn't have any idea if something was removed. Just like data-bound objects need to implement the INotifyPropertyChanged interface, data-bound lists need to implement the INotifyCollectionChanged interface, as in Example 4-28.

Example 4-28. The INotifyCollectionChanged interface
namespace System.Collections.Specialized {
  public interface INotifyCollectionChanged {
    event NotifyCollectionChangedEventHandler CollectionChanged;
  }
}

The INotifyCollectionChanged interface is used to notify the data-bound control that items have been added or removed from the bound list. While it's common to implement INotifyPropertyChanged in your custom types to enable two-way data binding on your type's properties, it's less common to implement your own collection classes, which leaves you less opportunity to implement the INotifyCollectionChanged interface. Instead, you'll most likely be relying on one of the collection classes in the .NET Framework Class Library to implement INotifyCollectionChanged for you. The number of such classes is small, and unfortunately, List<T>, the collection class we're using to hold Person objects, is not among them. While you're more than welcome to spend your evenings and weekends implementing INotifyCollectionChanged, WPF provides the ObservableCollection<T> class for those of us with more pressing duties, as in Example 4-29.

Example 4-29. WPF's implementation of INotifyCollectionChanged
namespace System.Windows.Data {
  public class ObservableCollection<T> :
    Collection<T>, INotifyCollectionChanged, INotifyPropertyChanged {
    ...
  }
}

Since ObservableCollection<T> derives from Collection<T> and implements the INotifyCollectionChanged interface, we can use it instead of List<T> for our Person collection, as in Example 4-30.

Example 4-30. ObservableCollection<T> in action
namespace PersonBinding {
  class Person : INotifyPropertyChanged {...}
  class People : ObservableCollection<Person> {}
}

Now, when an item is added to or removed from the Person collection, those changes will be reflected in the list data-bound controls, as shown in Figure 4-16.

Figure 4-16. Keeping the ListBox in sync with INotifyCollectionChanged


4.3.5. Sorting

Once we have data targets showing more than one thing at a time properly, a young person's fancy turns to more, well, fancy things, such as sorting the view of the data or filtering it. Recall that the view always sits between the data-bound target and the data source. This means that it's possible to skip data that we don't want to show (this is called filtering, and it will be covered directly), and it's possible to change the order in which the data is shown, a.k.a. sorting. The simplest way to sort is by manipulating the Sort property of the view, as in Example 4-31.

Example 4-31. Sorting
public partial class Window1 : Window {
  ...
  ICollectionView GetFamilyView(  ) {
    People people = (People)this.FindResource("Family");
    return BindingOperations.GetDefaultView(people);
  }

  void sortButton_Click(object sender, RoutedEventArgs e) {
    ICollectionView view = GetFamilyView(  );
    if( view.Sort.Count == 0 ) {
      view.Sort.Add(
        new SortDescription("Name", ListSortDirection.Ascending));
      view.Sort.Add(
        new SortDescription("Age", ListSortDirection.Descending));
    }
    else {
      view.Sort.Clear(  );
    }
  }
}

Here we are toggling between sorted and unsorted views by checking the SortDescriptionCollection exposed by the ICollectionView Sort property. If there are no sort descriptions, we sort first by the Name property in ascending order, then by the Age property in Descending order. If there are sort descriptions, we clear them, restoring the order to whatever it was before we applied our sort. While the sort descriptions are in place, any new objects added to the collection will be inserted into their proper sort position, as Figure 4-17 shows.

A collection of SortDescription objects should cover most cases, but if you'd like a bit more control, you can provide the view with a custom sorting object by implementing the IComparer interface, as in Example 4-32.

Figure 4-17. Unsorted, sorted with original data, and adding to a sorted view


Example 4-32. Custom sorting
class PersonSorter : IComparer {
  public int Compare(object x, object y) {
    Person lhs = (Person)x;
    Person rhs = (Person)y;

    // Sort Name ascending and Age descending
    int nameCompare = lhs.Name.CompareTo(rhs.Name);
    if( nameCompare != 0 ) return nameCompare;
    return rhs.Age - lhs.Age;
 }
}

public partial class Window1 : Window {
  ...
  ICollectionView GetFamilyView(  ) {
    People people = (People)this.FindResource("Family");
    return BindingOperations.GetDefaultView(people);
  }

  void sortButton_Click(object sender, RoutedEventArgs e) {
    ListCollectionView view = (ListCollectionView)GetFamilyView(  );
    if( view.CustomSort == null ) {
      view.CustomSort = new PersonSorter(  );
    }
    else {
      view.CustomSort = null;
    }
  }
}

In the case of setting a custom sorter, we have to make an assumption about the implementation of ICollectionViewspecifically, that it is a ListCollectionView, which is what WPF wraps around an implementation of IList (which our ObserverableCollection provides) to provide view functionality. There are other implementations of ICollectionView that don't provide custom sorting, so you'll want to test this code before shipping it.[*]

[*] Hopefully you'll test the rest of your code before shipping it, too, but it never hurts to point these things out...

While I'm sure this will get better as we approach v1.0 of WPF, as of right now, the view implementations associated with specific data characteristicssuch as the matching of ListCollectionView to IList are undocumented (at least as far as I could tell). Also, it seems somewhat funny that CustomSort was part of the view implementation class and not part of the ICollectionView interface, so let's keep our fingers crossed that this will also change as Microsoft moves toward the release of WPF.


4.3.6. Filtering

Just because all of the objects are shown in an order that makes you happy doesn't mean that you want all of the objects to be shown. For those rogue objects that happen to be in the data but that don't belong in the view, we need to feed the view an implementation of the CollectionFilterCallback delegate[*] that takes a single object parameter and returns a Boolean indicating whether the object should be shown or not, as in Example 4-33.

[*] Sorting uses a single-method interface implementation because of history, and filtering uses a delegate because with the addition of anonymous delegates in C# 2.0, delegates are all the rage.

Example 4-33. Filtering
public partial class Window1 : Window {
  ...
  ICollectionView GetFamilyView(  ) {
    People people = (People)this.FindResource("Family");
    return BindingOperations.GetDefaultView(people);
  }

  void filterButton_Click(object sender, RoutedEventArgs e) {
    ICollectionView view = GetFamilyView(  );
    if( view.Filter == null ) {
       view.Filter = delegate(object item) {
        return ((Person)item).Age >= 18;
      };
    }
    else {
      view.Filter = null;
    }
  }
}

Like sorting, with a filter in place, new things are filtered appropriately, as Figure 4-18 shows.

Figure 4-18. Unfiltered, filtered for adults, and adding to a filtered view


The top window in Figure 4-18 shows no filtering, the middle window shows filtering of the initial list, and the bottom window shows adding a new adult with filtering still in place.


©2008 FAQ - WPF Labs - Discuss - Terms of Use - Privacy Policy - About WPF
- Interview Questions - Sharepoint Articles - Interview Questions Resource Library - All about LINQ - MS Knowledgebase Articles - Electronics and Hardware discussions