Как получить DelegateCommand CanExecute для обновления интерфейса WPF с использованием MVVM? - PullRequest
0 голосов
/ 17 октября 2019

Отрабатывая этот вопрос , я пытаюсь создать интерфейс с двумя кнопками. Нажатие одной кнопки отключает себя и включает другую. Я использую шаблон MVVM и DelegateCommand:

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

Проблема в том, что когда я нажимаю кнопку Start Server, состояние кнопки IsEnabled не изменяется в пользовательском интерфейсе. Я неправильно использую DelegateCommand?

Ответы [ 2 ]

3 голосов
/ 17 октября 2019

Проблема с вашим кодом состоит в том, что в свойствах вашей команды используются тела выражений, они выполняются каждый раз, когда кто-то получает свойство, то есть каждый раз, когда вы запрашиваете команду StartServer, она дает вам новый экземпляр. Вы можете проверить это самостоятельно, поставив точку останова на получателе, и вы увидите, что она будет поражена несколько раз и всегда создаст новый экземпляр команды делегата.

Для того, чтобы ваш код работал, вы должны использоватьровно по одному экземпляру каждой команды, вы можете достичь этого с помощью вспомогательного поля или свойства только для чтения и установить его в 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; }

Пользовательский интерфейс будет прослушивать только возникшее событие CanExecuteChangedс помощью экземпляра команды, с которой она связана (через Command={Binding ...}.

Ваш код также может быть упрощен, поскольку вы можете вызывать RaiseCanExecuteChanged напрямую, вместо того, чтобы вызывать PropertyChangedEvent и слушать его для себя:

  public bool ServerIsRunning
  {
     get => m_serverIsRunning;
     set
     {
        m_serverIsRunning = value;
        StartServer.RaiseCanExecuteChanged();
        StopServer.RaiseCanExecuteChanged();
     }
  }
2 голосов
/ 17 октября 2019

Примерно так?

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 () для обновления состояния кнопки.

...