WinUI アプリのデータ バインディングを使用すると、コントロールをデータ ソースに効率的に接続できます。 コントロールを 1 つの項目または項目のコレクションにバインドする方法、項目のレンダリングを制御する方法、詳細ビューを実装する方法、表示用のデータの書式設定を行う方法について説明します。 詳細については、「 データ バインディングの詳細」を参照してください。
前提 条件
このトピックでは、Windows App SDK を使用して基本的な WinUI アプリを作成する方法を知っていることを前提としています。 最初の WinUI アプリを作成する手順については、「 WinUI アプリの作成」を参照してください。
プロジェクトを作成する
新しい WinUI 空のアプリ、パッケージ化された C# プロジェクトを作成します。 "クイック スタート" という名前を付けます。
1 つの項目にバインドする
すべてのバインディングは、バインディング ターゲットとバインディング ソースで構成されます。 通常、ターゲットはコントロールまたはその他の UI 要素のプロパティであり、ソースはクラス インスタンス (データ モデルまたはビュー モデル) のプロパティです。 この例では、コントロールを 1 つの項目にバインドする方法を示します。 ターゲットは、Textの TextBlock プロパティです。 ソースは、オーディオ録音を表す Recording という名前の単純なクラスのインスタンスです。 まずクラスを見てみましょう。
新しいクラスをプロジェクトに追加し、クラスに Recording名前を付けます。
namespace Quickstart
{
public class Recording
{
public string ArtistName { get; set; }
public string CompositionName { get; set; }
public DateTime ReleaseDateTime { get; set; }
public Recording()
{
ArtistName = "Wolfgang Amadeus Mozart";
CompositionName = "Andante in C for Piano";
ReleaseDateTime = new DateTime(1761, 1, 1);
}
public string OneLineSummary
{
get
{
return $"{CompositionName} by {ArtistName}, released: "
+ ReleaseDateTime.ToString("d");
}
}
}
public class RecordingViewModel
{
private Recording defaultRecording = new();
public Recording DefaultRecording { get { return defaultRecording; } }
}
}
次に、マークアップのウィンドウを表すクラスからバインディング ソース クラスを公開します。 MainWindow.xaml.csに RecordingViewModel 型の プロパティを追加します。
namespace Quickstart
{
public sealed partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
}
public RecordingViewModel ViewModel{ get; } = new RecordingViewModel();
}
}
最後に、TextBlock を ViewModel.DefaultRecording.OneLineSummary プロパティにバインドします。
<Window x:Class="Quickstart.MainWindow" ... >
<Grid>
<TextBlock Text="{x:Bind ViewModel.DefaultRecording.OneLineSummary}"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Grid>
</Window>
結果を次に示します。
項目のコレクションにバインドする
一般的なシナリオは、ビジネス オブジェクトのコレクションにバインドすることです。 C# では、データ バインディングに汎用 ObservableCollection<T> クラスを使用します。
INotifyCollectionChanged インターフェイスを実装します。このインターフェイスは、項目が追加または削除されたときにバインディングに変更通知を提供します。 ただし、.NET 8 以降での WinUI リリース モードの既知のバグのため、一部のシナリオでは List<T> を使用する必要があります。特にコレクションが静的であり、初期化後に変更されない場合です。 実行時にコレクションが変更されたときに UI を更新する必要がある場合は、 ObservableCollection<T>を使用します。 固定アイテムのセットのみを表示する必要がある場合は、 List<T> で十分です。 さらに、バインドされたコントロールをコレクション内のオブジェクトのプロパティに変更して更新する場合、それらのオブジェクトは INotifyPropertyChanged を実装する必要があります。 詳細については、「データ バインディングの詳細」を参照してください。
手記
List<T>を使用すると、コレクションの変更に関する変更通知を受け取らない場合があります。 変更に対応する必要がある場合は、 ObservableCollection<T>の使用を検討してください。 この例では、コレクションの変更に対応する必要がないため、 List<T> で十分です。
次の例では、 ListView を Recording オブジェクトのコレクションにバインドします。 まず、コレクションをビュー モデルに追加します。 これらの新しいメンバーを RecordingViewModel クラスに追加します。
public class RecordingViewModel
{
...
private List<Recording> recordings = new();
public List<Recording> Recordings{ get{ return recordings; } }
public RecordingViewModel()
{
recordings.Add(new Recording(){ ArtistName = "Johann Sebastian Bach",
CompositionName = "Mass in B minor", ReleaseDateTime = new DateTime(1748, 7, 8) });
recordings.Add(new Recording(){ ArtistName = "Ludwig van Beethoven",
CompositionName = "Third Symphony", ReleaseDateTime = new DateTime(1805, 2, 11) });
recordings.Add(new Recording(){ ArtistName = "George Frideric Handel",
CompositionName = "Serse", ReleaseDateTime = new DateTime(1737, 12, 3) });
}
}
次に、 ListView を ViewModel.Recordings プロパティにバインドします。
<Window x:Class="Quickstart.MainWindow" ... >
<Grid>
<ListView ItemsSource="{x:Bind ViewModel.Recordings}"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Grid>
</Window>
Recording クラスのデータ テンプレートをまだ提供していないので、UI フレームワークで実行できる最善の方法は、ListView の各項目に対して ToString を呼び出す方法です。
ToStringの既定の実装では、型名が返されます。
この問題を解決するには、 ToString をオーバーライドして OneLineSummaryの値を返すか、データ テンプレートを指定します。 データ テンプレート オプションは、より一般的で柔軟なソリューションです。 データ テンプレートは、コンテンツ コントロールの ContentTemplate プロパティまたはアイテム コントロールの ItemTemplate プロパティを使用して指定します。
Recording用のデータ テンプレートを、結果の図と共に設計する 2 つの方法を次に示します。
<ListView ItemsSource="{x:Bind ViewModel.Recordings}"
HorizontalAlignment="Center" VerticalAlignment="Center">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Recording">
<TextBlock Text="{x:Bind OneLineSummary}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView ItemsSource="{x:Bind ViewModel.Recordings}"
HorizontalAlignment="Center" VerticalAlignment="Center">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Recording">
<StackPanel Orientation="Horizontal" Margin="6">
<SymbolIcon Symbol="Audio" Margin="0,0,12,0"/>
<StackPanel>
<TextBlock Text="{x:Bind ArtistName}" FontWeight="Bold"/>
<TextBlock Text="{x:Bind CompositionName}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
XAML 構文の詳細については、「XAMLを使用して UI を作成する」を参照してください。 コントロール レイアウトの詳細については、「XAMLを使用してレイアウトを定義する」を参照してください。
詳細ビューを追加する
Recording アイテム内の オブジェクトのすべての詳細を表示することを選択できます。 しかし、そのアプローチは多くの領域を占有します。 代わりに、項目を識別するのに十分なデータだけをアイテムに表示できます。 ユーザーが選択を行うと、選択した項目のすべての詳細を、詳細ビューと呼ばれる別の UI に表示できます。 この配置は、マスター/詳細ビュー、またはリスト/詳細ビューとも呼ばれます。
この配置は、2 つの方法で実装できます。 詳細ビューは、ListViewの SelectedItem プロパティにバインドできます。 または、 CollectionViewSource を使用できます。 この場合、 ListView と詳細ビューの両方を CollectionViewSourceにバインドします。 この方法では、現在選択されている項目が自動的に処理されます。 両方の手法を次のセクションに示します。両方とも同じ結果を得られます (図を参照)。
手記
ここまでのトピックでは、 {x:Bind} マークアップ拡張のみを使用しました。 ただし、次のセクションで示すどちらの手法でも、 {Binding} マークアップ拡張の柔軟性が高くなります (ただし、パフォーマンスは低くなります)。
まず、SelectedItem 手法を示します。 C# アプリケーションの場合、必要な唯一の変更はマークアップです。
<Window x:Class="Quickstart.MainWindow" ... >
<Grid>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<ListView x:Name="recordingsListView" ItemsSource="{x:Bind ViewModel.Recordings}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Recording">
<StackPanel Orientation="Horizontal" Margin="6">
<SymbolIcon Symbol="Audio" Margin="0,0,12,0"/>
<StackPanel>
<TextBlock Text="{x:Bind CompositionName}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel DataContext="{Binding SelectedItem, ElementName=recordingsListView}"
Margin="0,24,0,0">
<TextBlock Text="{Binding ArtistName}"/>
<TextBlock Text="{Binding CompositionName}"/>
<TextBlock Text="{Binding ReleaseDateTime}"/>
</StackPanel>
</StackPanel>
</Grid>
</Window>
CollectionViewSource 手法では、最初に最上位レベルの CollectionViewSourceのリソースとして Grid を追加します。
<Grid.Resources>
<CollectionViewSource x:Name="RecordingsCollection" Source="{x:Bind ViewModel.Recordings}"/>
</Grid.Resources>
手記
WinUI の Window クラスには、Resources プロパティがありません。 代わりに、最上位の CollectionViewSource (または Gridなどの他の親 UI 要素) 要素に StackPanel を追加できます。
Page内で作業している場合は、CollectionViewSource を Page.Resourcesに追加できます。
次に、 ListView (名前を付ける必要がなくなったもの) と詳細ビューのバインドを 調整して CollectionViewSource を使用します。 詳細ビューを CollectionViewSourceに直接バインドすることで、コレクション自体にパスが見つからないバインド内の現在の項目にバインドすることを意味します。 バインディングのパスとして CurrentItem プロパティを指定する必要はありませんが、あいまいさがある場合は指定できます。
...
<ListView ItemsSource="{Binding Source={StaticResource RecordingsCollection}}">
...
<StackPanel DataContext="{Binding Source={StaticResource RecordingsCollection}}" ...>
...
各ケースで同じ結果を次に示します。
表示するデータ値を書式設定または変換する
上記のレンダリングには問題があります。
ReleaseDateTime プロパティは単なる日付ではなく、DateTime です。 そのため、必要以上の精度で表示されます。 1 つの解決策は、Recordingと同等のものを返す文字列プロパティを ReleaseDateTime.ToString("d") クラスに追加することです。 プロパティ ReleaseDate の名前付けは、日付と時刻ではなく、日付を返していることを示します。
ReleaseDateAsString名前付けすると、文字列が返されることを示します。
より柔軟なソリューションは、値コンバーターを使用することです。 独自の値コンバーターを作成する方法の例を次に示します。 Recording.csソース コード ファイルに次 の コードを追加します。
public class StringFormatter : Microsoft.UI.Xaml.Data.IValueConverter
{
// This converts the value object to the string to display.
// This will work with most simple types.
public object Convert(object value, Type targetType,
object parameter, string language)
{
// Retrieve the format string and use it to format the value.
string formatString = parameter as string;
if (!string.IsNullOrEmpty(formatString))
{
return string.Format(formatString, value);
}
// If the format string is null or empty, simply
// call ToString() on the value.
return value.ToString();
}
// No need to implement converting back on a one-way binding
public object ConvertBack(object value, Type targetType,
object parameter, string language)
{
throw new NotImplementedException();
}
}
StringFormatterのインスタンスをリソースとして追加し、TextBlock プロパティを表示するReleaseDateTimeのバインドで使用できるようになりました。
<Grid.Resources>
...
<local:StringFormatter x:Key="StringFormatterValueConverter"/>
</Grid.Resources>
...
<TextBlock Text="{Binding ReleaseDateTime,
Converter={StaticResource StringFormatterValueConverter},
ConverterParameter=Released: \{0:d\}}"/>
...
ご覧のように、書式設定の柔軟性のために、マークアップはコンバーター パラメーターを使用して書式指定文字列をコンバーターに渡します。 このトピックに示すコード例では、C# 値コンバーターはそのパラメーターを使用します。
結果を次に示します。
カスタム書式設定 を使用して日付を表示する
Binding と x:Bind の違い
WinUI アプリでデータ バインディングを使用する場合、 Binding と x:Bindという 2 つの主要なバインディング メカニズムが発生する可能性があります。 どちらも UI 要素をデータ ソースに接続する目的を果たしますが、次のような違いがあります。
-
x:Bind: コンパイル時のチェックを行い、パフォーマンスを向上させ、そして厳密な型指定を行います。 コンパイル時にデータ構造がわかっているシナリオに最適です。 -
Binding: 実行時の評価を提供し、コンパイル時にデータ構造が不明な場合など、動的なシナリオに対して柔軟性が高まります。
x:Bind でサポートされていないシナリオ
x:Bindは強力ですが、特定のシナリオでは使用できません。
-
動的データ構造: コンパイル時にデータ構造が不明な場合は、
x:Bindを使用できません。 -
要素間バインド:
x:Bindでは、2 つの UI 要素間の直接バインドはサポートされていません。 -
DataContextへのバインド:x:Bindは、親要素のDataContextを自動的に継承しません。 -
Mode=TwoWayを使用した双方向バインディング: サポートされていますが、x:Bindでは、一方向または双方向のバインドを使用するかどうかに関係なく、ソースが変更されたときに UI を更新するプロパティに対して、INotifyPropertyChangedを明示的に実装する必要があります。 双方向バインディングの主な違いは、変更も UI からソースに戻るということです。
実際の例と、それぞれを使用するタイミングの詳細については、次のトピックを参照してください。
関連コンテンツ
Windows developer