今回は「ItemsSource」についてです。
その前にちょっと復習を。
前回、View-ViewModel間でのデータのやり取りにはDataContextという「データ環境」を介して行っている、ということを説明しました。
例えばWPFアプリケーションを作成するとMainWindow.xamlが作成されますが、これのコードビハインド(MainWindow.xaml.cs)で以下のような記述をするとします。
public MainWindow()
{
InitializeComponent();
Hoge hoge = new Hoge();
this.DataContext = hoge;
}
この場合、MainWindowはhogeインスタンスが持つプロパティをDataContextを介してやり取りしますよ、という意味になります。
それを踏まえた上で、以下のようなクラス定義がされている場合を考えてみます。
public class Hoge
{
public Hoge()
{
this.ComboItems = new List();
this.ComboItems.Add(new TestComboItem() { Value = "test1", DispValue = "てすと1" });
this.ComboItems.Add(new TestComboItem() { Value = "test2", DispValue = "てすと2" });
}
private string _SampleText = "テストテキスト";
public string SampleText
{
get { return _SampleText; }
set { _SampleText = value; }
}
public List ComboItems
{
get;
set;
}
}
public class TestComboItem
{
public string Value { get; set; }
public string DispValue { get; set; }
}
HogeクラスはSampleTextというプロパティを持っています。
MainWindowのDataContextにはこのクラスのインスタンスが設定されているので、以下な感じのコードでSampleTextプロパティにデータバインディングアクセスできるはずです。
<Window x:Class="WpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel Orientation="Vertical">
<TextBox Text="{Binding SampleText}"/>
</StackPanel>
</Window>
これを実行すると、HogeクラスのSampleTextプロパティの値がテキストボックスに表示されます。
ちゃんとDataContextに設定したhogeインスタンスが持つSampleTextプロパティ値が表示された(TextBox.TextプロパティにSampleTextプロパティ値がバインドされた)ことが確認できますね(画像ないけど)。
さて、TextBox.Textには「単一の値」がバインドされました。
でもユーザコントロールは単一の値ではなく、ListBoxのように「複数の値」から1つの値を選択するようなコンポーネントもあります。
では、「複数の値」をバインドするにはどうすればよいのでしょう?
そのやり方自体は簡単です。ググれば山ほど出てきますが、大体以下のようなコードが出てくると思います。
<ListBox DisplayMemberPath="DispValue" SelectedValuePath="Value" ItemsSource="{Binding ComboItems}"/>
上記のように、赤字で示している部分の、「ItemsSource」を利用するのです。
このように、「複数の値」をバインドするには「ItemsSource」に「リストやコレクション」をバインドしてやればよいのです。
そしてバインドされたリストの各データ(TestComboItem)のValueプロパティ、DispValueプロパティを
内部保持値(SelectedValuePath)、表示値(DisplayMemberPath)として設定し、表示しています。
単純に「やり方」だけならここまで読めばわかります。
でもちょっとまって、どうしてItemsSourceにコレクションをバインドすればうまくいくのでしょう?
っていうかItemsSourceってなんなの?
★ItemsSourceってなんぞ?
まずは、ItemsSource="{Binding ComboItems}を思い出してください。
先ほど、【「複数の値」をバインドするにはどうしたらよいの?】という表現を使いましたが、この表現は正しくありません。
なぜなら、ItemsSourceにバインドしているのはあくまでDataContextが保持する「ComboItemsプロパティ値」であるためです。
もっと詳しく言うと、一つの「Listインスタンス」をバインドしているだけです。
つまり
データバインドとはDataContext(に設定されたインスタンス)が保持する単一のプロパティへの参照を指定するもの
です。ここを勘違いしてはいけません。
「複数の値」をバインドする機能なぞ存在しないのです。
さて、ItemsSourceに話を戻しますが、ざっくり言ってしまうと、ItemsSourceとは「複数の値をとりうるコントロールが持つ、選択肢一覧を保持する入れ物」です。
そのため、ListBox、ComboBox、DataGridなど、内部に複数の値を保持しなければいけないコントロールはすべてItemsSourceプロパティを持っています。
先ほどのItemsSourceへのバインディングは、「選択肢を設定した」と言い換えても差支えないと思います。
さて、実のところItemsSource自体の説明はこれだけで足りてしまうのです。
しかしながら、ItemsSourceを利用するということは、もっと知っておかなければならないことあります。
それは
「暗黙でターゲットとなるDataContextが変わっている」
ということです。
???何言ってんの?と思いませんでしたか?
では説明してみましょう。
★暗黙でターゲットとなるDataContextが変わる?ってなんぞ?
くどいようですが、
データバインドとはDataContext(に設定されたインスタンス)が保持する単一のプロパティへの参照を指定するもの
です。
さてここでもう一度以下のxamlを見てみましょう。
<ListBox DisplayMemberPath="DispValue" SelectedValuePath="Value" ItemsSource="{Binding ComboItems}"/>
さて、これを見て疑問に思うところはありませんか?
先ほどはサラッと流しましたが、DisplayMemberPath="DispValue",SelectedValuePath="Value"の部分です。
Value,DispValueはTestComboItemクラスが保持するプロパティですが、現在DataContextに指定しているインスタンスはhogeインスタンスです。そしてHogeクラスはValue、DipsValueプロパティを持っていません。
データバインドは必ず「DataContext」から参照できるプロパティしかバインドできません。
なのに、TestComboItemクラスが保持するプロパティにアクセスしているようにみえます。
これはどういうことでしょう?
ListBoxを表示すると、複数行のラベルが表示されます。
つまり、ListBoxは単一のコンポーネントで構成されているわけではなく、複数のラベルコンポーネントを連続表示して成り立っているコンポーネントであることがわかります。
さて、この複数のラベルコンポーネント、どこからデータを持ってきて表示しているのでしょうか?
これが今回のポイントです。
それは、ItemsSourceに設定されたリストの各要素から持ってきているのです。
つまり
1行目ラベル = ItemsSource[0];
2行目ラベル = ItemsSource[1];
の関係ということになります。
ですが、ものすごくクドイようですが
データバインドは必ず「DataContext」から参照できるプロパティしかバインドできません。
つまり、ラベルに値を設定するには、各行のラベルコンポーネントのDataContextも設定してやる必要があります。
つまり
1行目ラベル.DataContext = ItemsSource[0];
2行目ラベル.DataContext = ItemsSource[1];
という関係にしなくてはなりません。
そして、この関係は内部で自動で行われています。
これが、「暗黙でターゲットとなるDataContextが変わる」ということです。
★まとめ
ItemsSourceを利用した際に注意しなくてはいけない重要な点は、
ItemsSourceの各要素は、別コンポーネントのDataContextとして扱われている
ことを意識することです。
つまり、このコンポーネントのDataContextはどこを参照しているのかをきちんと把握することです。
これが頭から抜けていると、DataTemplateを利用した見た目のカスタマイズや、カスタムコンポーネントの作成で必ずつまづきます。
今回はえらく長くなってしまいました。
ですが、ここはWPFアプリケーションにおける最重要項目です。ここは理解できるまで踏ん張りましょう。
でないと永遠に脱初心者がなりません。
わからない場合はコメントなりで質問してみてくださいね。
より上のコードですが、
this.ComboItems = new List();
public List ComboItems
のList部分にエラーが出てしまいます…。
何回も読み返して考え方を身に着けます。
ありがとうございます。