2014年05月01日

【WPF基礎】脱WPF初心者のための基礎知識 その2〜ItemsSourceってなんぞ?〜

今回は「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アプリケーションにおける最重要項目です。ここは理解できるまで踏ん張りましょう。

でないと永遠に脱初心者がなりません。

わからない場合はコメントなりで質問してみてくださいね。


posted by おっさん at 12:09 | Comment(2) | 【C#】WPF基礎 | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
HogeクラスはSampleTextというプロパティを持っています。

より上のコードですが、

this.ComboItems = new List();

public List ComboItems

のList部分にエラーが出てしまいます…。
Posted by aaa at 2017年11月10日 16:19
こういう記事を探していました。
何回も読み返して考え方を身に着けます。
ありがとうございます。
Posted by michi at 2020年08月11日 13:16
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント: