WPFの中核を成すであろうDataContextについて、設定方法がいくつか存在します。どのやり方でも結果として同じになるのですが、利用シーンにおいて適した設定方法が存在しますので、整理もかねて紹介します。
事前知識(XAMLが解釈されるタイミング)
DataContextの話に入る前に、WPFで扱われるXAMLがどのタイミングで解釈され生成されるのかを確認しておきます。
C言語などではmain関数から始まりますが、C#WPFでは、App.xaml,csからプログラムがスタートします。App.xamlを確認すると、StartupUriというプロパティがありますが、ここで最初に画面を指定しています。
<Application x:Class="WpfApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
上記の例だと5行目のStartupUri="MainWindow.xaml"
で、MainWindow.xamlを呼び出しています。しかし、ここで実際に読み込まれるのは、コードビハインドであるMainWindow.xaml.csになります。
では、xamlが読み込まれるのはどこになるのでしょうか?
namespace WpfApp {
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
}
}
それはコードビハインドMainWindow.xaml.cs内のコンストラクタで実行されるInitializeComponent()です。InitializeComponent()が実行されるとMainWindow.xamlが解釈され、生成されます。つまりxamlないでDataContextにデータバインディングしている場合、InitializeComponet()が実行されxamlの生成が開始される前にDataContextを設定する必要があります。

DataContextの設定方法
まずプロジェクトの構成は次のように考えます。

namespace WpfApp.ViewModel {
public class MainViewModel {
public string labelText { get; }
public MainViewModel() {
labelText = "ProgLife";
}
}
}
MainWindowにはLabelを一つ置き、MainViewModel内のstringをLabelに反映させる単純なプログラムです。ちなみにlabelTextにGetterを用意しておかないとXAML内で使用できません。

その1:XAML内で設定
もっともよく目にするDataContextの設定方法だと思います。
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:WpfApp.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="100" Width="200">
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
<Grid>
<Label Content="{Binding labelText}"/>
</Grid>
</Window>
XAML内でDataContextとなるクラスを指定しています。これでXAML内でDataContext内のプロパティ(ここではlabelText)を使用できるようになります。
このDataContextがインスタンス化されるのは、コードビハインドMainWindow.xaml.csのコンストラクタでInitializeComponent()が実行され、XAMLが解析されるタイミングですね。
その2:データコンテキストのコンストラクタ内で設定
DataContextはXAMLが解析される前にインスタンス化し、DataContextとして設定する必要があります。なのでこういう書き方もできます。
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:WpfApp.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="100" Width="200">
<Grid>
<Label Content="{Binding labelText}"/>
</Grid>
</Window>
namespace WpfApp {
public partial class MainWindow : Window {
public MainWindow() {
this.DataContext = new MainViewModel();
InitializeComponent();
}
}
}
このパターンではInitializeComponet()を実行する前に、MainViewModelをインスタンス化し、DataContextとして設定しています。DataContextはWindowクラスのプロパティとして用意されています。
このDataContextがインスタンス化されるのは、コードビハインドMainWindow.xaml.csのコンストラクタが呼ばれたタイミングで、InitializeComponent()が実行される前になります。
その3:Windowインスタンス化時のプロパティとして設定
ここまで紹介した方法ですと、XAMLとデータコンストラクタは1対1に紐づけられてしまいます。Windowの呼び出し方によって、表示内容は同じでもデータコンストラクタは別にしたいという場合もあるかと思います。その場合は、Windowを生成するときにDataContextをプロパティとして渡してあげることができます。
<Application x:Class="WpfApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp" >
<Application.Resources>
</Application.Resources>
</Application>
using System.Windows;
using WpfApp.ViewModel;
namespace WpfApp {
public partial class App : Application {
protected override void OnStartup(StartupEventArgs e) {
var v = new MainWindow() {
DataContext = new MainViewModel()
};
v.ShowDialog();
}
}
}
App.xamlのStartupUriを削除して、App.xaml.cs内でMainWindowをインスタンス化し、プロパティであるDataContextを設定しています。
今回は説明のためにApp.xamlとApp.xaml.csを変更していますが、ボタンを押したときの画面遷移などでも同様に使えます。
まとめ
どのやり方でも実現できます。個人的には、最初に呼ばれるWindowの場合はコードビハインド内のコンストラクタ、他Windowから呼び出される場合はインスタンス化時のプロパティとしてデータコンテキストを設定する方法が好きです。(結局好み)
コメント