从 WPF DataGrid 内的按钮触发 RelayCommand

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12179701/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 23:30:24  来源:igfitidea点击:

Firing a RelayCommand from a button inside a WPF DataGrid

wpfmvvmcommand

提问by Adrian Hand

I have a DataGridshowing a list of customers defined in XAML as follows bound to my ViewModel:

我有一个DataGrid显示在 XAML 中定义的客户列表,如下绑定到我的 ViewModel:

<DataGrid Name="CustomersDataGrid" ItemsSource="{Binding Customers}">
    <DataGrid.Columns>
        <DataGridTemplateColumn>
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <Button Command="{Binding showCustomerCommand}" 
                        Content="{Binding Path=Name}"/>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

The display of the grid works fine. I want to be able to display the particulars of an individual customer. Previously, I had setup a binding for the selected row, and had a button on the page which was bound to the following command:

网格的显示工作正常。我希望能够显示单个客户的详细信息。以前,我为所选行设置了一个绑定,并在页面上有一个绑定到以下命令的按钮:

RelayCommand _showCustomerCommand;
public ICommand showCustomerCommand
{
    get
    {
        if (_showCustomerCommand == null)
        {
            _showCustomerCommand = new RelayCommand(param => this.ShowCustomer());
        }
        return _showCustomerCommand;
    }
}

private void ShowCustomer()
{
    if (Parent != null)
    {
        // Handle Customer Display Here
    }
}

This worked fine. But I want to be able to click a button inside the individual row, rather than the single button based on selected row. I know the datacontext is wrong in the above XAML, but I don't know how to correct it, nor how to pass out the specific row from which the button press originated. Any and all suggestions to help me wire up my embedded button are gratefully received!

这工作得很好。但我希望能够单击单个行内的按钮,而不是基于所选行的单个按钮。我知道上面 XAML 中的数据上下文是错误的,但我不知道如何更正它,也不知道如何传递按钮按下的特定行。感谢所有帮助我连接嵌入式按钮的建议!

回答by Jithesh kumar

This will help you

这会帮助你

<DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <StackPanel>
                            <Button 
                          Command="{Binding Path=DataContext.showCustomerCommand,       
 RelativeSource= {RelativeSource FindAncestor,
  AncestorType={x:Type DataGrid}}}">
                            </Button>
                        </StackPanel>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>

回答by Jakub Kaleta

Thisquestion/answer looks similar to what you're looking for.

这个问题/答案看起来与您正在寻找的相似。

You can bind the ID of the row to the command parameter

您可以将行的 ID 绑定到命令参数

<Button Click="Button_Click" Command="{Binding showCustomerCommand}" 
    CommandParameter="{Binding Path=ID}">View Details</Button>

回答by Ryan The Leach

The Resharper/Intellisense inference is incorrect inside of DataGridTemplate.

Resharper/Intellisense 推断在DataGridTemplate.

This can cause confusion, In order to correct that, the DataTemplate should have an explicit DataType referencing the type that the row is being bound to.

这可能会导致混淆,为了解决这个问题,DataTemplate 应该有一个显式的 DataType,引用行绑定到的类型。

There are 2 ways I found of solving your question, Using a Relative Binding for the Command , so that it binds using the Context of the DataGrid, not the current row, Or creating an x:Name attribute on an element that has the context you wish to bind to.

我发现有两种方法可以解决您的问题,为 Command 使用相对绑定,以便它使用 DataGrid 的上下文而不是当前行进行绑定,或者在具有您的上下文的元素上创建 x:Name 属性想绑定。

It's my belief that the relative binding is cleaner if you are trying to access an ancestor, but if you need to bind to something elsewhere in the tree, then maybe ElementName will allow that.

我相信如果您尝试访问祖先,则相对绑定会更清晰,但是如果您需要绑定到树中其他地方的某些内容,那么 ElementName 可能会允许这样做。

XAML:

XAML:

<Window x:Class="TestBindings.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:TestBindings"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <local:WindowViewModel/>
    </Window.DataContext>
    <Grid ShowGridLines="True">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*"/>
            <ColumnDefinition Width="1*"/>
        </Grid.ColumnDefinitions>

        <GroupBox x:Name="Test"  Header="Grid" Grid.Column="0"> <!-- #2 Add a name to the element you need to bind to. -->
            <DataGrid ItemsSource="{Binding RowList}" IsSynchronizedWithCurrentItem="True" SelectionUnit="FullRow" >
                <DataGrid.Columns>
                    <DataGridTemplateColumn>
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate DataType="local:RowViewModel"><!-- Needed for correct inference in XAML editor -->
                                <!-- #1 Cleaner solution as it doesn't depend on names -->
                                <Button Content="{Binding Path=RowCount}" Command="{Binding Path=DataContext.NineCommand, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" CommandParameter="{Binding}" />
                                <!-- #2 Naming the element explicitly probably allows you to jump elsewhere in the tree -->
                                <Button Content="{Binding Path=RowCount}" Command="{Binding Path=DataContext.NineCommand, ElementName=Test}" CommandParameter="{Binding}" />

                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                </DataGrid.Columns>
            </DataGrid>
        </GroupBox>
        <GroupBox Header="{Binding Path=SelectedRow.RowCount}" Grid.Column="1">
            <Label Content="{Binding Path=RowList/RowCount}"></Label>
        </GroupBox>
    </Grid>
</Window>

Class:

班级:

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Data;
using System.Windows.Input;
using Microsoft.Expression.Interactivity.Core;
using Microsoft.Practices.Prism.ViewModel;

namespace TestBindings
{
    public class RowViewModel : NotificationObject
    {
        private static int _max = 0;
        private int _rowCount;

        public int RowCount
        {
            get { return _rowCount; }
            set
            {
                if (_rowCount == value) return;
                _rowCount = value;
                RaisePropertyChanged(nameof(RowCount));
            }
        }

        public RowViewModel()
        {
            _rowCount = _max++;
        }
    }

    public class TypedCommand<T> : ICommand
    {
        private readonly Predicate<T> _canExecute;
        private readonly Action<T> _execute;

        public TypedCommand(Action<T> execute, Predicate<T> canExecute = null)
        {
            this._canExecute = canExecute;
            this._execute = execute;
        }

        public bool CanExecute(object parameter)
        {
            return _canExecute == null || _canExecute((T) parameter);
        }

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

        public event EventHandler CanExecuteChanged;
    }

    public class WindowViewModel : NotificationObject
    {
        public ObservableCollection<RowViewModel> RowList { get; } = new ObservableCollection<RowViewModel>();

        private RowViewModel _selectedRow;

        public RowViewModel SelectedRow
        {
            get { return _selectedRow; }
            private set
            {
                if (_selectedRow == value) return;
                _selectedRow = value;
                RaisePropertyChanged(nameof(SelectedRow));
            }
        }

        private static readonly Action<RowViewModel> DeleteAction = new Action<RowViewModel>(row => row.RowCount=99);

        public ICommand NineCommand { get; } = new TypedCommand<RowViewModel>(DeleteAction);

        public WindowViewModel()
        {
            //0
            RowList.Add(new RowViewModel());
            //1
            RowList.Add(new RowViewModel());
            //2
            RowList.Add(new RowViewModel());
        }
    }
}