I'm having trouble getting the CanExecute method of my command to work property. I've bound a command to a button that is inside of a DataGrid. I've bound the CommandParameter to the button's DataContext, which happens to be a record for a row in the DataGrid.
What I expect to happen is for the CanExecute method to be re-evaluated when the CommandParameter binding changes, which in this case would be the row's DataContext property being set. But instead of evaluating the CanExecute method against the row data, it looks like the CanExecute method is being evaluated before the row gets its DataContext and it is never re-evaluated after the DataContext has been updated.
Can you tell me how to get the CanExecute method of my command to be evaluated against each row's DataContext?
I've created a sample application to demonstrate my problem. Here's the code:
The code-behind for the MainWindow.xaml
public partial class MainWindow : Window
{
public ObservableCollection<LogRecord> Records { get; private set; }
public ICommand SignOutCommand { get; private set; }
public MainWindow()
{
InitializeComponent();
DataContext = this;
Records = new ObservableCollection<LogRecord>();
SignOutCommand = new SignOutCommand();
CreateDemoData();
}
private void CreateDemoData()
{
for (int i = 0; i < 5; i++)
{
Records.Add(new LogRecord());
}
}
}
public class LogRecord : INotifyPropertyChanged
{
private DateTime _EntryTime;
public DateTime EntryTime
{
get { return _EntryTime; }
set
{
if (_EntryTime == value) return;
_EntryTime = value;
RaisePropertyChanged("EntryTime");
}
}
private DateTime? _ExitTime;
public DateTime? ExitTime
{
get { return _ExitTime; }
set
{
if (_ExitTime == value) return;
_ExitTime = value;
RaisePropertyChanged("ExitTime");
}
}
public LogRecord()
{
EntryTime = DateTime.Now;
}
#region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
public class SignOutCommand : ICommand
{
#region Implementation of ICommand
public void Execute(object parameter)
{
var record = parameter as LogRecord;
if (record == null) return;
record.ExitTime = DateTime.Now;
}
public bool CanExecute(object parameter)
{
var record = parameter as LogRecord;
return record != null && !record.ExitTime.HasValue;
}
public event EventHandler CanExecuteChanged;
#endregion
}
The XAML for MainWindow.xaml
<Window x:Class="Command_Spike.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Width="525"
Height="350">
<DataGrid ItemsSource="{Binding Path=Records}" IsReadOnly="True" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Entry Time" Binding="{Binding Path=EntryTime}" />
<DataGridTextColumn Header="Exit Time" Binding="{Binding Path=ExitTime}" />
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=Window},
Path=DataContext.SignOutCommand}"
CommandParameter="{Binding}"
Content="Sign Out" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
If you load up the example code, you can see that all of the Sign Out buttons are disabled because in each row, the CanExecute method is receiving null as the parameter instead of the row-specific data that I want. If this sample were working properly, all of the buttons would be enabled initially and would only disable after a value in the Exit Time column was set.