DataGrid内のボタンを親のクラスで受け取る

2020-03-25

下記ソースの場合、ボタンを押下すると、行ごとのクラス内のイベントが発生してしまう。

MainView.xaml

<Window x:Class="WpfApp6.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:WpfApp6"
        xmlns:vm="clr-namespace:WpfApp6.ViewModel"
        mc:Ignorable="d"
        Title="MainWindow" Height="200" Width="300">

    <Window.DataContext>
        <vm:MainViewModel />
    </Window.DataContext>

    <Grid>
        <DataGrid Margin="10" ItemsSource="{Binding Path=ArrayData}" AutoGenerateColumns="False" SelectionMode="Single" SelectionUnit="FullRow">
            <DataGrid.CellStyle>
                <Style TargetType="DataGridCell" BasedOn="{StaticResource {x:Type DataGridCell}}">
                    <Setter Property="BorderThickness" Value="0" />
                </Style>
            </DataGrid.CellStyle>
            <DataGrid.Columns>
                <DataGridTextColumn Header="Index" Binding="{Binding Path=Index}" />

                <DataGridTemplateColumn Header="名前">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <Button Content="{Binding Path=Name}" Command="{Binding Path=ClickItem}" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
                
                <DataGridTextColumn Header="値段" Binding="{Binding Path=Price}" />
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

MainViewModel.cs

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Input;

namespace WpfApp6.ViewModel
{
    public class MainViewModel : INotifyPropertyChangedBase
    {
        private ObservableCollection<ItemPriceData> _ArrayData = new ObservableCollection<ItemPriceData>();

        public ObservableCollection<ItemPriceData> ArrayData
        {
            get
            {
                return _ArrayData;
            }
            set
            {
                _ArrayData = value;
                OnPropertyChanged();
            }
        }

        private ICommand _ClickItem;
        public ICommand ClickItem
        {
            get
            {
                return _ClickItem ?? (_ClickItem = new RelayCommand(() => { MessageBox.Show("MainViewModel内のClickItem"); }));
            }
        }

        public MainViewModel()
        {
            ArrayData.Add(new ItemPriceData(0, "りんご", 150));
            ArrayData.Add(new ItemPriceData(1, "みかん", 200));
            ArrayData.Add(new ItemPriceData(2, "バナナ", 380));
        }
    }

    public class ItemPriceData : INotifyPropertyChangedBase
    {
        private int _Index;

        public int Index
        {
            get
            {
                return _Index;
            }
            set
            {
                _Index = value;
                OnPropertyChanged();
            }
        }

        private string _Name;

        public string Name
        {
            get
            {
                return _Name;
            }
            set
            {
                _Name = value;
                OnPropertyChanged();
            }
        }

        private int _Price;

        public int Price
        {
            get
            {
                return _Price;
            }
            set
            {
                _Price = value;
                OnPropertyChanged();
            }
        }

        public ItemPriceData(int intIndex, string strName, int intPrice)
        {
            Index = intIndex;
            Name = strName;
            Price = intPrice;
        }

        private ICommand _ClickItem;
        public ICommand ClickItem
        {
            get
            {
                return _ClickItem ?? (_ClickItem = new RelayCommand(() => { MessageBox.Show("ItemPriceData内のClickItem"); }));
            }
        }
    }

    public class INotifyPropertyChangedBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public class RelayCommand : ICommand
    {
        private Action _action;

        public RelayCommand(Action action)
        {
            _action = action;
        }

        public event EventHandler CanExecuteChanged;

        public bool CanExecute(object parameter)
        {
            return _action != null;
        }

        public void Execute(object parameter)
        {
            _action?.Invoke();
        }
    }
}

ボタンのCommandのバインドを下記のように修正することで、
親で受け取ることができる。

<Button Content="{Binding Path=Name}" Command="{Binding Path=DataContext.ClickItem, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}" />

また、ボタンにCommandParameterを設定し、RelayCommandを引数対応に修正することで、
どのボタンが押されたかを親で取得することができる。

MainView.xaml

<Window x:Class="WpfApp6.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:WpfApp6"
        xmlns:vm="clr-namespace:WpfApp6.ViewModel"
        mc:Ignorable="d"
        Title="MainWindow" Height="200" Width="300">

    <Window.DataContext>
        <vm:MainViewModel />
    </Window.DataContext>

    <Grid>
        <DataGrid Margin="10" ItemsSource="{Binding Path=ArrayData}" AutoGenerateColumns="False" SelectionMode="Single" SelectionUnit="FullRow">
            <DataGrid.CellStyle>
                <Style TargetType="DataGridCell" BasedOn="{StaticResource {x:Type DataGridCell}}">
                    <Setter Property="BorderThickness" Value="0" />
                </Style>
            </DataGrid.CellStyle>
            <DataGrid.Columns>
                <DataGridTextColumn Header="Index" Binding="{Binding Path=Index}" />

                <DataGridTemplateColumn Header="名前">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <Button Content="{Binding Path=Name}" Command="{Binding Path=DataContext.ClickItem, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}" CommandParameter="{Binding Path=Index}" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
                
                <DataGridTextColumn Header="値段" Binding="{Binding Path=Price}" />
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

MainViewModel

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Input;

namespace WpfApp6.ViewModel
{
    public class MainViewModel : INotifyPropertyChangedBase
    {
        private ObservableCollection<ItemPriceData> _ArrayData = new ObservableCollection<ItemPriceData>();

        public ObservableCollection<ItemPriceData> ArrayData
        {
            get
            {
                return _ArrayData;
            }
            set
            {
                _ArrayData = value;
                OnPropertyChanged();
            }
        }

        private ICommand _ClickItem;
        public ICommand ClickItem
        {
            get
            {
                return _ClickItem ?? (_ClickItem = new RelayCommand((object param) => 
                {
                    MessageBox.Show(_ArrayData.First(t => t.Index == (int)param).Name);
                }));
            }
        }

        public MainViewModel()
        {
            ArrayData.Add(new ItemPriceData(0, "りんご", 150));
            ArrayData.Add(new ItemPriceData(1, "みかん", 200));
            ArrayData.Add(new ItemPriceData(2, "バナナ", 380));
        }
    }

    public class ItemPriceData : INotifyPropertyChangedBase
    {
        private int _Index;

        public int Index
        {
            get
            {
                return _Index;
            }
            set
            {
                _Index = value;
                OnPropertyChanged();
            }
        }

        private string _Name;

        public string Name
        {
            get
            {
                return _Name;
            }
            set
            {
                _Name = value;
                OnPropertyChanged();
            }
        }

        private int _Price;

        public int Price
        {
            get
            {
                return _Price;
            }
            set
            {
                _Price = value;
                OnPropertyChanged();
            }
        }

        public ItemPriceData(int intIndex, string strName, int intPrice)
        {
            Index = intIndex;
            Name = strName;
            Price = intPrice;
        }

        private ICommand _ClickItem;
        public ICommand ClickItem
        {
            get
            {
                return _ClickItem ?? (_ClickItem = new RelayCommand((object param) => { MessageBox.Show("ItemPriceData内のClickItem"); }));
            }
        }
    }

    public class INotifyPropertyChangedBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public class RelayCommand : ICommand
    {
        private Action<object> _action;

        public RelayCommand(Action<object> action)
        {
            _action = action;
        }

        public event EventHandler CanExecuteChanged;

        public bool CanExecute(object parameter)
        {
            return _action != null;
        }

        public void Execute(object parameter)
        {
            _action?.Invoke(parameter);
        }
    }
}