使用Button,Command和canExecute的奇怪行为

时间:2014-05-06 00:02:39

标签: c# wpf button command

我有一个简单的按钮:

<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集合不为null或其中包含某些项目时才会启用它。这是集合:

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

        _files = value;
        OnPropertyChanged("Files");
    }
}

该集合绑定到窗口中的ListView。到目前为止,没有什么特别的......而且这里有奇怪的行为......

如果我在集合中有足够的项目要显示ListView ScrollBar,那么我的按钮会显示为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情况下),但是当我不使用{{1}时更新我的收藏,我没有经历过这种行为。当然,由于漫长的操作,我不得不处理悬挂问题。考虑到我不想阻止UI线程,我不知道如何解决这个问题。我甚至试过这个并没有改变任何东西:

Task

但我没有看到有关未更新的属性的问题。我已经检查过,所有属性都会根据需要更新。它只是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(); 状态更新中的一小部分内容,只有焦点更改才会更新(在此方案中)。

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

2 个答案:

答案 0 :(得分:3)

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

你可以直接用它来设置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方法)。所以这部分可以进一步改进。

答案 1 :(得分:0)

我无法评论为什么这个设计不起作用,因为从代码中提供的命令处理启用按钮的方法不清楚。但我这样做的方式如下: 在我的viewmodel类中,我将拥有一个公共属性IsPrintAllowed。

private bool _isPrintAllowed;

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

Files集合的CollectionChanged事件将评估IsPrintAllowed属性。

Files.CollectionChanged += EvaluateIsPrintAllowed;


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

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

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