当DataContext更改时,WPF命令CanExecute不会被重新评估

时间:2012-06-29 19:40:18

标签: c# wpf xaml

我无法获取命令的CanExecute方法来处理属性。我已将命令绑定到DataGrid内的按钮。我已将CommandParameter绑定到按钮的DataContext,它恰好是DataGrid中一行的记录。

我期望发生的是在CommandParameter绑定发生更改时重新评估CanExecute方法,在这种情况下,将设置行的DataContext属性。但是,不是针对行数据评估CanExecute方法,而是在行获取其DataContext之前评估CanExecute方法,并且在更新DataContext之后永远不会重新评估它。

你能告诉我如何根据每一行的DataContext来评估我的命令的CanExecute方法吗?

我已经创建了一个示例应用程序来演示我的问题。这是代码:

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
}

MainWindow.xaml的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>

如果加载示例代码,您可以看到所有注销按钮都被禁用,因为在每一行中,CanExecute方法接收null作为参数而不是我想要的行特定数据。如果此示例工作正常,则最初将启用所有按钮,并且仅在设置“退出时间”列中的值后禁用。

1 个答案:

答案 0 :(得分:2)

您没有正确设置自定义命令。在当前示例中,您不需要手动创建实现ICommand的命令,只需创建Routed或RoutedUI命令并连接相应的处理程序即可。删除SignOutCommand对象,然后修改您的Window代码,如下所示:

public partial class MainWindow: Window
{
    public ObservableCollection<LogRecord> Records { get; private set; }
    public static RoutedUICommand SignOutCommand { get; private set; }

    public MainWindow()
    {
        InitializeComponent();
        DataContext = this;
        Records = new ObservableCollection<LogRecord>();
        CreateDemoData();

        SignOutCommand = new RoutedUICommand();
        CommandBinding cb = new CommandBinding(SignOutCommand, OnSignOut, OnCanSignOut);
        this.CommandBindings.Add(cb);
    }


    private void CreateDemoData()
    {
        for (int i = 0; i < 5; i++)
        {
            Records.Add(new LogRecord());
        }
    }

    private void OnCanSignOut(object sender, CanExecuteRoutedEventArgs e)
    {
        var record = e.Parameter as LogRecord;
        e.CanExecute = record != null && !record.ExitTime.HasValue;

    }

    private void OnSignOut(object sender, ExecutedRoutedEventArgs e)
    {
        var record = e.Parameter as LogRecord;
        if (record == null) return;
        record.ExitTime = DateTime.Now;
    }
}

然后,修改你的DataTemplate如下(基本上,只需从Path中删除DataContext):

<dg:DataGridTemplateColumn>
  <dg:DataGridTemplateColumn.CellTemplate>
    <DataTemplate>
       <Button Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=SignOutCommand}" CommandParameter="{Binding}" Content="Sign Out" />
    </DataTemplate>
  </dg:DataGridTemplateColumn.CellTemplate>
</dg:DataGridTemplateColumn>

使用此方法,在设置DataContext时,您的注销按钮将正确启用。