How to bind to hierarchical data and create a master/details view (XAML)

You can make a multi-level master/details view of hierarchical data by binding list controls to CollectionViewSource instances.

One common structure for Windows Runtime apps is to navigate to different details pages when a user makes a selection in a master list. This is useful when you want to provide a rich visual representation of each item at every level in a hierarchy. The Grid Application and Split Application templates in Visual Studio demonstrate how to use this technique.

Another option is to display multiple levels of data on a single page. This is useful when you want to display a few simple lists that let people quickly drill down to an item of interest. This topic describes how to implement this technique by using multiple CollectionViewSource instances. These instances keep track of the current selection at each level.

The following example creates a view of a sports hierarchy that is organized into lists for leagues, divisions, and teams, and includes a team details view. When you select an item from any list, the subsequent views update automatically.

Roadmap: How does this topic relate to others? See:

Prerequisites

This topic assumes that you can create a basic Windows Runtime app using C++, C#, or Visual Basic. For instructions on creating your first Windows Runtime app, see Create your first Windows Store app using C# or Visual Basic.

Instructions

Step 1: Create the project

Create a new Windows Runtime app named "MasterDetailsBinding", by using the Blank Application template.

Step 2: Create the data model

Add a new class file to your project and replace its contents with the following code.

This code shows a simple data model that also generates sample data for display in the designer and at run time.

using System;
using System.Collections.Generic;
using System.Linq;

namespace MasterDetailsBinding
{
    public class Team
    {
        public string Name { get; set; }
        public int Wins { get; set; }
        public int Losses { get; set; }
    }

    public class Division
    {
        public string Name { get; set; }
        public IEnumerable<Team> Teams { get; set; }
    }

    public class League
    {
        public string Name { get; set; }
        public IEnumerable<Division> Divisions { get; set; }
    }

    public class LeagueList : List<League>
    {
        public LeagueList()
        {
            this.AddRange(GetLeague().ToList());
        }

        public IEnumerable<League> GetLeague()
        {
            return from x in Enumerable.Range(1, 2) select new League
            {
                Name = "League " + x,
                Divisions = GetDivisions(x).ToList()
            };
        }

        public IEnumerable<Division> GetDivisions(int x)
        {
            return from y in Enumerable.Range(1, 3) select new Division
            {
                Name = String.Format("Division {0}-{1}", x, y),
                Teams = GetTeams(x, y).ToList()
            };
        }

        public IEnumerable<Team> GetTeams(int x, int y)
        {
            return from z in Enumerable.Range(1, 4) select new Team
            {
                Name = String.Format("Team {0}-{1}-{2}", x, y, z),
                Wins = 25 - (x * y * z),
                Losses = x * y * z
            };
        }
    }
}

Step 3: Create the view

Replace the contents of the MainPage.xaml file with the following code.

This code shows the XAML that defines the view. The XAML first declares the sample data source (the local:LeagueList element) and three CollectionViewSource instances, and binds them together in a chain. The subsequent controls can then bind to the appropriate CollectionViewSource depending on the level in the hierarchy.

<Page
    x:Class="MasterDetailsBinding.MainPage"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MasterDetailsBinding"
    xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

  <Page.Resources>

    <local:LeagueList x:Key="LeagueData"/>
    <CollectionViewSource x:Name="Leagues" 
      Source="{StaticResource LeagueData}"/>
    <CollectionViewSource x:Name="Divisions"
      Source="{Binding Divisions, Source={StaticResource Leagues}}"/>
    <CollectionViewSource x:Name="Teams"
      Source="{Binding Teams, Source={StaticResource Divisions}}"/>

    <Style TargetType="TextBlock">
      <Setter Property="FontSize" Value="15"/>
      <Setter Property="FontWeight" Value="Bold"/>
    </Style>

    <Style TargetType="ListBox">
      <Setter Property="FontSize" Value="15"/>
    </Style>

    <Style TargetType="ContentControl">
      <Setter Property="FontSize" Value="15"/>
    </Style>

  </Page.Resources>

  <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">

    <StackPanel Orientation="Horizontal">

      <!-- All Leagues view -->

      <StackPanel Margin="5">
        <TextBlock Text="All Leagues"/>
        <ListBox ItemsSource="{Binding Source={StaticResource Leagues}}" 
          DisplayMemberPath="Name"/>
      </StackPanel>

      <!-- League/Divisions view -->

      <StackPanel Margin="5">
        <TextBlock Text="{Binding Name, Source={StaticResource Leagues}}"/>
        <ListBox ItemsSource="{Binding Source={StaticResource Divisions}}" 
          DisplayMemberPath="Name"/>
      </StackPanel>

      <!-- Division/Teams view -->

      <StackPanel Margin="5">
        <TextBlock Text="{Binding Name, Source={StaticResource Divisions}}"/>
        <ListBox ItemsSource="{Binding Source={StaticResource Teams}}" 
          DisplayMemberPath="Name"/>
      </StackPanel>

      <!-- Team view -->

      <ContentControl Content="{Binding Source={StaticResource Teams}}">
        <ContentControl.ContentTemplate>
          <DataTemplate>
            <StackPanel Margin="5">
              <TextBlock Text="{Binding Name}" 
                FontSize="15" FontWeight="Bold"/>
              <StackPanel Orientation="Horizontal" Margin="10,10">
                <TextBlock Text="Wins:" Margin="0,0,5,0"/>
                <TextBlock Text="{Binding Wins}"/>
              </StackPanel>
              <StackPanel Orientation="Horizontal" Margin="10,0">
                <TextBlock Text="Losses:" Margin="0,0,5,0"/>
                <TextBlock Text="{Binding Losses}"/>
              </StackPanel>
            </StackPanel>
          </DataTemplate>          
        </ContentControl.ContentTemplate>
      </ContentControl>

    </StackPanel>

  </Grid>
</Page>

Whenever a binding specifies a property, it automatically uses the value from the currently selected item in the bound CollectionViewSource. For example, the ContentControl representing the team view has its Content property bound to the TeamsCollectionViewSource. However, the controls in the DataTemplate bind to properties of the Team class because the CollectionViewSource automatically supplies the currently selected team from the teams list.

Roadmaps

Roadmap for Windows Runtime apps using C# or Visual Basic

Roadmap for Windows Runtime apps using C++

Samples

XAML data binding sample

XAML GridView grouping and SemanticZoom sample

StorageDataSource and GetVirtualizedFilesVector sample

Reference

Binding

DataContext

DependencyProperty

CollectionViewSource

DataTemplate

Concepts

Quickstart: Data binding to controls

Data binding with XAML

Binding markup extension

Property-path syntax

RelativeSource markup extension

Dependency properties overview

Custom dependency properties

Attached properties overview

Create your first Windows Store app using C# or Visual Basic