C# WPFのDataContextの設定方法とタイミング

プログラミング知識
スポンサーリンク

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の設定方法

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

DataContextはMainViewModel.csです。
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から呼び出される場合はインスタンス化時のプロパティとしてデータコンテキストを設定する方法が好きです。(結局好み)

コメント

タイトルとURLをコピーしました