奇怪的行为与按钮,命令和canExecute

本文关键字:命令 canExecute 按钮 | 更新日期: 2023-09-27 18:02:04

我有一个简单的按钮:

<Button Content="Print" Command="{Binding PrintCommand}"/>

…使用命令:

private RelayCommand _printCommand;
public ICommand PrintCommand
{
    get
    {
        if (_printCommand == null)
        {
            _printCommand = new RelayCommand(param => Print(),
                                             () => (Files != null && Files.Count > 0));
        }
        return _printCommand;
    }
}

仅当Files集合不为空或包含某些项时才启用。以下是集合:

private ObservableCollection<RecordModel> _files;
public ObservableCollection<RecordModel> Files
{
    get { return _files; }
    set
    {
        if (_files == value)
        {
            return;
        }
        _files = value;
        OnPropertyChanged("Files");
    }
}

集合被绑定到窗口中的ListView。到目前为止,没什么特别的……这就是奇怪行为的由来……

如果集合中有足够的物品可以显示ListViewScrollBar,那么我的按钮显示为Enabled,这很好。如果我没有物品,那么它是Disabled,这也很好。但是,如果我有足够的项目来填充可见ListView的一部分,而不触发ScrollBar的出现,那么我的按钮显示为Disabled。如果我专注于任何控件,包括按钮本身,那么它会弹出Enabled。我不知道发生了什么。一开始,我以为这可能是我正在使用的按钮模板,所以我去掉了它,并保留了默认设置的按钮,但奇怪的行为仍然存在。

知道是怎么回事吗?

这是RelayCommand类。我不确定是否有问题,但这就是我已经使用了一段时间:

public class RelayCommand : ICommand
{
    readonly Action<object> _execute; 
    readonly Func<bool> _canExecute; 
    public RelayCommand(Action<object> execute) : this(execute, null) { } 
    public RelayCommand(Action<object> execute, Func<bool> canExecute) 
    {
        if (execute == null)
        {
            throw new ArgumentNullException("execute");
        }
        _execute = execute; 
        _canExecute = canExecute; 
    } 
    public bool CanExecute(object parameter) 
    { 
        return _canExecute == null ? true : _canExecute(); 
    } 
    public event EventHandler CanExecuteChanged 
    { 
        add 
        { 
            CommandManager.RequerySuggested += value; 
        } 
        remove 
        { 
            CommandManager.RequerySuggested -= value; 
        } 
    } 
    public void Execute(object parameter) 
    { 
        _execute(parameter); 
    } 
}
编辑:

这是我如何填充我的集合。

public FileManagerViewModel()
{
    LoadCollection();
}
private void LoadCollection()
{
    Task task = new Task(() =>
    {
        Files = DbWorker.GetFiles();
    });
    task.Start();
}
下面是我如何将集合绑定到ListView:
<Window.DataContext>
    <vm:FileManagerViewModel/>
</Window.DataContext>
<Window.Resources>
    <CollectionViewSource Source="{Binding Files}" x:Key="GroupedFiles">
        <CollectionViewSource.GroupDescriptions>
            <PropertyGroupDescription PropertyName="RepNum"/>
        </CollectionViewSource.GroupDescriptions>
    </CollectionViewSource>
</Window.Resources>
<ListView ItemsSource="{Binding Source={StaticResource GroupedFiles}}">
    ...
</ListView
编辑:

嗯,我不知道这是不是它或者它是如何导致的(特别是整个ScrollBar的情况),但是当我不使用Task来更新我的集合时,我没有经历过这种行为。当然,我还得处理由于长时间手术而导致的绞刑。不知道如何补救这一点,考虑到我不想阻塞UI线程。我甚至试过这个,它没有改变任何东西:

var temp = new ObservableCollection<RecordModel>();
Task task = new Task(() =>
{
    temp = DbWorker.GetFiles();
});
task.ContinueWith((result) =>
{
    Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
        new Action(() =>
        {
            Files = temp;
        }));
});
task.Start();

但是我没有看到属性没有更新的问题。我已经检查并根据需要更新了所有属性。这只是CanExecute状态更新中的一些小挂起,仅通过焦点更改更新(在此场景中)。

根据我所知道的,似乎线程和命令之间存在问题…嗯。每次我手动给UI元素一个焦点,点击,命令更新(或出现)。如果滚动条出现或消失,也会发生这种情况。但是其他UI元素不做任何事情,比如text.

奇怪的行为与按钮,命令和canExecute

嗯,正如我所看到的,你的命令只是不知道它与Files收集有关。所以Files被改变了,它的setter被调用,你的ListView被更新了,因为它被绑定到那个集合并对PropertyChanged做出反应。但命令并不直接与它绑定,所以它只是坐在那里不受干扰,喝咖啡或其他什么。只有当UI开始改变时,系统才会调用CanExecute并开始工作。所以我认为这里的简单解决方案是将Files绑定到command。

你可以直接在setter中设置(因为你知道命令依赖于集合):

public ObservableCollection<RecordModel> Files
{
    get { return _files; }
    set
    {
        if (_files == value) return;
        _files = value;
        OnPropertyChanged("Files");
        CommandManager.InvalidateRequerySuggested();
    }
}

或者你可以在加载集合时这样做(例如,如果你知道这是唯一影响集合大小的操作):

task.ContinueWith((result) =>
{
    Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
        new Action(() =>
        {
            Files = temp;
            CommandManager.InvalidateRequerySuggested();
        }));
});


注意:我使用InvalidateRequerySuggested作为使命令无效的最简单方法,但它有点过度。其他ICommand实现使用不同的技术来做到这一点(例如Telerik的DelegateCommand有自定义的InvalidateCanExecute方法)。

我将无法评论为什么这个设计不工作,因为从提供的代码中不清楚您的命令如何处理启用按钮。但我的做法如下:在我的视图模型类中,我将有一个公共属性isprintalallowed。

private bool _isPrintAllowed;
public bool IsPrintAllowed{
get{ return _isPrintAllowed;}
set{_isPrintAllowed = value;
RaisePropertyChanged(() => IsPrintAllowed)}

Files集合的CollectionChanged事件将计算isprintalallowed Property。

Files.CollectionChanged += EvaluateIsPrintAllowed;

private void EvaluateIsPrintAllowed()
{
     IsPrintAllowed = Files != null && Files.Count > 0;
}

在xaml中,我将把按钮的IsEnabled属性绑定到isprintalallowed。

<Button Content="Print" Command="{Binding PrintCommand}" IsEnabled = {Binding IsPrintAllowed}/>