【问题标题】:Odd behavior with Button, Command and canExecuteButton、Command 和 canExecute 的奇怪行为
【发布时间】:2014-06-22 10:14:49
【问题描述】:

我有一个简单的按钮:

<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 元素赋予焦点时,命令都会更新(或者它会出现)。如果 ScrollBar 出现或消失,也会发生这种情况。但随后其他 UI 元素不执行任何操作,例如文本。

【问题讨论】:

  • 我知道这可能是什么。您能否发布相关的 XAML 并展示您创建视图模型的方式/位置以及设置 Files 属性的位置/方式?
  • @MichaelGunter XAML 的哪一部分?有很多,所以我不想充斥着代码。我发布了 ViewModel 部分,显示了我如何设置我的收藏。你有什么样的想法?
  • 您能否也发布将 IsEnabled 属性为您的按钮绑定到集合大小的 xaml 部分。
  • @ankhuri 启用/禁用由命令处理。该代码已发布。

标签: c# wpf button command


【解决方案1】:

好吧,在我看来,您的命令只是不知道它与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 方法)。所以这部分可以进一步改进。

【讨论】:

  • 第一个不起作用。第二个做到了。太感谢了! :) 我会研究改进它的方法,但它现在可以解决问题。有没有办法在 XAML 中分配它?也许通过目标属性?
  • 对于第一个工作,您仍然需要在 UI 线程中设置 Files 以便正确调用通知事件(就像您在 ContinueWith 任务中所做的那样)。更多面向 XAML 的方法可以是将 Files 绑定到按钮的 CommandParameter 并让您的 CanExecute 方法接受并处理它。但这不会解决您最初的问题,因为您仍然需要将重点放在按钮的某些控件上以获取新的 IsEnabled 值。 @B.K.
  • 啊,有道理。感谢您为我提供更多信息,这对我来说非常宝贵。
  • 对我来说,Dispatcher 的第二个选项也有效。我想在我的情况下问题是属性 CanExecute() 返回是从不同的线程调用的。如果我理解正确,Dispatcher 会将调用传递给 UI 线程,然后绑定正常工作。
【解决方案2】:

我无法评论为什么此设计不起作用,因为从提供的代码中不清楚您的命令如何处理按钮的启用。但我会这样做的方式如下: 在我的视图模型类中,我将有一个公共属性 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}/>

【讨论】:

  • 如果我的代码不清楚,我认为您应该阅读此内容:msdn.microsoft.com/en-us/magazine/dd419663.aspx 向下滚动到 RelayCommand。
  • 在这种情况下,当您在命令中设置 CanExecute 的值时是否会引发 CanExecuteChanged 事件?
  • 嗯,看起来是这样。 PrintCommand.CanExecuteChanged += CanExecuteChanged; private void CanExecuteChanged(object sender, EventArgs e) { Debug.WriteLine(String.Format("sender: {0}, e: {1}", sender, e)); } 触发。但它也会触发视图中的其他命令 CanExecute 状态。
猜你喜欢
  • 2014-06-25
  • 1970-01-01
  • 2015-06-06
  • 1970-01-01
  • 2014-02-21
  • 2017-07-15
  • 1970-01-01
  • 1970-01-01
  • 2020-08-04
相关资源
最近更新 更多