1
votes

WPF

MVVM

RelayCommands

DataGrid with multiple buttons on each row.

Goal: To enable and disable some of the buttons for the selected row only, when I click on my "Start" button

Current Behavior: When I click the Start button, the buttons get enabled and disabled the same for all rows

In this image you can see that I clicked on the Start button and the command fired and correctly set the enabled/disabled state of the other buttons however it applied it to all rows.

I've been stuck trying to get it to apply for the selected item only. I'm currently trying to pass a parameter to the RelayCommand, if anyone could show me how to do that maybe it would work.

enter image description here

XAML

<DataGrid x:Name="dataGridThreadView" Grid.Row="1" Grid.Column="0" AutoGenerateColumns="False" 
                  IsReadOnly="True" CanUserResizeRows="False" CanUserReorderColumns="True" Margin="4"  
                  CanUserAddRows="False" CanUserDeleteRows="False" CanUserSortColumns="True" 
                  EnableRowVirtualization="True" ItemsSource="{Binding Threads}"
                  SelectedItem="{Binding Path=SelectedThread, Mode=TwoWay}">

            <DataGrid.Columns>
                <DataGridTextColumn Header="Name"
                        Binding="{Binding Thread.Name}"/>
                <DataGridTemplateColumn Header="Actions">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <StackPanel Orientation="Horizontal" Margin="2">
                                <Button Name="buttonStartThread" Content="Start" Command="{Binding ElementName=dataGridThreadView,  Path=DataContext.StartCommand}" CommandParameter="{Binding SelectedThread}"/>
                                <Button Name="buttonSuspendThread" Content="Suspend" Command="{Binding ElementName=dataGridThreadView,  Path=DataContext.SuspendCommand}"/>
                                <Button Name="buttonResumeThread" Content="Resume" Command="{Binding ElementName=dataGridThreadView,  Path=DataContext.ResumeCommand}"/>
                                <Button Name="buttonInterruptThread" Content="Interrupt" Command="{Binding ElementName=dataGridThreadView,  Path=DataContext.InterruptCommand}"/>
                                <Button Name="buttonAbortThread" Content="Abort" Command="{Binding ElementName=dataGridThreadView,  Path=DataContext.AbortCommand}"/>
                            </StackPanel>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>

ViewModel

 public class ThreadViewModel : PropertyChangedBase
{
    public ThreadModel SelectedThread { get; set; }
    public ObservableCollection<ThreadModel> Threads { get; set; }
    private bool _disableStartButton = false;
    private bool _disableSuspendButton = false;
    private bool _disableResumeButton = false;
    private bool _disableInterruptButton = false;
    private bool _disableAbortButton = false;

    public RelayCommand StartCommand { get; private set; } // should only be set once in vm during construction

    public ThreadViewModel()
    {
        _disableStartButton = false;
        _disableSuspendButton = true;
        _disableResumeButton = true;
        _disableInterruptButton = true;
        _disableAbortButton = true;
        Threads = new ObservableCollection<ThreadModel>();

        StartCommand = new RelayCommand(OnStart, CanStart);
    }

    private void OnStart(object template)
    {
        this._disableStartButton = true;
        StartCommand.RaiseCanExecuteChanged();
        _disableSuspendButton = false;
        SuspendCommand.RaiseCanExecuteChanged();
        _disableInterruptButton = false;
        InterruptCommand.RaiseCanExecuteChanged();
        _disableAbortButton = false;
        AbortCommand.RaiseCanExecuteChanged();
    }

    private bool CanStart()
    {
        return !this._disableStartButton;
    }
}

RelayCommand class

using System;
using System.Windows.Input;

namespace Multthreading
{
public class RelayCommand : ICommand
{
    Action _TargetExecuteMethod;
    Func<bool> _TargetCanExecuteMethod;

    public RelayCommand(Action executeMethod)
    {
        _TargetExecuteMethod = executeMethod;
    }

    public RelayCommand(Action executeMethod, Func<bool> canExecuteMethod)
    {
        _TargetExecuteMethod = executeMethod;
        _TargetCanExecuteMethod = canExecuteMethod;
    }

    public void RaiseCanExecuteChanged()
    {
        CanExecuteChanged(this, EventArgs.Empty);
    }
    #region ICommand Members

    bool ICommand.CanExecute(object parameter)
    {
        if (_TargetCanExecuteMethod != null)
        {
            return _TargetCanExecuteMethod();
        }
        if (_TargetExecuteMethod != null)
        {
            return true;
        }
        return false;
    }

    // Beware - should use weak references if command instance lifetime is longer than lifetime of UI objects that get hooked up to command
    // Prism commands solve this in their implementation
    public event EventHandler CanExecuteChanged = delegate { };

    void ICommand.Execute(object parameter)
    {
        if (_TargetExecuteMethod != null)
        {
            _TargetExecuteMethod();
        }
    }
    #endregion
}

public class RelayCommand<T> : ICommand
{
    Action<T> _TargetExecuteMethod;
    Func<T, bool> _TargetCanExecuteMethod;

    public RelayCommand(Action<T> executeMethod)
    {
        _TargetExecuteMethod = executeMethod;
    }

    public RelayCommand(Action<T> executeMethod, Func<T,bool> canExecuteMethod)
    {
        _TargetExecuteMethod = executeMethod;
        _TargetCanExecuteMethod = canExecuteMethod;
    }

    public void RaiseCanExecuteChanged() 
    {
         CanExecuteChanged(this, EventArgs.Empty); 
    }
    #region ICommand Members

    bool ICommand.CanExecute(object parameter)
    {
        if (_TargetCanExecuteMethod != null)
        {
            T tparm = (T)parameter;
            return _TargetCanExecuteMethod(tparm);
        }
        if (_TargetExecuteMethod != null)
        {
            return true;
        }
        return false;
    }

    // Beware - should use weak references if command instance lifetime is longer than lifetime of UI objects that get hooked up to command
    // Prism commands solve this in their implementation
    public event EventHandler CanExecuteChanged = delegate { };

    void ICommand.Execute(object parameter)
    {
        if (_TargetExecuteMethod != null)
        {
            _TargetExecuteMethod((T)parameter);
        }
    }
    #endregion
    }
}
1
Have you tried using bindings? You'll have to provide extra properties in the object's model for it though. Each row is bound to a different item, so each item can have their individual properties bound to those of the buttons. This way you can just edit the properties of your selected item and this should automatically change that of the corresponding buttons. - Oceans
Thanks for your suggestion, I tried using bindings and it works. I just removed the Can methods out of the RelayCommand, and made them standard functions in the Model. I binded these then to the IsEnabled property of the multiple buttons per row of the DataGrid. I didn't have to remove the RelayCommands for the buttons, I just removed the Can functions. If you post your suggestion as a response I will mark it as the answer. - user2113566
Will do, cheers - Oceans
i think you picked up my mistake, when I said "I just removed the Can methods out of the RelayCommand, and made them standard functions in the Model." should have been "I made them booleans in the model (with get and set)" - user2113566

1 Answers

0
votes

A possible solution is the use of Bindings. Each row is bound to a different item, so each item can have their individual properties bound to those of the buttons.

You'll have to provide extra properties in the object's model. You can bind simple booleans to the IsEnabled property of the buttons. Put the default value on false. This way you can just edit the properties of your selected item and this should automatically change that of the corresponding buttons.