DataGridのCellTemplateが親のDataContextにバインドするのって以外に難しくね?
Pageの中にDataGridが1つあったとします。
こんなかんじで。
- Page (DataContext=PageViewModel)
- DataGrid (ItemsSource=PageViewModel.DataGridItems)
- DataGridTemplateColumn
- DataGridTemplateColumn.CellTemplate
- ComboBox
- DataGridTemplateColumn.CellTemplate
- DataGridTemplateColumn
- DataGrid (ItemsSource=PageViewModel.DataGridItems)
さて、ここで問題。
CellTemplateの中のComboBoxのItemsSouceに、PageViewModelのプロパティを使いたい場合、どうすればいいでしょうか。
...
即答されると悲しいけど、以外に難しいと思う。
直感的には
<ComboBox ItemsSource="{Binding DataContext.ComboSource, ElementName=LayoutRoot}" /> <!-- ※これは動きません -->
とか、
<ComboBox ItemsSource="{Binding ComboSource, Source=Parent}" /> <!-- ※これは大嘘コードです -->
とか書けると良いけど、残念ながらダメ。
結構、こうしたい局面ってあると思う。
Pageで大項目を指定させて、DataGridのすべての行は、それに紐尽く小項目をItemsSourceにしたいとか。
(Pageが都道府県で、行が市、ってなんかそう。)
解決策でよく見かける(自分もやっていた)は
- 行アイテムにも行アイテム用のViewModelを持たせ、PageViewModelとの連携を図る
- ページのリソースにPageViewModelを定義し、そこを参照する
例:
<UserControl.Resources> <local:PageViewModel x:Key="PageViewModel" /> </UserControl.Resources> <Grid x:Name="LayoutRoot" DataContext="{StaticResource PageViewModel}"> <sdk:DataGrid> ........... <ComboBox ItemsSource="{Binding ComboSource, Source={StaticResource PageViewModel}}" />
とかです。
でも、それぞれ難点があって、
1は、王道な解決策なんだろうけど、WCF RIA Services使ってると行用のViewModelなんか絶対作りたくない(利点をまるまる殺してしまうから)と思うし、
2はPageのDataContextにViewModelを設定できないのがイケてない・・・。
......
簡単そうに見えて、世の中甘くないもんですね。。。
......
と拗ねてたら、かなり小手先ではあるけど1つやり方を考えついた。
注目したのは
- ResouceはTemplateの中からもアクセスできる。
- Resouceに追加されたオブジェクトは、PageViewModelのプロパティにバインドできる。
の2点。
2つ合わせて、中継用オブジェクトをResouceに追加しちゃえば良いじゃない。
では、そのやり方。
1.まず、中継用クラスを作ります。
public class RelayPropertyObject : DependencyObject { /// <summary> /// 何でも中継しちゃうのでObjectです。 /// </summary> public object RelayProperty { get { return (object)GetValue(RelayPropertyProperty); } set { SetValue(RelayPropertyProperty, value); } } public static readonly DependencyProperty RelayPropertyProperty = DependencyProperty.Register( "RelayProperty", typeof(object), typeof(RelayPropertyObject), new PropertyMetadata(null)); }
簡単ですねえ。
2.そしたら、Pageのリソースに突っ込みます。
<UserControl.Resources> <local:RelayPropertyObject x:Key="RelayPropertyObject" RelayProperty="{Binding ComboSource}" /> </UserControl.Resources>
RelayPropertyにPageViewModelのプロパティをバインドしているのがミソです。
3.CellTemplateは
<ComboBox ItemsSource="{Binding RelayProperty, Source={StaticResource RelayPropertyObject}}" />
のように、ResouceにあるRelayPropertyObjectのRelayPropertyを指定します。
これでできちゃいます。
メデタシメデタシ。(静止画じゃようわからんですね・・・
ちなみにこのやり方、便利なのはCommandもバインドできちゃいます。(あたりまえか)
<UserControl.Resources> <local:RelayPropertyObject x:Key="RelayPropertyObject" RelayProperty="{Binding ComboSource}" /> <local:RelayPropertyObject x:Key="RemoveRowCommandRelay" RelayProperty="{Binding RemoveRowCommand}" /> </UserControl.Resources> ....................... <DataTemplate> <ComboBox ItemsSource="{Binding RelayProperty, Source={StaticResource RelayPropertyObject}}" /> </DataTemplate> ........................ <DataTemplate> <Button Command="{Binding RelayProperty, Source={StaticResource RemoveRowCommandRelay}}" CommandParameter="{Binding RowId}" /> </DataTemplate> <!-- 2011.10.07 Xaml記述が間違っていたので修正しました。 -->
なんて書いちゃって、行削除ボタンが作れたりします。
すごいですねぇ。うれしいですねぇ。
でも、やっぱり最初に書いた”Souce=Parent”とかが一番直感的なので、そう書かして欲しいですねぇ。