Команды

55

В хорошо структурированном приложении Silverlight деловая логика должна находиться не в обработчиках событий, а в высокоуровневых методах. Каждый такой метод представляет одну из задач, решаемых приложением. В свою очередь, каждая задача может зависеть от других библиотек (таких как отдельно скомпилированные компоненты, инкапсулирующие доступ к данным, вызов веб-служб и другие типы деловой логики). Отношения между задачами и компонентами показаны на рисунке ниже:

Наиболее очевидный способ реализации данной структуры состоит в добавлении обработчиков событий каждый раз, когда они нужны, и вызове соответствующих методов приложения в каждом обработчике. В сущности, страница Silverlight (т.е. пользовательский класс, производный от UserControl) становится при этом переключательной панелью, реагирующей на события ввода и передающей запросы методам приложения.

Данная структура вполне целесообразна, но все же требует интенсивного кодирования интерфейса. Чтобы иметь возможность вызвать методы приложения, оно должно реагировать на разные события, при этом одна и та же задача приложения иногда запускается разными способами (например, с помощью кнопки или команды контекстного меню).

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

Платформа Silverlight не предоставляет зрелого решения данной проблемы. Многие разработчики приложений Silverlight используют для ее решения шаблон MVVM (Model View — View Model). Базовая идея шаблона MVVM состоит в разделении приложения на слои. Модель — это содержимое приложения или данные, которыми управляет приложение, а представление — графический интерфейс, состоящий из кнопок, графики и других элементов управления Silverlight. Между ними находится слой модели представления, с помощью которого модель и представление сообщаются друг с другом. Например, когда пользователь щелкает на кнопке в представлении, он запускает этим команду в слое модели представления, которая в результате этого изменяет данные в модели.

Простая командная модель Silverlight не имеет почти ничего общего с мощной командной моделью WPF. Разработчикам приложений Silverlight предоставлена лишь базовая функциональность для создания собственной архитектуры MVVM. Ниже приведены существенные особенности командной модели Silverlight:

В следующих разделах рассмотрен простой пример использования команд.

Создание команды

Сердцевина командной модели Silverlight — интерфейс System.Windows.Input.ICommand, который задает поведение команды. В этом интерфейсе определены два метода и одно событие:

public interface ICommand
{
   void Execute(object parameter);
   bool CanExecute(object parameter);
   event EventHandler CanExecuteChanged;
}

В простейшей реализации метод Execute() должен содержать деловую логику задачи (например, процедуру печати документа). Метод CanExecute() возвращает состояние команды. Оно равно true, если команда доступна, или false, если команда отключена. Как Exectute(), так и CanExecute() принимают объект, посредством которого команде можно передать дополнительную информацию.

Событие CanExecuteChanged генерируется при изменении состояния команды. Оно сигнализирует всем элементам управления, в которых используется команда, что они могут вызвать метод CanExecute(), чтобы проверить состояние команды. Это позволяет источнику команды (например, кнопке) автоматически подключить себя, когда команда становится доступной, или отключить, когда она становится недоступной.

Например, приведенная ниже команда PrintTextCommand печатает одну строку (собственно, для упрощения примера она всего лишь отображает ее в окне сообщения, однако в нее можно добавить реальную логику). Метод CanExecute() проверяет строку и отменяет печать, если строки нет или она пустая. В обоих случаях команда получает строку посредством параметра метода:

public class PrintTextCommand : ICommand
{
        public event EventHandler CanExecuteChanged; 
        private bool canExecute;

        public bool CanExecute(object parameter)
        {
            // Проверка, можно ли выполнить команду bool 
            bool canExecuteNow = (parameter != null) && (parameter.ToString() != "");

            if (canExecute != canExecuteNow)
            {
                canExecute = canExecuteNow;
                if (CanExecuteChanged != null)
                {
                    CanExecuteChanged(this, new EventArgs());
                }
            }

            return canExecute;
        }

        public void Execute(object parameter)
        {
            MessageBox.Show("Печать: " + parameter);
        }
}

Подключение команды

Чтобы применить команду, нужно установить свойства Command и CommandParameter кнопки. Это можно сделать в коде, но тогда будут утрачены преимущества командной модели, позволяющей удалить код обработки события из класса пользовательского элемента управления. Идеальное решение состоит в подключении всего, что нужно команде, в разметке XAML.

В первую очередь нужно связать пространство имен проекта с префиксом XML, чтобы класс создаваемой пользовательской команды был доступен в разметке. Затем следует добавить команду как источник в ресурсы приложения. И наконец, нужно установить свойство Command кнопки с помощью ресурса и свойство CommandParameter путем связывания данных. В данном примере свойство CommandParameter извлекает текст из текстового поля. В приведенном ниже примере страница называется MainPage, а приложение — SilverlightTest:

<UserControl x:Class="SilverlightTest.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:SilverlightTest">
    <UserControl.Resources>
        <local:PrintTextCommand x:Key="printCommand"/>
    </UserControl.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Button Margin="5" Content="Напечатать фразу" Command="{StaticResource printCommand}" 
                CommandParameter="{Binding ElementName=txt, Path=Text}"/>
        <TextBox x:Name="txt" Grid.Row="1" Margin="5" TextWrapping="Wrap"/>
    </Grid>
</UserControl>

На следующем рисунке показаны два состояния кнопки. Когда в текстовом поле нет текста, команда не может быть выполнена, и кнопка автоматически отключается. Когда текст есть, кнопка автоматически подключается. В этот момент можно щелкнуть на ней, и будет выполнен метод PrintTextCommand.Execute():

Отключенное (слева) и подключенное (справа) состояния команды

Автоматическое управление состояниями — прекрасное средство, но оно ограничено подключением и отключением элементов управления. Между тем легко представить себе другие ситуации, в которых оно было бы полезным, например установка флажка или сокрытие элемента списка, когда условие становится равным true. К сожалению, в текущей реализации Silverlight без дополнительного кодирования это невозможно.

Реальное преимущество командной модели — возможность реализовать команды без скучного кода обработки событий. В рассмотренном выше примере для производного класса не пришлось написать вручную ни одной строки кода! Так и было задумано при создании шаблона MVVM, однако в Silverlight была бы полезной более мощная модель, в которой команды являются частью модели представлений и предоставляются посредством свойств наряду с другими командами, используемыми на странице.

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

Пройди тесты
Лучший чат для C# программистов