WPF&MVVM: Описание логики Relay Command

351
05 октября 2017, 10:36

В данном уроке по паттерну MVVM для приложений WPF был такой пример:

private RelayCommand addCommand;
public RelayCommand AddCommand
{
    get
    {
        return addCommand ??
            (addCommand = new RelayCommand(obj =>
            {
                Phone phone = new Phone();
                Phones.Insert(0, phone);
                SelectedPhone = phone;
            }));
    }
}

Это команда на добавление нового объекта, находящаяся в ApplicationViewModel. Теперь мы можем привязать кнопке эту команду:

<Button Command="{Binding AddCommand}">+</Button>

Одна из первых проблем, с которой я столкнулся при переписывании своего проекта в соответствии с шаблоном MVVM - это установка связи между командой RelayCommand и методом, где и описывается логика команды (впрочем, если это позволено и если логики мало, то можно и прямо внутри RelayCommand логику описать).

Вот конкретный неработающий пример. Метод ToogleButton должен изменяеть надпись на кнопке при нажатии на неё:

private void ToggleButton(object sender, EventArgs e) {
    Button clickedButton = sender as Button;
    if (clickedButton.Tag.Equals("On")) {
        clickedButton.Content = "Off";
        clickedButton.Tag="Off";
    }
    else {
        clickedButton.Content = "On";
        clickedButton.Tag="Off";
    }
}

С событиями делалось всё просто и этот метод работал прекрасно, но в случае с MVVM событий принято избегать. Каким образом следует исправить приведённый ниже код, чтобы он заработал?

private RelayCommand clickButton;
public RelayCommand ClickButton {
    ToggleButton();
}
private void ToggleButton(object sender, EventArgs e) {
    Button clickedButton = sender as Button;
    if (clickedButton.Tag.Equals("On")) {
        clickedButton.Content = "Off";
        clickedButton.Tag="Off";
    }
    else {
        clickedButton.Content = "On";
        clickedButton.Tag="Off";
    }
}

XAML:

<Button Command="{Binding ClickButton}" Content="On" Tag="On" />
Answer 1

Я покажу как бы сделал это я.

Заведите в VM свойство для команды:

public ICommand OnCommand { get; }

Заведите свойство, которое будет отображать состояние (On/Of), например:

bool isOn;
public bool IsOn
{
    get => isOn;
    set 
    {
        isOn = value;
        OnPropertyChanged(nameof(IsOn));
    }
}

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

void Switch()
{
    IsOn = !IsOn;
    // тут ваша логика
}

В конструкторе VM создадим команду:

public MainVm()
{
    OnCommand = new RelayCommand(_ => Switch());
}

Мне не нравится синтаксис типа:

private RelayCommand addCommand;
public RelayCommand AddCommand
{
    get
    {
        return addCommand ??
            (addCommand = new RelayCommand(obj =>
            {
                Phone phone = new Phone();
                Phones.Insert(0, phone);
                SelectedPhone = phone;
            }));
    }
}

Он слишком громоздок и избыточен

Теперь View-часть:

<Button Command="{Binding OnCommand}" Content="{Binding IsOn}"/>

Это уже работает, но текст на кнопке будет переключаться между False/True, давайте напишем конвертер, который будет выводить то что нужно нам:

class BoolToOnOffConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return (bool)value ? "On" : "Off";
    }
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Привязка будет работать в одну сторону, поэтому я не реализовываю метод ConvertBack. Метод Convert получает в параметре bool-значение и возвращает строку, которую мы выведем на кнопке.

Добавим в разметку ресурс с конвертером:

<Window.Resources>
    <local:BoolToOnOffConverter x:Key="BoolToOnOffConverter"/>
</Window.Resources>

Теперь воспользуемся этим конвертером:

<Button Command="{Binding OnCommand}"
        Content="{Binding IsOn, Converter={StaticResource BoolToOnOffConverter}}"/>

Готово!

Ну и напоследок небольшая хитрость, позволяющая несколько разгрузить и упростить разметку.

Давайте уберем ресурс с конвертером, а сам конвертер определим как расширение разметки, нам потребуется добавить всего строчку кода, 90% которой нам сгенерирует студия:

class BoolToOnOffConverter : MarkupExtension, IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return (bool)value ? "On" : "Off";
    }
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
    public override object ProvideValue(IServiceProvider serviceProvider) => this;
}

Отлично. Используем это так:

<Button Command="{Binding OnCommand}"
        Content="{Binding IsOn, Converter={local:BoolToOnOffConverter}}"/>
READ ALSO
C# Прорисовка участка формы вокруг курсора

C# Прорисовка участка формы вокруг курсора

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

314
Шаблон для Combobox

Шаблон для Combobox

В приложении WPF нужно создать свой Template для ComboboxПодскажите как убрать прямоугольник или задать ему радиус скругления = 0

636
С# Движение участка формы

С# Движение участка формы

C#, Windows forms Есть форма, 600 на 600 размерыПри запуске показывается её часть (верхний левый угол), 300 на 300 пикселей

276
asp.net core api авторизация JwtBearer vs. Identity

asp.net core api авторизация JwtBearer vs. Identity

В каких случаях какой способ авторизации лучше использовать?

314