How to: Bind to Hierarchical Data and Create a Master/Details View
Microsoft Silverlight will reach end of support after October 2021. Learn more.
This topic describes how to bind list controls to hierarchical data in order to implement a multi-level master/details view.
Example
This example creates a view of sports teams that is organized hierarchically into leagues, divisions, and teams. When you select a league, you see the corresponding divisions. When you select a division, you see the corresponding teams.
The data is an ObservableCollection(Of League) (ObservableCollection<League> in C#). Each League has a Divisions property of type ObservableCollection(Of Division), and each Division has a Teams property of type ObservableCollection(Of Team). Each entity type also has a Name property.
Imports System.Collections.ObjectModel
Public Class Team
Public Property Name As String
End Class
Public Class Division
Public Property Name As String
Public Property Teams As ObservableCollection(Of Team)
End Class
Public Class League
Public Property Name As String
Public Property Divisions As ObservableCollection(Of Division)
End Class
Public Class LeagueList
Inherits ObservableCollection(Of League)
Public Sub New()
MyBase.New()
For x = 1 To 3
Dim theLeague As New League() With {
.Name = "League " & x,
.Divisions = New ObservableCollection(Of Division)}
For y = 1 To 4
Dim theDivision As New Division() With {
.Name = String.Format("Division {0}-{1}", x, y),
.Teams = New ObservableCollection(Of Team)}
For z = 1 To 5
Dim theTeam As New Team() With {
.Name = String.Format("Team {0}-{1}-{2}", x, y, z)}
theDivision.Teams.Add(theTeam)
Next
theLeague.Divisions.Add(theDivision)
Next
Me.Add(theLeague)
Next
End Sub
End Class
using System;
using System.Collections.ObjectModel;
namespace MasterDetailsBinding
{
public class Team
{
public string Name { get; set; }
}
public class Division
{
public string Name { get; set; }
public ObservableCollection<Team> Teams { get; set; }
}
public class League
{
public string Name { get; set; }
public ObservableCollection<Division> Divisions { get; set; }
}
public class LeagueList : ObservableCollection<League>
{
public LeagueList()
{
for (int x = 1; x < 3; x++)
{
League theLeague = new League()
{
Name = "League " + x,
Divisions = new ObservableCollection<Division>()
};
for (int y = 1; y < 4; y++)
{
Division theDivision = new Division()
{
Name = String.Format("Division {0}-{1}", x, y),
Teams = new ObservableCollection<Team>()
};
for (int z = 1; z < 5; z++)
{
Team theTeam = new Team()
{
Name = String.Format("Team {0}-{1}-{2}", x, y, z)
};
theDivision.Teams.Add(theTeam);
}
theLeague.Divisions.Add(theDivision);
}
this.Add(theLeague);
}
}
}
}
The user interface and the data bindings are defined entirely in XAML. There is one TextBlock header and one ListBox for each entity type, with contents as shown in the following table:
Position |
TextBlock.Text |
ListBox.ItemsSource |
---|---|---|
first (leagues) |
"All Leagues" |
Bound to Leagues. |
second (divisions) |
Bound to the Name property of the current League. |
Bound to the Divisions property of the current League. |
third (teams) |
Bound to the Name property of the current Division. |
Bound to the Teams property of the current Division. |
The contents of the last two text blocks and list boxes depend on the selections in the first two list boxes. To track these selections, the league and division data is bound through two CollectionViewSource instances.
The LeaguesCollectionViewSource binds directly to the external sample data type instantiated in XAML. The DataContext of the LayoutRoot binds to the CollectionViewSource, providing a data source for the first ListBox and the second TextBlock.
As you can see in the following XAML, the ItemsSource property of the ListBox binds directly to the source. The TextBlock.Text property, however, uses a binding path set to "Name". The CollectionViewSource automatically routes this path to the Name property of the currently selected League.
Similarly, the DivisionsCollectionViewSource binds to the Divisions property of the current League in the LeaguesCollectionViewSource. This provides an ItemsSource binding for the second list box and a DataContext for the StackPanel that contains the third text block and list box. The text block and list box use binding path values to bind to properties of the currently selected Division.
To run this example, create a new Silverlight project called MasterDetailsBinding, add the data classes shown above, and replace MainPage.xaml with the following XAML.
<UserControl x:Class="MasterDetailsBinding.MainPage"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MasterDetailsBinding">
<UserControl.Resources>
<local:LeagueList x:Key="LeagueData"/>
<CollectionViewSource x:Name="Leagues" Source="{StaticResource LeagueData}"/>
<CollectionViewSource x:Name="Divisions"
Source="{Binding Divisions, Source={StaticResource Leagues}}"/>
</UserControl.Resources>
<StackPanel x:Name="LayoutRoot" Orientation="Horizontal" Margin="5"
DataContext="{Binding Source={StaticResource Leagues}}">
<StackPanel Margin="5">
<TextBlock Text="All Leagues" Margin="3" FontWeight="Bold"/>
<ListBox ItemsSource="{Binding}" DisplayMemberPath="Name"/>
</StackPanel>
<StackPanel Margin="5">
<TextBlock Text="{Binding Name}" Margin="3" FontWeight="Bold"/>
<ListBox ItemsSource="{Binding Source={StaticResource Divisions}}"
DisplayMemberPath="Name"/>
</StackPanel>
<StackPanel Margin="5"
DataContext="{Binding Source={StaticResource Divisions}}">
<TextBlock Text="{Binding Name}" Margin="3" FontWeight="Bold"/>
<ListBox ItemsSource="{Binding Teams}" DisplayMemberPath="Name"/>
</StackPanel>
</StackPanel>
</UserControl>