階層データをバインドしてマスター/詳細ビューを作る方法
Note
マスターや詳細の UWP サンプルもご覧ください。
チェーン内でバインドされた CollectionViewSource インスタンスに項目コントロールをバインドすることによって、階層データの複数レベルのマスター/詳細 (リスト/詳細とも呼ばれる) ビューを作成することができます。 このトピックでは、できる限り {x:Bind} マークアップ拡張を使用し、必要に応じて、より柔軟な (ただし効率は低下する) {Binding} マークアップ拡張を使います。
Windows アプリ SDK アプリで一般的な構造の 1 つに、マスター一覧でユーザーが選択したときに、さまざまな詳細ページに移動するということがあります。 これは、階層のすべてのレベルで、各項目をリッチな視覚表現を使って表示する場合に便利です。 また、複数のレベルのデータを 1 ページに表示することもできます。 これは、ユーザーが関心のある項目にすばやくドリルダウンできるように、シンプルな一覧をいくつか表示する場合に便利です。 このトピックでは、この操作を実装する方法について説明します。 CollectionViewSource インスタンスは、現在の選択を各階層レベルで追跡します。
ここでは、リーグ、クラス、チームの一覧に階層化され、チーム詳細のビューを含むスポーツ チーム階層のビューを作ります。 いずれかの一覧で項目を選ぶと、後続するビューが自動的に更新されます。
前提条件
このトピックでは、基本的な Windows アプリ SDK アプリを作成できることを前提としています。 最初のWindows アプリ SDK アプリを作成する手順については、「WinUI 3 (Windows App SDK) プロジェクトを初めて作成する」を参照してください。
プロジェクトを作成する
新しい空のアプリ、パッケージ (デスクトップの WinUI 3) プロジェクトを作成します。 プロジェクトに "MasterDetailsBinding" という名前を付けます。
データ モデルの作成
プロジェクトに新しいクラスを追加して ViewModel.cs という名前を付け、次のコードを追加します。 これは、バインディング ソース クラスになります。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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()
{
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
};
}
}
}
ビューを作る
次に、マークアップのページを表すクラスからバインディング ソース クラスを公開します。 これを行うには、LeagueList
型のプロパティを MainWindow に追加します。
namespace MasterDetailsBinding
{
public sealed partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
ViewModel = new LeagueList();
}
public LeagueList ViewModel { get; set; }
}
}
最後に、MainWindow.xaml ファイルの内容を次のマークアップに置き換えます。このマークアップでは、3 つの CollectionViewSource インスタンスを宣言し、チェーンで一緒にバインドします。 これにより、階層内のレベルに応じて、後続するコントロールを適切な CollectionViewSource
にバインドできるようになります。
<Window
x:Class="MasterDetailsBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MasterDetailsBinding"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Window.Resources>
<CollectionViewSource x:Name="Leagues"
Source="{x:Bind ViewModel}"/>
<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>
</Window.Resources>
<Grid>
<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>
</Window>
CollectionViewSource に直接バインドすることによって、コレクション自体ではパスが見つからない、バインディング内の現在の項目にバインドすることを意味します。 バインディングのパスとして CurrentItem
プロパティを指定する必要はありませんが、あいまいさがある場合は指定することもできます。 たとえば、チーム ビューを表す ContentControl で、その Content プロパティが Teams
CollectionViewSource
にバインドされているとします。 しかし、CollectionViewSource
が必要に応じてチームの一覧から現在選択されているチームを自動的に示すため、DataTemplate 内のコントロールは Team
クラスのプロパティにバインドされます。