Для взаимодействия пользователя и приложения в MVVM используются команды. Это не значит, что вовсе не можем использовать события и событийную модель, однако везде, где возможно, вместо событий следует использовать команды.
В WPF команды представлены интерфейсом ICommand:
public interface ICommand { event EventHandler CanExecuteChanged; void Execute (object parameter); bool CanExecute (object parameter); }
Однако WPF имеет в качестве реализации этого интерфейса имеет класс System.Windows.Input.RoutedCommand
, который ограничен по функциональности.
Поэтому, как правило, придется реализовывать свои собственные команды с помощью реализации ICommand.
Для использования команд продолжим работу с проектом из прошлой темы и добавим в него новый класс, который назовем RelayCommand:
using System; using System.Windows.Input; namespace MVVM { public class RelayCommand : ICommand { private Action<object> execute; private Func<object, bool> canExecute; public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } } public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null) { this.execute = execute; this.canExecute = canExecute; } public bool CanExecute(object parameter) { return this.canExecute == null || this.canExecute(parameter); } public void Execute(object parameter) { this.execute(parameter); } } }
Класс реализует два метода:
CanExecute: определяет, может ли команда выполняться
Execute: собственно выполняет логику команды
Событие CanExecuteChanged вызывается при изменении условий, указывающий, может ли команда выполняться. Для этого используется
событие CommandManager.RequerySuggested
.
Ключевым является метод Execute. Для его выполнения в конструкторе команды передается делегат типа Action<object>
. При этом
класс команды не знает какое именно действие будет выполняться. Например, мы можем написать так:
var cmd = new RelayCommand(o => { MessageBox.Show("Команда" + o.ToString()); }); cmd.Execute("1");
В результате вызова команды будет выведено окно с надписью "Команда1". Но мы могли также передать любое другое действие, которое бы соответствовало
делегату Action<object>
.
Для ряда визуальных элементов WPF, например, для кнопок, определена поддержка команд. Однако сами команды определяются в ViewModel и затем через механизм привязки устанавливаются для элементов управления. Например, изменим код ApplicationViewModel следующим образом:
using System.ComponentModel; using System.Runtime.CompilerServices; using System.Collections.ObjectModel; namespace MVVM { public class ApplicationViewModel : INotifyPropertyChanged { private Phone selectedPhone; public ObservableCollection<Phone> Phones { get; set; } // команда добавления нового объекта private RelayCommand addCommand; public RelayCommand AddCommand { get { return addCommand ?? (addCommand = new RelayCommand(obj => { Phone phone = new Phone(); Phones.Insert(0, phone); SelectedPhone = phone; })); } } public Phone SelectedPhone { get { return selectedPhone; } set { selectedPhone = value; OnPropertyChanged("SelectedPhone"); } } public ApplicationViewModel() { Phones = new ObservableCollection<Phone> { new Phone { Title="iPhone 7", Company="Apple", Price=56000 }, new Phone {Title="Galaxy S7 Edge", Company="Samsung", Price =60000 }, new Phone {Title="Elite x3", Company="HP", Price=56000 }, new Phone {Title="Mi5S", Company="Xiaomi", Price=35000 } }; } public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged([CallerMemberName]string prop = "") { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(prop)); } } }
Здесь добавлена команда на добавление объекта:
private RelayCommand addCommand; public RelayCommand AddCommand { get { return addCommand ?? (addCommand = new RelayCommand(obj => { Phone phone = new Phone(); Phones.Insert(0, phone); SelectedPhone = phone; })); } }
Команда хранится в свойстве AddCommand и представляет собой объект выше определенного класса RelayCommand. Этот объект в конструкторе принимает действие - делегат Action<object>. Здесь действие представлено в виде лямбда-выражения, которое добавляет в коллекцию Phones новый объект Phone и устанавливает его в качестве выбранного.
Используем эту команду. Для этого изменим код представления в MainWindow.xaml:
<Window x:Class="MVVM.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:MVVM" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <Style TargetType="TextBlock"> <Setter Property="FontSize" Value="14" /> </Style> <Style TargetType="TextBox"> <Setter Property="FontSize" Value="14" /> </Style> <Style TargetType="Button"> <Setter Property="Width" Value="40" /> <Setter Property="Margin" Value="5" /> </Style> </Window.Resources> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="0.8*" /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="0.2*" /> </Grid.RowDefinitions> <ListBox Grid.Column="0" ItemsSource="{Binding Phones}" SelectedItem="{Binding SelectedPhone}"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Margin="5"> <TextBlock FontSize="18" Text="{Binding Path=Title}" /> <TextBlock Text="{Binding Path=Company}" /> <TextBlock Text="{Binding Path=Price}" /> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <StackPanel Grid.Row="1" Orientation="Horizontal"> <Button Command="{Binding AddCommand}">+</Button> </StackPanel> <StackPanel Grid.Column="1" DataContext="{Binding SelectedPhone}"> <TextBlock Text="Выбранный элемент" /> <TextBlock Text="Модель" /> <TextBox Text="{Binding Title, UpdateSourceTrigger=PropertyChanged}" /> <TextBlock Text="Производитель" /> <TextBox Text="{Binding Company, UpdateSourceTrigger=PropertyChanged}" /> <TextBlock Text="Цена" /> <TextBox Text="{Binding Price, UpdateSourceTrigger=PropertyChanged}" /> </StackPanel> </Grid> </Window>
Здесь добавлена кнопка, свойство Command
которой приязано к свойству AddCommand объекта ApplicationViewModel:
<Button Command="{Binding AddCommand}">+</Button>
И нам не надо писать никаких обработчиков нажатия. Автоматически при нажатии на кнопку сработает команда, которая добавит в список еще один объект. А код в файле MainWindow.xaml.cs остается прежним:
using System.Windows; namespace MVVM { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); DataContext = new ApplicationViewModel(); } } }
И при нажатии на кнопку в список будет добавлен новый объект, который мы сразу сможем отредактировать в текстовых полях справа: