Blog

Books

Books I tech reviewed and recommend
Sorry I can't reply to every email and comment. If you need a fast response, try the WPF Forum or Silverlight Forum.
November 29, 2005

How do I synchronize three ListBoxes displaying three levels of hierarchical data?

The master-detail scenario with more than 2 levels is very common, and we made sure we have good support for it in WPF. I will show in this post three ways to sync selection of three ListBoxes, each displaying a different level of a hierarchy of data. In this sample, the first ListBox displays a list of mountain ski resorts. When the user selects a ski resort, the second ListBox gets updated with several lifts from that mountain. By selecting a particular lift, the third ListBox gets updated with ski runs that can be taken down from the top of that lift.

Here is the approach most people take when trying to get this scenario to work:

    <Window.Resources>
        <local:Mountains x:Key="mountains" />
        <CollectionViewSource Source="{StaticResource mountains}" x:Key="cvs" />
    </Window.Resources>
    <ListBox ItemsSource="{Binding Source={StaticResource cvs}}" DisplayMemberPath="Name" Name="lb1" />
    <ListBox ItemsSource="{Binding Source={StaticResource cvs}, Path=Lifts}" DisplayMemberPath="Name" Name="lb2" />
    <ListBox ItemsSource="{Binding Source={StaticResource cvs}, Path=Lifts/Runs}" Name="lb3" />

Unfortunately this does not work as expected: lb1 and lb2 are in sync but lb3 is not. When creating a custom view on top of a collection by using CollectionViewSource, selection and currency are in sync by default (more details can be found in my November 10 post). This is why lb1 and lb2 are in sync in this scenario. This markup does not use a custom view for the Lifts collection though – a default view is created internally instead. Default views do not have currency and selection in sync by default, which is the reason why lb2 and lb3 don’t sync.

There are at least three ways to have the three ListBoxes in sync.

The most obvious solution is to create a second CollectionViewSource for the Lifts collection and bind lb2 and lb3 to it:

    <Window.Resources>
        (…)
        <CollectionViewSource Source="{Binding Source={StaticResource cvs}, Path=Lifts}" x:Key="cvs2"/>
    </Window.Resources>
    <ListBox ItemsSource="{Binding Source={StaticResource cvs}}" DisplayMemberPath="Name" Name="lb1" />
    <ListBox ItemsSource="{Binding Source={StaticResource cvs2}}" DisplayMemberPath="Name" Name="lb2" />
    <ListBox ItemsSource="{Binding Source={StaticResource cvs2}, Path=Runs}" Name="lb3" />

The second solution is to ignore CollectionViewSource, and let WPF create default views internally for us. Because default views don’t sync selection and currency by default, we have to override the default behavior by setting IsSynchronizedWithCurrentItem to true:

    <ListBox ItemsSource="{Binding Source={StaticResource mountains}}" DisplayMemberPath="Name" IsSynchronizedWithCurrentItem="True" Name="lb1" />
    <ListBox ItemsSource="{Binding Source={StaticResource mountains}, Path=Lifts}" DisplayMemberPath="Name" IsSynchronizedWithCurrentItem="True" Name="lb2" />
    <ListBox ItemsSource="{Binding Source={StaticResource mountains}, Path=Lifts/Runs}" IsSynchronizedWithCurrentItem="True" Name="lb3" />

The third solution is to rely simply on the items displayed in the previous ListBox. Binding allows us to link not only to XML and objects, but also to other elements in the logical tree. To accomplish this scenario, we set the ElementName property of Binding to the Name of the source element (instead of setting Binding’s Source property), and the Path to the property of the element we’re interested in.

    <ListBox ItemsSource="{Binding Source={StaticResource mountains}}" DisplayMemberPath="Name" Name="lb1" IsSynchronizedWithCurrentItem="True"/>
    <ListBox DataContext="{Binding ElementName=lb1, Path=Items}" ItemsSource="{Binding Path=Lifts}" DisplayMemberPath="Name" Name="lb2" IsSynchronizedWithCurrentItem="True"/>
    <ListBox DataContext="{Binding ElementName=lb2, Path=Items}" ItemsSource="{Binding Path=Runs}" Name="lb3" IsSynchronizedWithCurrentItem="True"/>

In the markup above, we set the DataContext of the second ListBox to the first ListBox’s Items property. Because DataContext is not expecting a collection, internally the binding engine returns the current item of that collection (more details in my previous post). We can then bind the ItemsSource to the Lifts property of the current Mountain, which returns the list we want.

This sample uses CLR objects as the data source. When using an XML data source, note that only the third solution above will work (for reasons I won’t go into here).

Here is a screen shot of the completed sample:

Here you can find the VS project with this sample code. This works with November CTP WPF bits.


Posted by Bea under WPF | Comments (0)

November 18, 2005

Master-detail scenario

In the simplest master-detail scenario, clicking a particular item of an ItemsControl causes the details about that item to be displayed in another control. For example, an application may display a list of customer names in a ListBox, and clicking a particular customer causes TextBlocks to be updated with the address, phone number and date of birth of that customer.

In this post I will use a data source with the planets of the solar system: clicking on the name of a planet in the ListBox causes its picture and information to be displayed in a templated ContentControl. The ListBox plays the role of the master and the ContentControl presents the detail.

In the resources section of the Window, I have an XmlDataProvider with the planet data and a CollectionViewSource with the Source property bound to the provider (for more information about CollectionViewSource see my previous post). Here is the markup for the ListBox bound to the CollectionViewSource:

    <!– master –>
    <ListBox ItemsSource="{Binding Source={StaticResource cvs}}" DisplayMemberPath="@Name" Padding="5" Margin="0,0,5,0"/>

I also need a ContentControl, which is used to display the details of the selected item. The markup below may seem a little strange at first: we are binding a ContentControl (which displays a single item) to a collection of items? (Notice that its Content’s Binding is the same as the Binding in the ListBox’s ItemsSource.) This markup works fine because the data binding engine is smart enough to distinguish between the two targets. When binding an ItemsControl to a collection we get the collection; when binding a ContentControl to a collection we get the current item of that collection. This is what makes the master-detail scenario so simple in WPF.

    <!– detail –>
    <ContentControl ContentTemplate="{StaticResource detailTemplate}" Content="{Binding Source={StaticResource cvs}}"/>

To specify how the details of the planet data should be displayed in the ContentControl, we use a DataTemplate. The following markup shows the data-binding specific parts of the DataTemplate. Notice that because I am binding to XML, the Binding is using XPath instead of Path.

    <DataTemplate x:Key="detailTemplate">
        (…)
        <Image Source="{Binding XPath=Image, Converter={StaticResource stringToImageSource}}" />
        (…)
        <StackPanel Orientation="Horizontal" Margin="5,5,5,0">
            <TextBlock Text="Orbit: " FontWeight="Bold" />
            <TextBlock Text="{Binding XPath=Orbit}" />
        </StackPanel>
        <StackPanel Orientation="Horizontal" Margin="5,0,5,0">
            <TextBlock Text="Diameter: " FontWeight="Bold"/>
            <TextBlock Text="{Binding XPath=Diameter}" />
        </StackPanel>
        <StackPanel Orientation="Horizontal" Margin="5,0,5,5">
            <TextBlock Text="Mass: " FontWeight="Bold"/>
            <TextBlock Text="{Binding XPath=Mass}" />
        </StackPanel>
        (…)
    </DataTemplate>

Here is a screen shot of the completed sample:

Here you can find the VS project with this sample code. This works with September CTP WPF bits.


Posted by Bea under WPF | Comments (8)

November 10, 2005

How can I sync selection of two data bound ListBoxes?

I will show you two ways of syncing the selection of two data bound ListBoxes.

In the first solution, I will create a custom view over the collection and bind both ListBoxes to it. Views track the current item of their underlying collection, and allow us to sort, group and filter their items. CollectionViewSource is a new class introduced in September CTP that makes it possible to create a custom view in markup. Because the custom view created tracks the current item of the collection, and currency and selection are in sync in this scenario, binding both ListBoxes to the same view causes their selected items to be in sync.

    <Window.Resources>
        <local:GreekGods x:Key="source" />
        <CollectionViewSource Source="{StaticResource source}" x:Key="cvs"/>
    </Window.Resources>
    
    <ListBox ItemsSource="{Binding Source={StaticResource cvs}}" DisplayMemberPath="Name"/>
    <ListBox ItemsSource="{Binding Source={StaticResource cvs}}" DisplayMemberPath="Name"/>

I will write about how to use CollectionViewSource to sort, group and filter items in a future post.

An alternative way to achieve the same behavior is to set both ItemsSource properties to the data source and set the IsSynchronizedWithCurrentItem properties to true:

    <ListBox ItemsSource="{StaticResource source}" IsSynchronizedWithCurrentItem="True" DisplayMemberPath="Name"/>
    <ListBox ItemsSource="{StaticResource source}" IsSynchronizedWithCurrentItem="True" DisplayMemberPath="Name"/>

This markup works because when binding to a collection, a view is always created. If you don’t specify one, a default view is created for you internally. Although this view tracks current item the same way the custom one did, when you have a default view currency and selection do not sync by default. The way to override this behavior is by setting the IsSynchronizedWithCurrentItem property to true.

The data team made the default synchronization behavior be different for custom views and default views based on customer feedback. This way, users that are aware of the concept of view and are explicit about it get the synchronization they expect, and the rest of the users don’t.

In the image below, the first and second ListBoxes are bound to the CollectionViewSource and the third and fourth ones have InSynchronizedWithCurrentItem set to true.

Here you can find the VS project with this sample code. This works with September CTP WPF bits.


Posted by Bea under WPF | Comments (4)