Взаимодействие команд и событий

Последнее обновление: 28.08.2022

Кнопка поддерживает команды, и вместо обработки события Click мы можем прикрепить к ней команду. Но что делать, если элемент не поддерживает команду, а нам надо обработать какое-то его действие.

Например, у нас определен элемент ListBox, и мы хотим отслеживать выбор объекта в списке. Если бы мы использовали событийную модель, то мы бы обрабатывали событие SelectionChanged. Но мы знаем, что выделение объекта в списке ведет к изменению свойства SelectedItem элемента ListBox. Поэтому вместо применения события мы можем просто обрабатывать изменения свойства SelectedPhone в ApplicationViewModel, которое привязано к свойству SelectedItem у ListBox.

Аналогично у элемента TextBox есть событие TextChanged, которое вызывается при изменении вводимого текста. Но опять же изменение текста приводит к изменению свойства Text у элемента TextBox. Поэтому мы могли бы установить привязку для этого элемента к свойству Text:

<TextBox Text="{Binding Title, UpdateSourceTrigger=PropertyChanged}" />

А в классе Phone (либо в специальной ViewModel, которая инкапсулирует объект Phone) обрабатывать изменение свойства Title, которое бы синхронно менялось при вводе новых символов в текстовое поле:

public string Title
{
    get { return phone.Title; }
    set
    {
		// обработка изменения свойства
        phone.Title = value;
        OnPropertyChanged("Title");
    }
}

В тоже время подобные действия можно установить не для всех событий. И если мы хотим связать события с командами, необходимо использовать дополнительный функционал.

В частности, нам надо добавить в проект через пакетный менеджер Nuget специальный пакет Microsoft.Xaml.Behaviors.Wpf:

Команды и события в WPF и C#

Пусть у нас в проекте есть следующая модель Phone:

using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace HelloApp
{
    public class Phone : INotifyPropertyChanged
    {
        private string title;
        private string company;
        private int price;

        public Phone(string title, string company, int price)
        {
            this.title = title;
            this.company = company;
            this.price = price;
        }

        public string Title
        {
            get { return title; }
            set
            {
                title = value;
                OnPropertyChanged("Title");
            }
        }
        public string Company
        {
            get { return company; }
            set
            {
                company = value;
                OnPropertyChanged("Company");
            }
        }
        public int Price
        {
            get { return price; }
            set
            {
                price = value;
                OnPropertyChanged("Price");
            }
        }

        public event PropertyChangedEventHandler? PropertyChanged;
        public void OnPropertyChanged([CallerMemberName] string prop = "")
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(prop));
        }
    }
}

Затем определим следующий класс ApplicationViewModel, который будет представлять ViewModel:

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;

namespace HelloApp
{
    public class ApplicationViewModel : INotifyPropertyChanged
    {
        Phone? selectedPhone;
        public ObservableCollection<Phone> Phones { get; set; }

        // команда добавления нового объекта
        RelayCommand? addCommand;
        public RelayCommand AddCommand
        {
            get
            {
                return addCommand ??
                  (addCommand = new RelayCommand(obj =>
                  {
                      Phone phone = new Phone("", "", 0);
                      Phones.Insert(0, phone);
                      SelectedPhone = phone;
                  }));
            }
        }

        // команда удаления
        RelayCommand? removeCommand;
        public RelayCommand RemoveCommand
        {
            get
            {
                return removeCommand ??
                  (removeCommand = new RelayCommand(obj =>
                  {
                      Phone? phone = obj as Phone;
                      if (phone != null)
                      {
                          Phones.Remove(phone);
                      }
                  },
                 (obj) => Phones.Count > 0));
            }
        }

        RelayCommand? doubleCommand;
        public RelayCommand DoubleCommand
        {
            get
            {
                return doubleCommand ??
                    (doubleCommand = new RelayCommand(obj =>
                    {
                        Phone? phone = obj as Phone;
                        if (phone != null)
                        {
                            Phone phoneCopy = new Phone(phone.Title, phone.Company, phone.Price);
                            Phones.Insert(0, phoneCopy);
                        }
                    }));
            }
        }

        public Phone? SelectedPhone
        {
            get { return selectedPhone; }
            set
            {
                selectedPhone = value;
                OnPropertyChanged("SelectedPhone");
            }
        }

        public ApplicationViewModel()
        {
            Phones = new ObservableCollection<Phone>
            {
                new Phone("iPhone 7", "Apple", 56000),
                new Phone("Galaxy S7 Edge", "Samsung", 60000),
                new Phone("Elite x3", "HP", 56000),
                new Phone("Mi5S", "Xiaomi", 35000)
            };
        }

        public event PropertyChangedEventHandler? PropertyChanged;
        public void OnPropertyChanged([CallerMemberName] string prop = "")
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(prop));
        }
    }


    public class RelayCommand : ICommand
    {
        Action<object?> execute;
        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 canExecute == null || canExecute(parameter);
        }
        public void Execute(object? parameter)
        {
            execute(parameter);
        }
    }
}

Среди прочего здесь определена команда DoubleCommand, которая добавляет копию объекта в список.

Далее добавим в представление кнопку, которая будет вызывать данную команду. Но, допустим, мы хотим выполнять эту команду, если по кнопке был совершен двойной щелчок, то есть если произошло событие MouseDoubleClick:

<Window x:Class="HelloApp.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:i="http://schemas.microsoft.com/xaml/behaviors"
        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>
            <Button Command="{Binding RemoveCommand}" 
                    CommandParameter="{Binding SelectedPhone}">-</Button>
            <Button Content="2x">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="MouseDoubleClick">
                        <i:InvokeCommandAction 
                            Command="{Binding DoubleCommand}" 
                            CommandParameter="{Binding SelectedPhone}" />
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </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>

Для добавленной кнопки устанавливается свойство Interaction.Triggers, которое позволяет связать триггеры событий с командами и передать этим командам параметры:

<Button Content="2x">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="MouseDoubleClick">
            <i:InvokeCommandAction 
                Command="{Binding DoubleCommand}" 
                CommandParameter="{Binding SelectedPhone}" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
</Button>

В файле связанного кода MainPage.xaml.cs определим привязку ApplicationViewModel к контексту страницы MainPage

using System.Windows;

namespace HelloApp
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new ApplicationViewModel();
        }
    }
}

Теперь по двойному нажатию на эту кнопку произойдет дублирование в списке выделенного элемента:

EventTrigger и Command в WPF
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850