【问题标题】:MVVM-Light - RelayCommand CantExecute issueMVVM-Light - RelayCommand CantExecute 问题
【发布时间】:2016-06-21 14:20:25
【问题描述】:

我对 MVVM-Light 有疑问。我用的是5.3.0.0版本...

.XAML

<DockPanel Dock="Top">
        <Button Margin="5" VerticalAlignment="Top" HorizontalAlignment="Center" Command="{Binding CancelDownloadCommand}" FontSize="20" 
                Background="Transparent" BorderThickness="2" BorderBrush="{StaticResource AccentColorBrush4}" ToolTip="Cancelar"
                DockPanel.Dock="Right">
            <StackPanel Orientation="Horizontal">
                <Image Source="Images/48x48/Error.png" Height="48" Width="48"/>
                <Label Content="{Binding ToolTip, RelativeSource={RelativeSource AncestorType={x:Type Button}}}" FontFamily="Segoe UI Light"/>
            </StackPanel>
        </Button>
        <Button Margin="5" VerticalAlignment="Top" HorizontalAlignment="Center" Command="{Binding DownloadCommand}" FontSize="20" 
                Background="Transparent" BorderThickness="2" BorderBrush="{StaticResource AccentColorBrush4}" ToolTip="Descargar"
                DockPanel.Dock="Right">
            <StackPanel Orientation="Horizontal">
                <Image Source="Images/48x48/Download.png" Height="48" Width="48"/>
                <Label Content="{Binding ToolTip, RelativeSource={RelativeSource AncestorType={x:Type Button}}}" FontFamily="Segoe UI Light"/>
            </StackPanel>
        </Button>
    </DockPanel>

下载ViewModel.cs

我使用了 MessageBox,但在我的例子中,调用了一个读取 XML 的方法。此示例不起作用,按钮被禁用,但在执行结束时不会重新激活。我需要点击 UI 来激活。

using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.CommandWpf;

private async void Download()
{
    Reset();

    await Task.Run(() =>
    {
        MessageBox.Show("Hello");
    });

    Reset();
}

private void Reset()
{
    IsEnabled = !IsEnabled;
    IsEnabledCancel = !IsEnabledCancel;
}

private ICommand _downloadCommand;
public ICommand DownloadCommand
{
    get { return _downloadCommand ?? (_downloadCommand = new RelayCommand(Download, () => IsEnabled)); }
}

private ICommand _cancelDownloadCommand;
public ICommand CancelDownloadCommand
{
    get
    {
        return _cancelDownloadCommand ??
               (_cancelDownloadCommand = new RelayCommand(CancelDownload, () => IsEnabledCancel));
    }
}

private bool _isEnabled = true;
private bool IsEnabled
{
    get { return _isEnabled; }
    set
    {
        if (_isEnabled != value)
        {
            _isEnabled = value;
            RaisePropertyChanged();
        }
    }
}

private bool _isEnabledCancel;
private bool IsEnabledCancel
{
    get { return _isEnabledCancel; }
    set
    {
        if (_isEnabledCancel != value)
        {
            _isEnabledCancel = value;
            RaisePropertyChanged();
        }
    }
}

通过使用 CommandManager.InvalidateRequerySuggested(),我修复了它。但是在不推荐的地方阅读,因为此命令检查所有 RelayCommand。这在我之前没有发生过。

但如果在 Task.Run 中不添加任何内容。它完美地工作。按钮被激活和再次停用。

private async void Download()
{
    Reset();

    await Task.Run(() =>
    {
        // WIDTHOUT CODE
        // WIDTHOUT CODE
        // WIDTHOUT CODE
    });

    Reset();
}

【问题讨论】:

标签: wpf mvvm-light


【解决方案1】:

当你更新CanExecute,在你的情况下IsEnabledIsEnabledCancel属性,你必须引发CanExecuteChanged事件。

您还可以稍微简化代码。

private bool _isEnabled;

public bool IsEnabled
{
    get { return _isEnabled; }
    set
    {
        if (Set(ref _isEnabled, value))
        {
            DownloadCommand.RaiseCanExecuteChanged();
        }
    }
}

以同样的方式更新您的 IsEnabledCancel 属性。

当然,您必须将您的命令声明为RelayCommand 而不是ICommand

private RelayCommand _downloadCommand;

public RelayCommand DownloadCommand
{
    get { return _downloadCommand ?? (_downloadCommand = new RelayCommand(Download, () => IsEnabled)); }
}

您还可以阅读:“A smart MVVM command”。

【讨论】:

    【解决方案2】:

    source code for MVVM Light,它基于CommandManager.InvalidateRequerySuggested()(反)模式。由于(反)模式的全局性质,您说得对,这是一个巨大的性能消耗。

    问题出在构造函数上。 public RelayCommand(Action execute, Func&lt;bool&gt; canExecute)

    由于canExecuteFunc&lt;bool&gt;,因此无法(在运行时)获取属性名称,因此无法绑定INotifyPropertyChanged.PropertyChanged 事件。从而导致命令重新评估canExecute

    @kubakista 向您展示如何通过调用 RaiseCanExecuteChanged 方法来强制重新评估。但这确实违反了单一责任原则,并且泄露了ICommand的实现。

    我的建议是使用ReactiveUIReactiveCommand。这使您可以:

    DownloadCommand = ReactiveCommand.Create(Download, this.WhenAnyValue(x => x.IsEnabled).Select(enabled => enabled));
    CancelDownloadCommand = ReactiveCommand.Create(CancelDownload, this.WhenAnyValue(x => x.IsEnabled).Select(enabled => false == enabled));
    
    
    public bool IsEnabled
    { 
        get {return _isEnabled; } 
        set
        {
            _isEnabled = value;
            OnPropertyChanged();
        }
    }
    

    【讨论】:

      【解决方案3】:

      我注意到的一件事是你的Enabled 属性(IsEnabled、IsEnabledCancel)是private,而它们应该是public。但是,这并不能解决您的问题:)

      一个简单的解决方法是去掉命令中的 CanExecute 部分 例如

      public ICommand DownloadCommand
      {
          get { return _downloadCommand ?? (_downloadCommand = new RelayCommand(Download)); }
      }
      

      并绑定到 xaml 中 Button.IsEnabledproperty 上的属性 例如

      <Button IsEnabled="{Binding IsEnabled}" Margin="5" VerticalAlignment="Top"   
              HorizontalAlignment="Center" Command="{Binding DownloadCommand}" 
              FontSize="20" Background="Transparent" BorderThickness="2" 
              BorderBrush="Red" ToolTip="Descargar" DockPanel.Dock="Right">
          ...
      </Button>
      

      希望有帮助

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-01-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-08-03
        相关资源
        最近更新 更多