LoginSignup
6
10

More than 5 years have passed since last update.

[WPF] MVVMパターンにおいて、DataGridTextColumnがDataContextを参照する方法

Last updated at Posted at 2017-04-25

はじめに

WPF画面にて DataDrid を使っているとき、DataGridTextColumn の Visibilityプロパティが ItemsSource 外の ViewModel のメンバを参照したい場面が出てきました。
普通に、RelativeSource で記述しても、エラーとなり参照できません。

あれこれ調べていたら、WPFの偉い人のブログの記事を参考に解決できましたので紹介します。

環境

.NET Framework 4.6.1

具体例

ViewModel
public class ViewModel
{
    /// <summary>価格表示フラッグ</summary>
    public bool IsShowPrice { get; set; }

    /// <summary>データコレクション</summary>
    public List<Product> ProductList { get; set; }
}

こんな ViewModel を Window の DataContext に設定し、DataGrid は ProductList の一覧を表示。
そのとき、IsShowPrice が true のときだけ、Product 内にある Price 列を表示する・・・というケースです。

普通に考えると、

XAML
<DataGrid ItemsSource="{Binding ProductList}">
    <DataGrid.Columns>
        <!-- この列は、IsShowPriceがtrueのときだけ表示したい -->
        <DataGridTextColumn Header="Price"
                            Binding="{Binding Price}"
                            Visibility="{Binding DataContext.IsShowPrice,
                                Converter={StaticResource booleanToVisibilityConverter}}"/>

てな感じで動きそうですが、IsShowPrice の参照でエラーになっちゃいます。
RelativeSource での参照もうまく行きません。

これもエラーになります
Visibility="{Binding IsShowPrice, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"

ItemsControl などで使う DataTemplate と異なり、DataGrid.Columns の子要素は、親(DataGrid)の仮想・論理ツリーに属していないから・・・というのが理由のようですが、なんのこっちゃ解りません。

Freezable を使って解決

これを解決するには、Freezableクラスを継承した、データProxyオブジェクトを用意することで、参照可能になります。

まずはそのProxyクラスを作成します。

BindingProxy.cs
public class BindingProxy : Freezable
{
    #region Overrides of Freezable
    protected override Freezable CreateInstanceCore()
    {
        return new BindingProxy();
    }
    #endregion

    public object Data
    {
        get { return (object)GetValue(DataProperty); }
        set { SetValue(DataProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Data.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty DataProperty =
        DependencyProperty.Register(
            "Data",
            typeof(object),
            typeof(BindingProxy),
            new UIPropertyMetadata(null)
        );
}

XAMLでは、DataGrid の Resource に、Window の DataContext への参照を定義します。

XAML
(略)
Proxyクラスへの名前空間を定義しておきます
xmlns:vm="clr-namespace:WpfApplication1.ViewModels"
(略)

<DataGrid ItemsSource="{Binding ProductList}">
    <!-- DataContextのproxy -->
    <DataGrid.Resources>
        <vm:BindingProxy x:Key="proxy" Data="{Binding}"/>
    </DataGrid.Resources>

    <DataGrid.Columns>
        <DataGridTextColumn Header="Price"
                            Binding="{Binding Price}"
                            Visibility="{Binding proxy.IsShowPrice,
                                Converter={StaticResource BooleanToVisibilityConverter}
                                Source={StaticResource proxy}}"/>

BindingProxy 定義のところで、Data="{Binding}" として、親(Window)のDataContextを対象にしています。
DataGridTextColumn の Visibilityでは、Source={StaticResource proxy} と記述することで、proxy.IsShowPrice メンバにアクセスできます。

備考

この BindingProxy は、DataGrid.Columns だけでなく、ItemsControl などのコレクション要素でも使えます。
親要素を参照する RelativeSource の記述方法って、どうにもわかりづらいし、型をして上の階層を検索して・・・っていう動きがいまいち気に入らないです。
AncestorLevelを記述したら、入れ子構造に依存しまくりになるし。

そんな複雑な参照も、この BindingProxy なら、比較的シンプルに書けて見通しも良くなります。
ただし、双方向のバインディングは動かないので、グリグリ動くものには使えないですが。

参考ページ

6
10
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
10