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.

Cc645060.SL_MasterDetailsBinding(en-us,VS.95).png

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>