简体   繁体   中英

C# MVVM RelayCommand CanExecute action does not update button enable/disable state

I am new to MVVM so bare with me.

Objectives:

  1. Enable "Stop Server"-button when I click "Start Server"-button and vice versa
  2. Make the IsRunning-property in MainViewModel listen to changes in the _serverModel.IsRunning property

Current issue: When I click "Start Server"-button, both buttons become disabled.

在此处输入图片说明

MainWindow.xaml

<Window x:Class="ServerGUI.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:uc="clr-namespace:ServerGUI.Views"
        xmlns:vm="clr-namespace:ServerGUI.ViewModels"
        mc:Ignorable="d"
        Title="MainWindow" Height="150" Width="200">

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*" />
        </Grid.ColumnDefinitions>

        <GroupBox Grid.Column="0" Header="Server Properties">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="30" />
                    <RowDefinition Height="30" />
                    <RowDefinition Height="30" />
                </Grid.RowDefinitions>

                <Button Grid.Row="0" Content="Start Server" Command="{Binding Path=StartServerCommand}" Margin="3 5 3 0" />
                <Button Grid.Row="1" Content="Stop Server" Command="{Binding Path=StopServerCommand}" Margin="3 5 3 0" />
                <Label Grid.Row="2" x:Name="lblServerStatus" Content="Server Status: Offline" />
            </Grid>
        </GroupBox>
    </Grid>

</Window>

MainWindow.xaml.cs

using ServerGUI.ViewModels;
using System.Windows;

namespace ServerGUI
{
    public partial class MainWindow : Window
    {
        public MainViewModel MainViewModel { get; set; }

        public MainWindow()
        {
            MainViewModel = new MainViewModel();
            DataContext = MainViewModel;
            InitializeComponent();
        }
    }
}

BaseViewModel.cs

using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace ServerGUI.ViewModels
{
    public abstract class BaseViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected bool SetProperty<T>(T field, T newValue, [CallerMemberName] string propertyName = null)
        {
            if (!EqualityComparer<T>.Default.Equals(field, newValue))
            {
                field = newValue;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
                return true;
            }

            return false;
        }
    }
}

MainViewModel.cs

using ServerGUI.Commands;
using ServerGUI.Models;
using System.Windows.Input;

namespace ServerGUI.ViewModels
{
    public class MainViewModel : BaseViewModel
    {
        private readonly ServerModel _serverModel;

        public bool IsRunning
        {
            get => _serverModel.IsRunning;
            set => SetProperty(_serverModel.IsRunning, value);
        }

        private readonly RelayCommand _startServerCommand;
        private readonly RelayCommand _stopServerCommand;
        public ICommand StartServerCommand => _startServerCommand;
        public ICommand StopServerCommand => _stopServerCommand;

        public MainViewModel()
        {
            _serverModel = new ServerModel();
            _startServerCommand = new RelayCommand(OnStartServer, CanStartServer);
            _stopServerCommand = new RelayCommand(OnStopServer, CanStopServer);
        }

        private void OnStartServer(object command)
        {
            IsRunning = true;
            _startServerCommand.InvokeCanExecuteChanged();
        }

        private void OnStopServer(object command)
        {
            IsRunning = false;
            _stopServerCommand.InvokeCanExecuteChanged();
        }

        private bool CanStartServer(object command) => !IsRunning;
        private bool CanStopServer(object command) => IsRunning;
    }
}

ServerModel.cs

using System.ComponentModel;

namespace ServerGUI.Models
{
    public class ServerModel : INotifyPropertyChanged
    {
        private bool isRunning = default;

        public event PropertyChangedEventHandler PropertyChanged;

        public ServerModel()
        {
            IsRunning = false;
        }

        public bool IsRunning
        {
            get => isRunning;
            set
            {
                isRunning = value;
                RaisePropertyChanged(nameof(IsRunning));
            }
        }

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

RelayCommand.cs

using System;
using System.Windows.Input;

namespace ServerGUI.Commands
{
    class RelayCommand : ICommand
    {
        private readonly Action<object> _executeAction;
        private readonly Func<object, bool> _canExecuteAction;

        public RelayCommand(Action<object> executeAction, Func<object, bool> canExecuteAction)
        {
            _executeAction = executeAction;
            _canExecuteAction = canExecuteAction;
        }

        public void Execute(object parameter) => _executeAction(parameter);

        public bool CanExecute(object parameter) => _canExecuteAction?.Invoke(parameter) ?? true;

        public event EventHandler CanExecuteChanged;

        public void InvokeCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }
}

If you put a break point at line

set => SetProperty(_serverModel.IsRunning, value);

then debug the program, you will see that when property IsRunning is set to true in OnStartServer , it does not set _serverModel.IsRunning because SetProperty does not update the field as you expected. I would suggest you to modify SetProperty method by adding ref to field parameter as follows:

    protected bool SetProperty<T>(ref T field, T newValue, [CallerMemberName] string propertyName = null)
    {
        if (!EqualityComparer<T>.Default.Equals(field, newValue))
        {
            field = newValue;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            return true;
        }

        return false;
    }

Then, in MainViewModel , you just need to update IsRunning based on _serverModel.IsRunning . Also, you should notify changes from both StartServerCommand and StopServerCommand

public class MainViewModel : BaseViewModel
{
    private readonly ServerModel _serverModel;

    public bool IsRunning
    {
        get => _serverModel.IsRunning;
    }

    private readonly RelayCommand _startServerCommand;
    private readonly RelayCommand _stopServerCommand;

    public ICommand StartServerCommand => _startServerCommand;
    public ICommand StopServerCommand => _stopServerCommand;

    public MainViewModel()
    {
        _serverModel = new ServerModel();
        _startServerCommand = new RelayCommand(OnStartServer, CanStartServer);
        _stopServerCommand = new RelayCommand(OnStopServer, CanStopServer);
    }

    private void OnStartServer(object command)
    {
        _serverModel.IsRunning = true;
        _startServerCommand.InvokeCanExecuteChanged();
        _stopServerCommand.InvokeCanExecuteChanged();
    }

    private void OnStopServer(object command)
    {
        _serverModel.IsRunning = false;
        _startServerCommand.InvokeCanExecuteChanged();
        _stopServerCommand.InvokeCanExecuteChanged();
    }

    private bool CanStartServer(object command) => !IsRunning;
    private bool CanStopServer(object command) => IsRunning;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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