简体   繁体   English

如何让 DelegateCommand CanExecute 使用 MVVM 更新 WPF UI?

[英]How do I get DelegateCommand CanExecute to update WPF UI using MVVM?

Working off of this question , I'm attempting to make a UI with two buttons. 解决这个问题,我试图用两个按钮制作一个 UI。 Pressing one button disables itself and enables the other.按下一个按钮会禁用自身并启用另一个按钮。 I'm using the MVVM pattern and DelegateCommand:我正在使用 MVVM 模式和 DelegateCommand:

MainWindow XAML主窗口 XAML

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:ViewModel />
    </Window.DataContext>

    <StackPanel>
        <Rectangle Height="100"></Rectangle>
        <Button Command="{Binding StartServer}" >Start Server</Button>
        <Button Command="{Binding StopServer}" >Stop Server</Button>
    </StackPanel>
</Window>

ViewModel视图模型

public class ViewModel : INotifyPropertyChanged
{
  public ViewModel()
  {
    PropertyChanged += PropertyChangedHandler;
  }

  private void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)
  {
    switch (e.PropertyName)
    {
      case nameof(ServerIsRunning):
        StartServer.RaiseCanExecuteChanged();
        StopServer.RaiseCanExecuteChanged();
        break;
    }
  }

  private bool m_serverIsRunning;
  public bool ServerIsRunning
  {
    get => m_serverIsRunning;
    set
    {
      m_serverIsRunning = value;
      RaisePropertyChanged(nameof(ServerIsRunning));
    }
  }

  public DelegateCommand<object> StartServer => new DelegateCommand<object>(
    context =>
    {
      ServerIsRunning = true;
    }, e => !ServerIsRunning);

  public DelegateCommand<object> StopServer => new DelegateCommand<object>(
    context =>
    {
      ServerIsRunning = false;
    }, e => ServerIsRunning);

  public event PropertyChangedEventHandler PropertyChanged;

  public void RaisePropertyChanged(string property)
  {
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
  }
}

DelegateCommand委托命令

public class DelegateCommand<T> : ICommand
{
  private readonly Action<object> ExecuteAction;
  private readonly Func<object, bool> CanExecuteFunc;

  public DelegateCommand(Action<object> executeAction, Func<object, bool> canExecuteFunc)
  {
    ExecuteAction = executeAction;
    CanExecuteFunc = canExecuteFunc;
  }

  public bool CanExecute(object parameter)
  {
    return CanExecuteFunc == null || CanExecuteFunc(parameter);
  }

  public void Execute(object parameter)
  {
    ExecuteAction(parameter);
  }

  public event EventHandler CanExecuteChanged;

  public void RaiseCanExecuteChanged()
  {
    CanExecuteChanged?.Invoke(this, EventArgs.Empty);
  }
}

The problem is, when I click on the Start Server button, the button's IsEnabled state doesn't change in the UI.问题是,当我单击Start Server按钮时,按钮的 IsEnabled state 在 UI 中没有改变。 Am I using the DelegateCommand improperly?我是否不正确地使用了 DelegateCommand?

The problem with your code is that your Command Properties use expression bodies, they are executed every time someone is getting the property, meaning everytime you ask for the StartServer command it will give you a new instance.您的代码的问题在于您的命令属性使用表达式主体,每次有人获取属性时都会执行它们,这意味着每次您请求StartServer命令时,它都会为您提供一个新实例。 You can check this yourself by putting a breakpoint on the getter and you see that it will be hit several times and always create a new instance of the delegate command.您可以通过在 getter 上放置一个断点来自己检查这一点,您会看到它会被多次命中,并且总是会创建一个新的委托命令实例。

In order to make your code work you have to use exactly one instance of each command, you can achieve this by using a backing field or a read-only property and set it in the conostructor:为了使您的代码工作,您必须使用每个命令的一个实例,您可以通过使用支持字段或只读属性并将其设置在构造函数中来实现这一点:

  public MainWindowViewModel()
  {
     PropertyChanged += PropertyChangedHandler;
     StartServer = new DelegateCommand<object>(
        context =>
        {
           ServerIsRunning = true;
        }, e => !ServerIsRunning);

     StopServer = new DelegateCommand<object>(
        context =>    {
           ServerIsRunning = false;
        }, e => ServerIsRunning);
  }

  public DelegateCommand<object> StartServer
  { get; }

  public DelegateCommand<object> StopServer
  { get; }

The UI will only listen to the CanExecuteChanged event raised by the instance of the command it is bound to (via the Command={Binding...} . UI 只会监听它所绑定的命令实例引发的CanExecuteChanged事件(通过Command={Binding...}

Your code could also be simplified as you could invoke the RaiseCanExecuteChanged directly instead of raising a PropertyChangedEvent and listening it to yourself:您的代码也可以简化,因为您可以直接调用RaiseCanExecuteChanged而不是引发PropertyChangedEvent并自己听:

  public bool ServerIsRunning
  {
     get => m_serverIsRunning;
     set
     {
        m_serverIsRunning = value;
        StartServer.RaiseCanExecuteChanged();
        StopServer.RaiseCanExecuteChanged();
     }
  }

Something like this?像这样的东西?

public class ViewModel : INotifyPropertyChanged
{
    bool _isServerStarted;
    public ViewModel()
    {
        StartServer = new DelegateCommand(OnStartServerExecute, OnStartServerCanExecute);
        StopServer = new DelegateCommand(OnStopServerExecute, OnStopServerCanExecute);
    }

    void OnStartServerExecute()
    {
        IsServerStarted = true;
    }
    bool OnStartServerCanExecute()
    {
        return !IsServerStarted;
    }
    void OnStopServerExecute()
    {
        IsServerStarted = false;
    }
    bool OnStopServerCanExecute()
    {
        return IsServerStarted;
    }
    void RaiseCanExecuteChanged()
    {
        StartServer.RaiseCanExecuteChanged();
        StopServer.RaiseCanExecuteChanged();
    }
    public bool IsServerStarted 
    {
        get => _isServerStarted;
        set
        {
            _isServerStarted = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsServerStarted)));
            RaiseCanExecuteChanged();
        }
    }

    public DelegateCommand StartServer { get; }
    public DelegateCommand StopServer { get; }

    public event PropertyChangedEventHandler PropertyChanged;
}

You just should use CommandManager.InvalidateRequerySuggested() for updating button state.您只需使用 CommandManager.InvalidateRequerySuggested() 来更新按钮 state。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM