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.
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
}
}
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.