#はじめに
WPF画面にて DataDrid を使っているとき、DataGridTextColumn の Visibilityプロパティが ItemsSource 外の ViewModel のメンバを参照したい場面が出てきました。
普通に、RelativeSource で記述しても、エラーとなり参照できません。
あれこれ調べていたら、WPFの偉い人のブログの記事を参考に解決できましたので紹介します。
#環境
.NET Framework 4.6.1
#具体例
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 列を表示する・・・というケースです。
普通に考えると、
<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クラスを作成します。
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 への参照を定義します。
(略)
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 なら、比較的シンプルに書けて見通しも良くなります。
ただし、双方向のバインディングは動かないので、グリグリ動くものには使えないですが。
#参考ページ