WPF Tip: Binding to the Selected Item in a Master-Details View

December 6, 2011

tags: , , , ,
no comments

Master-details is a common way to display data. A master view provides minimal data for a collection of objects (e.g. book names), and when selecting one such item, a details view provides more information for the selected item (e.g. a book’s name, author, publication date, etc.).

In WPF, data binding (in XAML) is typically used to achieve the connection between an object and its details without using any code. Let’s see how we can achieve this.

Let’s say we want to show a list of book names, and when a book is selected, we want to show its details. The complete application might look like so:

image

The windows contains a Grid with two rows. The top row hosts a ListBox, while the bottom row hosts a StackPanel with a bunch of simple TextBlocks inside. Here’s the basic ListBox markup:

  1. <ListBox ItemsSource="{Binding}" HorizontalContentAlignment="Stretch">
  2.     <ListBox.ItemTemplate>
  3.         <DataTemplate>
  4.             <Border BorderBrush="Blue" BorderThickness="2" Margin="0" Padding="4" CornerRadius="4" x:Name="border">
  5.                 <TextBlock TextAlignment="Center" Text="{Binding Name}" Foreground="Black" FontWeight="Bold" FontSize="16" x:Name="text"/>
  6.             </Border>
  7.             <DataTemplate.Triggers>
  8.                 <DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType=ListBoxItem}}" Value="True">
  9.                     <Setter Property="Background" Value="Yellow" TargetName="border" />
  10.                 </DataTrigger>
  11.             </DataTemplate.Triggers>
  12.         </DataTemplate>
  13.     </ListBox.ItemTemplate>
  14. </ListBox>

The data trigger is of no significance as it just sets the selected item’s background as yellow.

The ItemsSource property is bound to the nearest DataContext up the visual tree. We’ll set that context in code (for simplicity) in the Window’s constructor:

  1. var books = new ObservableCollection<Book> {
  2.     new Book { Name = "Windows Internals", Author = "Mark Russinovich", Published = new DateTime(2009, 6, 17) },
  3.     new Book { Name = "Essential COM", Author = "Don Box", Published = new DateTime(1998, 1, 1) },
  4.     new Book { Name = "Programming Windows with MFC", Author = "Jeff Prosise", Published = new DateTime(1999, 5, 13) },
  5.     new Book { Name = "Astrology of Fate", Author = "Liz Greene", Published = new DateTime(1984, 11, 1) },
  6.     new Book { Name = "Windows via C/C++", Author = "Jeffrey Richter", Published = new DateTime(2007, 12, 12) },
  7.     new Book { Name = "CLR Via C#", Author = "Jeffrey Richter", Published = new DateTime(2010, 2, 11) },
  8.     new Book { Name = "Pro WPF in C# 2010", Author = "Matthew Macdonald", Published = new DateTime(2009, 12, 23) },
  9. };
  10.  
  11. DataContext = books;

Now for the details part. How can we bind the TextBlocks Text property to the currently selected item in the ListBox? Here’s one way to do it:

  1. <StackPanel Grid.Row="1" TextBlock.FontSize="16" DataContext="{Binding SelectedItem, ElementName=_list}">
  2.     <TextBlock Margin="4" Text="{Binding Name, StringFormat=Name: {0}}" />
  3.     <TextBlock Margin="4" Text="{Binding Author, StringFormat=Author: {0}}" />
  4.     <TextBlock Margin="4" Text="{Binding Published, StringFormat=Published: {0:d}}" />
  5. </StackPanel>

This assumes the ListBox is named _list. Although this works, it’s a bit too specific: we’re looking up the SelectedItem of a specific control. What happens if that control is later replaced with something else (like a ComboBox) and is named differently? What is the ListBox is removed altogether and selection is controlled programmatically via some other UI elements?

It turns out, we can do better. We need to add the IsSynchronizedWithCurrentItem=”true” to the ListBox. This will use the default view, that sits between the ListBox and the actual data collection (the books). Now we can bind to the view, by hooking up to the CurrentItem property (from the ICollectionView interface implemented by the view):

  1. <StackPanel Grid.Row="1" TextBlock.FontSize="16" DataContext="{Binding CurrentItem}">

That’s better. No dependency on any specific ListBox. Can we make it even better? We can. We’ll remove the local DataContext on the StackPanel and prepend a forward slash (/) to the property names within the TextBlocks Text property, like so:

  1. <StackPanel Grid.Row="1" TextBlock.FontSize="16" >
  2.     <TextBlock Margin="4" Text="{Binding /Name, StringFormat=Name: {0}}" />
  3.     <TextBlock Margin="4" Text="{Binding /Author, StringFormat=Author: {0}}" />
  4.     <TextBlock Margin="4" Text="{Binding /Published, StringFormat=Published: {0:d}}" />
  5. </StackPanel>

Note the slash in the bound properties (/Name, /Author, /Published). We simply pick the relevant properties on a single item (book in our example). Note that the IsSynchronizedWithCurrentItem must be set to true, as before.

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*