【问题标题】:How do I get DelegateCommand CanExecute to update WPF UI using MVVM?如何让 DelegateCommand CanExecute 使用 MVVM 更新 WPF UI?
【发布时间】:2019-10-17 16:35:36
【问题描述】:

使用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));
  }
}

委托命令

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?

【问题讨论】:

    标签: c# wpf mvvm delegatecommand


    【解决方案1】:

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

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

      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 事件(通过 Command={Binding ...}

    您的代码也可以简化,因为您可以直接调用 RaiseCanExecuteChanged 而不是引发 PropertyChangedEvent 并自己听:

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

    【讨论】:

      【解决方案2】:

      这样的?

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

      您应该使用 CommandManager.InvalidateRequerySuggested() 来更新按钮状态。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-11-23
        • 2012-03-28
        • 1970-01-01
        • 2011-11-13
        • 1970-01-01
        • 1970-01-01
        • 2021-12-20
        相关资源
        最近更新 更多