DataGrid 上のボタンに引数を設定する

MVVM パターンを用いて WPF の DataGrid のカラムに Button を設置して、ボタンをクリックするとボタンが設置されている行のデータを処理するサンプルコードです。実現方法はボタンにバインドしているコマンドにデータの ID 値を与える方式をとっています。別解として、DataGrid の SelectedItem からデータの ID を得る方法(この方法も後のほうで書いています)もありますが、こちらの方法のほうがより直感的でなおかつ確実な処理が期待できるかなと 😉 (確実な処理が期待できる理由については、後述の別解のところに書いてあります)

作成するのは、ボタンをクリックすると当該行のデータの Name プロパティの値を表示するプログラムです。画面例は次のとおり。

CmdInDataGrid01

それではプログラムです。WPF なプロジェクトを作成します。例ではプロジェクト名を CmdInDataGrid01 としています。

引数を扱うコマンドが必要になるので、先日書いた ViewModelBase をプロジェクトの参照に加えておきます(ViewModelBase のプロジェクトをビルドしておいてから、サンプルプログラムのプロジェクトのソリューション エクスプローラーから参照設定を右クリック、参照の追加を選択し、ビルドした ViewModelBase.dll を追加してください)。

最初にビューモデルから。
プロジェクトに ViewModels フォルダを作成し、当該フィルダに MainWindowViewModel クラスを作成します。

using System.Collections.Generic;
using System.Windows.Input;

using MakCraft.ViewModels;

namespace CmdInDataGrid.ViewModels
{
    class MainWindowViewModel : ViewModelBase
    {
        public MainWindowViewModel()
        {
            Samples = initDatas();
        }

        public List<Sample> Samples { get; set; }

        private List<Sample> initDatas()
        {
            var samples = new List<Sample> {
                new Sample { Id = 1, Name = "サンプル1"},
                new Sample { Id = 2, Name = "サンプル2"},
                new Sample { Id = 3, Name = "サンプル3"},
                new Sample { Id = 4, Name = "サンプル4"},
                new Sample { Id = 5, Name = "サンプル5"},
            };
            return samples;
        }

        private string _result;
        public string Result
        {
            get { return _result; }
            set
            {
                _result = value;
                base.RaisePropertyChanged(() => Result);
            }
        }

        private void selectNameExecute(int id)
        {
            Result = Samples.Find(m => m.Id == id).Name;
        }
        private RelayCommand<int> _selectNameCommand;
        public ICommand SelectNameCommand
        {
            get
            {
                if (_selectNameCommand == null)
                    _selectNameCommand = new RelayCommand<int>(selectNameExecute);
                return _selectNameCommand;
            }
        }
    }

    class Sample
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
}

次に、MainWindow.xaml です。


<Window x:Class="CmdInDataGrid.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="clr-namespace:CmdInDataGrid.ViewModels"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <vm:MainWindowViewModel />
    </Window.DataContext>
    
    <StackPanel>
        <TextBlock
            Text="DataGrid の中にボタンを設置してコマンドにパラメータを渡す"
            HorizontalAlignment="Center" Margin="8" FontSize="14" />

        <DataGrid
            ItemsSource="{Binding Samples}"
            Margin="4" IsReadOnly="True" AutoGenerateColumns="False">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding Id}" Header="ID" MinWidth="50">
                    <DataGridTextColumn.ElementStyle>
                        <Style TargetType="TextBlock">
                            <Setter Property="TextAlignment" Value="Right" />
                            <Setter Property="Padding" Value="8 0" />
                        </Style>
                    </DataGridTextColumn.ElementStyle>
                </DataGridTextColumn>
                <DataGridTemplateColumn Header="名称" MinWidth="200">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <Button
                                Content="{Binding Name}"
                                Command="{Binding RelativeSource={RelativeSource AncestorType=DataGrid},
                                    Path=DataContext.SelectNameCommand}"
                                CommandParameter="{Binding Id}" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>
        
        <StackPanel Orientation="Horizontal" Margin="8">
            <Label Content="選択名:" />
            <TextBox Text="{Binding Result}" IsReadOnly="True" MinWidth="100" />
        </StackPanel>
    </StackPanel>
</Window>

Button の Command のバインディングの設定のところがちょっと複雑なので、説明しておきます。
Button の DataContext は親の DatGrid の ItemsSource でバインドしている Samples(List<Sample> なオブジェクト MainWindowViewModel.Samples、MainWindowViewModel へのバインドは DataGrid.DataContext が Window.DataContext での設定値を引き継いでいる)になっています。このため、Content プロパティへのバインドは Name(Sample.Name)で行えますが、Command にバインドしたい MainWindowViewModel.SelectNameCommand は Sample クラスに存在しないため、通常の方法ではバインドできません。そこで RelativeSource を用いて、親要素のプロパティを指定する方法をとります。AncestorType=DataGrid で親要素の DataGrid オブジェクトを指定し、Path=DataContext.SelectNameCommand でバインドするプロパティを指定しています(親要素の DataGrid オブジェクトの DataContext プロパティにバインドされた MainWndowViewModel オブジェクトの SelectNameCommand プロパティ)。
また、CommandParameter で Sample.Id を SelectNameCommand のパラメータとして設定しています(結果として MainWindowViewModel.selectNameExecute(Sample オブジェクトの Id プロパティの値) が呼び出されます)。

以上でプログラムが動きます。

続いて、文頭で触れた、別解として、DataGrid の SelectedItem からデータの ID を得る方法です。

MainWindowViewModel クラスに次のプロパティとコマンドを追加します。

// DataGrid で選択されている項目
        private Sample _selectedSample;
        public Sample SelectedSample
        {
            get { return _selectedSample; }
            set
            {
                _selectedSample = value;
                base.RaisePropertyChanged(() => SelectedSample);
            }
        }

        private void selectNameExecute2()
        {
            Result = Samples.Find(m => m.Id == SelectedSample.Id).Name;
        }
        private ICommand _selectNameCommand2;
        public ICommand SelectNameCommand2
        {
            get
            {
                if (_selectNameCommand2 == null)
                    _selectNameCommand2 = new RelayCommand(selectNameExecute2);
                return _selectNameCommand2;
            }
        }

次に修正後の MainWindow.xaml です。

<Window x:Class="CmdInDataGrid.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="clr-namespace:CmdInDataGrid.ViewModels"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <vm:MainWindowViewModel />
    </Window.DataContext>
    
    <StackPanel>
        <TextBlock
            Text="DataGrid の中にボタンを設置してコマンドにパラメータを渡す"
            HorizontalAlignment="Center" Margin="8" FontSize="14" />

        <DataGrid
            ItemsSource="{Binding Samples}"
            Margin="4" IsReadOnly="True" AutoGenerateColumns="False" SelectedItem="{Binding SelectedSample}">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding Id}" Header="ID" MinWidth="50">
                    <DataGridTextColumn.ElementStyle>
                        <Style TargetType="TextBlock">
                            <Setter Property="TextAlignment" Value="Right" />
                            <Setter Property="Padding" Value="8 0" />
                        </Style>
                    </DataGridTextColumn.ElementStyle>
                </DataGridTextColumn>
                <DataGridTemplateColumn Header="名称" MinWidth="200">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <Button
                                Content="{Binding Name}"
                                Command="{Binding RelativeSource={RelativeSource AncestorType=DataGrid},
                                    Path=DataContext.SelectNameCommand2}" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>
        
        <StackPanel Orientation="Horizontal" Margin="8">
            <Label Content="選択名:" />
            <TextBox Text="{Binding Result}" IsReadOnly="True" MinWidth="100" />
        </StackPanel>
    </StackPanel>
</Window>

DataGrid の SelectedItem のバインドと Button の Command のバインドを修正し、Button の CommandParameter を削除しています。

このコードが期待どおりに動くには、Button コントロールの Command のキックの前に DataGrid のSelectedItem 変更のバインディング ソースへの反映が行われていることが必要になります(DataGrid のカラム上に配置された Button コントロールに対するユーザーのクリック操作により DataGrid の SelectedItem が変更されるため)。現時点ではこの順番で処理されていますが、保証された動作かどうかは定かではありません(おそらく処理系の実装依存じゃないかなぁと)。この点から、先に書いたコマンドに引数を渡す方式のほうが確実な処理が期待できると思われます。


コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です