Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Firing a RelayCommand from a button inside a WPF DataGrid

Tags:

command

mvvm

wpf

I have a DataGrid showing a list of customers defined in XAML as follows bound to my 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!

like image 740
Adrian Hand Avatar asked Aug 29 '12 13:08

Adrian Hand


3 Answers

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>
like image 147
Jithesh kumar Avatar answered Nov 18 '22 00:11

Jithesh kumar


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

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

<Button Click="Button_Click" Command="{Binding showCustomerCommand}" 
    CommandParameter="{Binding Path=ID}">View Details</Button>
like image 7
Jakub Kaleta Avatar answered Nov 17 '22 22:11

Jakub Kaleta


The Resharper/Intellisense inference is incorrect inside of 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.

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.

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.

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());
        }
    }
}
like image 4
Ryan Leach Avatar answered Nov 18 '22 00:11

Ryan Leach