RunningCSharp

MS系開発者による、雑多な記事。記事は所属企業とは関係のない、個人の見解です。

WPF:DataGridヘッダーのクリック時にViewModel側のICommandを実行するための添付プロパティ

stackoverflow.com

上記リンクでBlendのオブジェクトの拡張を作成してTriggerをStyle内に記述する方法での解決があります。 汎用性があるものの、多くのxamlを書く必要があるため、添付プロパティでの解決方法も一応記載してみようと思いました。

<Window x:Class="WpfApplication.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>
    <Grid>
        <DataGrid ItemsSource="{Binding Items}" />
    </Grid>
</Window>

Windowはこんな感じで、

namespace WpfApplication
{
    public class MainViewModel
    {
        public List<object> Items { get; } = new List<object>() { new { Item1 = 1, Item2 = "山田" }, new { Item1 = 2, Item2 = "田中" } };
    }
}

DataContextに入れるVMはこんな感じで。

f:id:ys-soniclab:20160902182112p:plain

この画面に、ヘッダークリックイベントを付けていきます。

namespace WpfApplication
{
    public class Attached
    {
        public static ICommand GetColumnHeaderClickCommand(DependencyObject obj)
        {
            return (ICommand)obj.GetValue(ColumnHeaderClickCommandProperty);
        }

        public static void SetColumnHeaderClickCommand(DependencyObject obj, ICommand value)
        {
            obj.SetValue(ColumnHeaderClickCommandProperty, value);
        }

        // Using a DependencyProperty as the backing store for ColumnHeaderClickCommand.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ColumnHeaderClickCommandProperty =
            DependencyProperty.RegisterAttached("ColumnHeaderClickCommand", typeof(ICommand), typeof(Attached), new PropertyMetadata(null, PropertyChanged));

        private static void PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var grid = d as DataGrid;
            grid.Loaded -= Grid_Loaded;
            if (e.NewValue != null)
                grid.Loaded += Grid_Loaded;
        }

        private static void Grid_Loaded(object sender, RoutedEventArgs e)
        {
            var grid = sender as DataGrid;
            grid.FindChildren<DataGridColumnHeader>().ToList().ForEach(col => col.Click += Col_Click);
        }

        private static void Col_Click(object sender, RoutedEventArgs e)
        {
            var header = sender as DataGridColumnHeader;
            var grid = header.FindParent<DataGrid>();
            GetColumnHeaderClickCommand(grid)?.Execute(null);
        }

    }

    public static class Extensions
    {
        public static T FindParent<T>(this DependencyObject child) where T : DependencyObject
        {
            DependencyObject parentObject = VisualTreeHelper.GetParent(child);
            if (parentObject == null) return null;
            T parent = parentObject as T;
            return parent != null ? parent : FindParent<T>(parentObject);
        }

        public static List<T> FindChildren<T>(this DependencyObject parent, List<T> children = null)
            where T : DependencyObject
        {
            if (children == null)
                children = new List<T>();

            if (parent != null)
            {
                int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
                for (int i = 0; i < childrenCount; i++)
                {
                    var child = VisualTreeHelper.GetChild(parent, i);
                    var typeChild = child as T;
                    if (typeChild != null)
                    {
                        children.Add(typeChild);
                    }
                    children = FindChildren<T>(child, children);
                }
            }
            return children;
        }

    }

}

上記のクラスを作成し、

<Window …><Grid>
        <DataGrid ItemsSource="{Binding Items}" local:Attached.ColumnHeaderClickCommand="{Binding HeaderClickCommand}"/>
    </Grid>
</Window>

上記のように添付プロパティをDataGridに追加し、

namespace WpfApplication
{
    public class MainViewModel
    {
…
        private RelayCommand _headerClickCommand;

        /// <summary>
        /// Gets the HeaderClickCommand.
        /// </summary>
        public RelayCommand HeaderClickCommand
        {
            get
            {
                return _headerClickCommand
                    ?? (_headerClickCommand = new RelayCommand(
                                          () =>
                                          {
                                              MessageBox.Show("header clicked");
                                          }));
            }
        }

    }
}

ViewModelに上記実装を加えると、クリック時にメッセージボックスが出るようになります。

f:id:ys-soniclab:20160902182131p:plain

xamlの変更は少ないのですが、添付プロパティがそこそこの大きさになってしまいました…