Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
Изменение файла MainWindow.xaml.cs
Следующее изменение связано с созданием экземпляра класса ChangeColorCommand, к которому может иметь доступ элемент управления Button. В настоящий момент вы будете делать это в файле отделенного кода для MainWindow (позже в главе код переместится в модель представления). Откройте файл MainWindow.xaml.cs и удалите обработчик события Click для кнопки Change Color. Поместите в начало файла следующие операторы using (пространство имен может варьироваться в зависимости от того, работаете вы с предыдущим проектом или начали новый):
using WpfCommands.Cmds;
using System.Windows.Input;
Добавьте открытое свойство по имени ChangeColorCmd типа ICommand с поддерживающим полем. В теле выражения для свойства возвратите значение поддерживающего поля (создавая экземпляр ChangeColorCommand, если поддерживающее поле равно null):
private ICommand _changeColorCommand = null;
public ICommand ChangeColorCmd
=> _changeColorCommand ??= new ChangeColorCommand());
Изменение файла MainWindow.xaml
Как было показано в главе 25, элементы управления WPF, реагирующие на щелчки (вроде Button), имеют свойство Command, которое позволяет назначать элементу управления объект команды. Для начала присоедините объект команды, созданный в файле отделенного кода, к кнопке btnChangeColor. Поскольку свойство для команды находится в классе MainWindow, с помощью синтаксиса привязки RelativeSource получается окно, содержащее необходимую кнопку:
Command="{Binding Path=ChangeColorCmd,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type Window}}}"
Кнопка также нуждается в передаче объекта Car в качестве параметра для методов CanExecute() и Execute(), что делается через свойство CommandParameter. Установите свойство Path для CommandParameter в свойство SelectedItem элемента ComboBox по имени cboCars:
CommandParameter="{Binding ElementName=cboCars, Path=SelectedItem}"
Вот завершенная разметка для кнопки:
<Button x:Name="btnChangeColor" Content="Change Color" Margin="5,0,5,0"
Padding="4, 2" Command="{Binding Path=ChangeColorCmd,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type Window}}}"
CommandParameter="{Binding ElementName=cboCars, Path=SelectedItem}"/>
Тестирование приложения
Запустите приложение. Кнопка Change Color не будет доступной (рис. 28.8), т.к. автомобиль еще не выбран.
Теперь выберите автомобиль; кнопка Change Color становится доступной, а щелчок на ней обеспечивает изменение цвета, как и ожидалось!
Создание класса CommandBase
Если распространить такой шаблон на AddCarCommand.cs, то итогом стал бы код, повторяющийся среди классов. Это хороший знак о том, что необходим базовый класс. Создайте внутри папки Cmds новый файл класса по имени CommandBase.cs и добавьте оператор using для пространства имен System.Windows.Input. Сделайте класс CommandBase открытым и реализующим интерфейс ICommand. Превратите класс и методы Execute() и CanExecute() в абстрактные. Наконец, добавьте обновление в событие CanExecuteChanged из класса ChangeColorCommand. Ниже показана полная реализация:
using System;
using System.Windows.Input;
namespace WpfCommands.Cmds
{
public abstract class CommandBase : ICommand
{
public abstract bool CanExecute(object parameter);
public abstract void Execute(object parameter);
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
}
}
Добавление класса AddCarCommand
Добавьте в папку Cmds новый файл класса по имени AddCarCommand.cs. Сделайте класс открытым и укажите CommandBase в качестве базового класса. Поместите в начало файла следующие операторы using:
using System.Collections.ObjectModel;
using System.Linq;
using WpfCommands.Models;
Ожидается, что параметр должен иметь тип ObservableCollection<Car>, поэтому предусмотрите в методе CanExecute() соответствующую проверку. Если параметр относится к типу ObservableCollection<Car>, тогда метод Execute() должен добавить дополнительный объект Car подобно обработчику события Click.
public class AddCarCommand :CommandBase
{
public override bool CanExecute(object parameter)
=> parameter is ObservableCollection<Car>;
public override void Execute(object parameter)
{
if (parameter is not ObservableCollection<Car> cars)
{
return;
}
var maxCount = cars.Max(x => x.Id);
cars.Add(new Car
{
Id = ++maxCount,
Color = "Yellow",
Make = "VW",
PetName = "Birdie"
});
}
}
Изменение файла MainWindow.xaml.cs
Добавьте открытое свойство типа ICommand по имени AddCarCmd с поддерживающим полем. В теле выражения для свойства возвратите значение поддерживающего поля (создавая экземпляр AddCarCommand, если поддерживающее поле равно null):
private ICommand _addCarCommand = null;
public ICommand AddCarCmd
=> _addCarCommand ??= new AddCarCommand());
Изменение файла MainWindow.xaml
Модифицируйте разметку XAML, удалив атрибут Click и добавив атрибуты Command и CommandParameter. Объект AddCarCommand будет получать список автомобилей из поля со списком cboCars. Ниже показана полная разметка XAML для кнопки:
<Button x:Name="btnAddCar" Content="Add Car" Margin="5,0,5,0" Padding="4, 2"
Command="{Binding Path=AddCarCmd,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type Window}}}"
CommandParameter="{Binding ElementName=cboCars, Path=ItemsSource}"/>
В результате появляется возможность добавления автомобилей и обновления их цветов (пока с весьма ограниченной функциональностью) с помощью многократно используемого кода, содержащегося в автономных классах.
Изменение класса ChangeColorCommand
Финальным шагом будет обновление класса ChangeColorCommand, чтобы он стал унаследованным от CommandBase. Замените интерфейс ICommand классом CommandBase, добавьте к обоим методам ключевое слово override и удалите код события CanExecuteChanged. Все оказалось действительно настолько просто! Вот как выглядит новый код:
public class ChangeColorCommand : CommandBase
{
public override bool CanExecute(object parameter)
=> parameter is Car;
public override void Execute(object parameter)
{
((Car)parameter).Color = "Pink";
}
}
Объекты RelayCommand
Еще одной реализацией паттерна "Команда"