DataGridのCellTemplateが親のDataContextにバインドするのって以外に難しくね?

Pageの中にDataGridが1つあったとします。
こんなかんじで。

  • Page    (DataContext=PageViewModel)
    • DataGrid  (ItemsSource=PageViewModel.DataGridItems)
      • DataGridTemplateColumn
        • DataGridTemplateColumn.CellTemplate
          • ComboBox

さて、ここで問題。

CellTemplateの中のComboBoxのItemsSouceに、PageViewModelのプロパティを使いたい場合、どうすればいいでしょうか。

...
即答されると悲しいけど、以外に難しいと思う。
直感的には

<ComboBox ItemsSource="{Binding DataContext.ComboSource, ElementName=LayoutRoot}" />
<!-- ※これは動きません --> 

とか、

<ComboBox ItemsSource="{Binding ComboSource, Source=Parent}" />
<!-- ※これは大嘘コードです --> 

とか書けると良いけど、残念ながらダメ。

結構、こうしたい局面ってあると思う。
Pageで大項目を指定させて、DataGridのすべての行は、それに紐尽く小項目をItemsSourceにしたいとか。
(Pageが都道府県で、行が市、ってなんかそう。)

解決策でよく見かける(自分もやっていた)は

  1. 行アイテムにも行アイテム用のViewModelを持たせ、PageViewModelとの連携を図る
  2. ページのリソースに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”とかが一番直感的なので、そう書かして欲しいですねぇ。