Маршрутизируемые события

80

Каждый разработчик, работающий в .NET, знаком с понятием события: это сообщение, которое посылается объектом (например, элементом WPF) для уведомления кода о том, что произошло что-то важное. WPF дополняет модель событий .NET новой концепцией маршрутизации событий. Маршрутизация позволяет событию возникать в одном элементе, а генерироваться в другом: например, щелчок на кнопке панели инструментов генерируется в панели инструментов, а затем в содержащем эту панель окне, и только тогда передается на обработку коду.

Маршрутизируемые события — это события с большими транспортными возможностями: они могут туннелироваться вниз и распространяться пузырьками наверх по дереву элементов и по пути запускать обработчики событий. Маршрутизируемые события позволяют обработать событие в одном элементе (например, в метке), хотя оно возникло в другом (например, в изображении внутри этой метки). Как и в случае свойств зависимости, маршрутизируемые события можно употреблять и традиционным способом — подключив обработчик событий с нужной сигнатурой — но все равно необходимо понимать принципы их работы, чтобы задействовать все их возможности.

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

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

Например, WPF-класс Button предлагает знакомое событие Click, являющееся потомком абстрактного класса ButtonBase. Ниже показано, как определяется и регистрируется это событие:

public abstract class ButtonBase : ContentControl, ...
{
   // Определение события
   public static readonly RoutedEvent ClickEvent;
   
   // Регистрация события
   static ButtonBase()
   {
      ButtonBase.ClickEvent = EventManager.RegisterRoutedEvent(
         "Click", RoutingStrategy.Bubble,
         typeof(RoutedEventHandler), typeof(ButtonBase));
   }
   
   // Традиционная оболочка события
   public event RoutedEventHandler Click
   {
      add
      {
         base.AddHandler(ButtonBase.ClickEvent, value);
      }
      remove
      {
         base.RemoveHandler(ButtonBase.ClickEvent, value);
      }
   }
   ...
}

Свойства зависимости регистрируются посредством метода DependencyProperty.Register(), а для регистрации маршрутизируемых событий предназначен метод EventManager.RegisterRoutedEvent(). При регистрации события нужно указать имя события, тип маршрутизации (об этом чуть позже), делегат, определяющий синтаксис обработчика события (в данном примере это RoutedEventHandler), и класс, которому принадлежит событие (в данном примере ButtonBase).

Как правило, маршрутизируемые события упаковываются в обычные события .NET, чтобы сделать их доступными для всех языков .NET. Оболочка события добавляет и удаляет зарегистрированные вызывающие объекты с помощью методов AddHandler() и RemoveHandler(), которые определены в базовом классе FrameworkElement и наследуются каждым элементом WPF.

Как и в случае свойств зависимости, определение маршрутизируемых событий можно совместно использовать несколькими классами. К примеру, событие MouseUp используют два базовых класса: UIElement (начальная точка для обычных элементов WPF) и ContentElement (начальная точка для элементов контента — отдельных частей содержимого, которые могут помещаться в документе потока). Событие MouseUp определено в классе System.Windows.Input.Mouse. Классы UIElement и ContentElement просто используют его с помощью метода RoutedEvent.AddOwner():

UIElement.MouseUpEvent = Mouse.MouseUpEvent.AddOwner(typeof(UIElement));

Генерация маршрутизируемого события

Конечно, как и любое событие, определяющий класс должен где-то сгенерировать маршрутизируемое событие. Где именно — это уже детали реализации. Однако следует помнить, что ваше событие не возбуждается через традиционную оболочку событий .NET. Вместо этого используется метод RaiseEvent(), наследуемый каждым элементом от класса UIElement. Ниже представлен соответствующий код класса ButtonBase:

RoutedEventArgs е = new RoutedEventArgs(ButtonBase.ClickEvent, this);
base.RaiseEvent(e);

Метод RaiseEvent() отвечает за генерацию события для каждого вызывающего объекта, который был зарегистрирован с помощью метода AddHandler(). Поскольку этот метод является общедоступным, вызывающим объектам предоставляется выбор: они могут зарегистрироваться напрямую, с помощью метода AddHandler(), либо воспользоваться оболочкой события.

В любом случае они будут уведомлены о вызове метода RaiseEvent(). Все события WPF придерживаются знакомого вам условия о сигнатурах событий, существующего в .NET. Первый параметр каждого обработчика события содержит ссылку на объект, который сгенерировал событие (отправитель). Второй параметр — объект EventArgs, объединяющий все дополнительные детали, которые могут понадобиться.

Например, событие MouseUp предоставляет объект MouseEventArgs, который показывает, какая кнопка мыши была нажата при возникновении события.

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

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

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