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

标签 c# wpf mvvm delegatecommand

处理 this question ,我正在尝试制作一个带有两个按钮的用户界面。按下一个按钮会禁用自身并启用另一个按钮。我正在使用 MVVM 模式和 DelegateCommand:

主窗口 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>

查看型号
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));
  }
}

委托(delegate)指挥
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);
  }
}

问题是,当我点击 Start Server按钮,按钮的 IsEnabled 状态在 UI 中不会改变。我是否不正确地使用了 DelegateCommand?

最佳答案

您的代码的问题是您的命令属性使用表达式主体,每次有人获取属性时都会执行它们,这意味着每次您要求 StartServer命令它会给你一个新的实例。您可以通过在 getter 上放置一个断点来自己检查这一点,您会看到它会被多次命中,并且总是会创建一个新的委托(delegate)命令实例。

为了使您的代码工作,您必须使用每个命令的一个实例,您可以通过使用支持字段或只读属性并将其设置在构造函数中来实现这一点:

  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; }

UI 将只收听 CanExecuteChanged绑定(bind)到的命令实例引发的事件(通过 Command={Binding ...}

您的代码也可以简化,因为您可以调用 RaiseCanExecuteChanged直接而不是提出 PropertyChangedEvent并自己听:
  public bool ServerIsRunning
  {
     get => m_serverIsRunning;
     set
     {
        m_serverIsRunning = value;
        StartServer.RaiseCanExecuteChanged();
        StopServer.RaiseCanExecuteChanged();
     }
  }

关于c# - 如何让 DelegateCommand CanExecute 使用 MVVM 更新 WPF UI?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58437185/

相关文章:

.net - WPF 中的 NameScope 如何工作?

c# - 在另一个 IList 数据模型中存储 Array 或 IList

c# - DDD实体持久化场景

c# - 使用 OpenXML 2.5 将数据写入 docx 文档中的 TextInput 元素

c# - 使用反射模仿 C# 数组初始化器行为

wpf - DataGrid 数据绑定(bind)问题与样式

c# - 如何在 WPF 中使用 Prism 6 处理 ViewModel 中的按键事件?

extjs 5如何将商店绑定(bind)到模式窗口和网格

c# - 用于将中间用户输入从 ViewModel 传递到 Model 的 MVVM 模式

c# - 管理员通过部门查看文档