やる気駆動型エンジニアの備忘録

WPF(XAML+C#)の話題を中心に.NET/Android/CI やたまに趣味に関するブログです

DataGridColumnにデータバインドするには

業務アプリを制作する際にDataGridのある列の表示非表示を切り替えたいケースがあると思います。
表示だけでなく、ヘッダーや幅を変えたいなどの場合も同様です。
しかし、少し手を加えなければDataGridColumn のバインディングは不可能です。
(なぜかは後述します。)

このような場合、上記の仕組みを実現するには主に以下の2通りがあります。

1.画面(View)側でDataGridColumn コントロールにアクセス
2.ViewModel から表示プロパティをバインド

1の場合は対応する画面が一つだけなら問題無いでしょう。
しかし、画面が複数あったりした場合はどうでしょう?
いやいやWPFらしくデータバインドを使いたい、と思う方もいらっしゃるでしょう。
ということで、今回はケース2.の方法を実現しようと思います。


■参考資料
http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/

DataGridColumn には間をとりもつ存在が必要

参考資料より引用

I don’t know the exact mechanism that enables this behavior, but we’re going to take advantage of it to make our binding work…


オチから説明すると、なぜDataGridColumn とデータバインドできない原因は明確にはわかっていません。
上記Blogを読むに、論理ツリー内にDataGridColumn が含まれていないからでは?ということみたいです。
DataGridColumn を論理ツリー内に含むにはFreezable オブジェクトとバインドするのだそうです。

以下は、ViewModel の間をとりもつBindingProxy クラスの実装です。

< BindingProxy.cs >

 using System.Windows;

namespace DataGridColumnBinding
{
    /// <summary>
    /// ViewModelのバインディングソースの代理として働くクラスです。
    /// </summary>
    public class BindingProxy : Freezable
    {
        /// <summary>
        /// Freezableオブジェクトのインスタンスを生成します。
        /// </summary>
        /// <returns></returns>
        protected override Freezable CreateInstanceCore()
        {
            return new BindingProxy();
        }
        
        /// <summary>
        /// 間をとりもつプロパティ
        /// データバインドした場合は、このプロパティがViewModelの代わりになる。
        /// </summary>
        public object Data
        {
            get { return (object) GetValue(DataProperty); }
            set { SetValue(DataProperty, value); }
        }
        
        /// <summary>
        /// Data の依存関係プロパティ定義
        /// </summary>
        public static readonly DependencyProperty DataProperty =
            DependencyProperty.Register("Data", typeof (object), typeof (BindingProxy), new UIPropertyMetadata(null));
    }
}


これに対し、Xaml とViewModel の定義は次のようになります。< MainWindow.xaml >

<Window x:Class="DataGridColumnBinding.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="clr-namespace:DataGridColumnBinding"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="MainWindow"
        Width="525"
        Height="350"
        Loaded="MainWindow_OnLoaded"
        d:DataContext="{d:DesignInstance {x:Type local:MainWindowViewModel}, IsDesignTimeCreatable=True}"
        mc:Ignorable="d">
    <Window.Resources>
        <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <StackPanel Margin="12" Orientation="Horizontal">
            <CheckBox Content="VisibleDate" IsChecked="{Binding IsVisibleDate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
            <CheckBox Content="VisibleTime" IsChecked="{Binding IsVisibleTime, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
        </StackPanel>
        <Border Grid.Row="1">
            <DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Times, Mode=OneWay}">
                <DataGrid.Resources>
                    <!--
                        BindingProxy のリソースを定義
                        Data には、MainWindowViewModel がバインドされる。
                    -->
                    <local:BindingProxy x:Key="Proxy" Data="{Binding}" />
                </DataGrid.Resources>
                <DataGrid.Columns>
                    <DataGridTextColumn Binding="{Binding Today, Mode=OneWay, StringFormat=\{0:d\}}"
                                        Header="Date"
                                        Visibility="{Binding Data.IsVisibleDate, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay, Source={StaticResource Proxy}}" />
                    <DataGridTextColumn Binding="{Binding Mode=OneWay, StringFormat=\{0:hh:mm:ss\}}"
                                        Header="Time"
                                        Visibility="{Binding Data.IsVisibleTime, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay, Source={StaticResource Proxy}}" />
                    <DataGridTextColumn Binding="{Binding Mode=OneWay, StringFormat=\{0:G\}}" Header="DateTime" />
                </DataGrid.Columns>
            </DataGrid>
        </Border>
    </Grid>
</Window>

< MainWindowViewModel.cs >

 using System;
using System.Collections.ObjectModel;
using System.Linq;
using Microsoft.Practices.Prism.Mvvm;

namespace DataGridColumnBinding
{
    public class MainWindowViewModel : BindableBase
    {
        public MainWindowViewModel()
        {
            IsVisibleDate = true;
            IsVisibleTime = true;

            Times = new ObservableCollection<DateTime>();

            foreach (var value in Enumerable.Range(0, 20))
            {
                Times.Add(DateTime.Now.AddHours(value));
            }
        }

        /// <summary>
        /// 日付を表示するかどうか
        /// </summary>
        private bool _isVisibleDate;

        /// <summary>
        /// 日付を表示するかどうか
        /// </summary>
        public bool IsVisibleDate
        {
            get { return _isVisibleDate; }
            set { SetProperty(ref _isVisibleDate, value); }
        }

        /// <summary>
        /// 時刻を表示するかどうか
        /// </summary>
        private bool _isVisibleTime;

        /// <summary>
        /// 時刻を表示するかどうか
        /// </summary>
        public bool IsVisibleTime
        {
            get { return _isVisibleTime; }
            set { SetProperty(ref _isVisibleTime, value); }
        }

        public ObservableCollection<DateTime> Times { get; private set; }
    }
}


ポイントは以下の2箇所
1.BindingProxy リソースの定義

<local:BindingProxy x:Key="Proxy" Data="{Binding}" />


2.データバインドの定義

<DataGridTextColumn Binding="{Binding Today, Mode=OneWay, StringFormat=\{0:d\}}"
                    Header="Date"
                    Visibility="{Binding Data.IsVisibleDate, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay, Source={StaticResource Proxy}}" />

Visibility の設定を上記のように定義することでデータバインドが可能になります。
画面を表示すると以下のようになります。

f:id:iyemon018:20160130234226p:plain

f:id:iyemon018:20160130234229p:plain

f:id:iyemon018:20160130234230p:plain



このようにDataGridColumn のプロパティもデータバインドが可能になっています。
今回のサンプルソースは以下から↓↓↓
github.com