Многие элементы управления в WPF являются элементами управления содержимым, которые могут иметь разный тип и разный объем вложенного содержимого. Например, можно собрать графическую кнопку из отдельных графических элементов, создать метку, которая будет совмещать текст и рисунки, или поместить содержимое в специальный контейнер, чтобы его можно было прокручивать или сворачивать. И такой процесс “вкладывания” можно повторять столько раз, сколько уровней нужно получить.
При этом возникает интересный вопрос. Например, предположим, что имеется метка, в которой имеется панель StackPanel, содержащая два текстовых блока и изображение:
<Label BorderBrush="LightBlue" BorderThickness="3" Margin="5" HorizontalAlignment="Center">
<StackPanel>
<TextBlock Margin="3" FontSize="13">
Всем привет!
</TextBlock>
<Image Source="grimace.png" Width="80" Height="80" ></Image>
<TextBlock Margin="3" FontSize="13">
Маршрутизация событий
</TextBlock>
</StackPanel>
</Label>
Как вам уже известно, каждый ингредиент, помещаемый в окно WPF, так или иначе является наследником класса UIElement, включая Label, StackPanel, TextBlock и Image. Класс UIElement определяет несколько ключевых событий. Например, каждый класс, являющийся потомком UIElement, обеспечивает события MouseUp и MouseDown.
А теперь подумайте, что произойдет при щелчке на изображении в такой метке. Понятно, что при этом возникнут события Image.MouseDown и Image.MouseUp. А если вам нужно обрабатывать все щелчки на метке одинаковым образом? То есть неважно, где щелкнул пользователь: на изображении, на тексте или на пустом месте в области метки. В любом из этих случаев нужно реагировать на щелчок с помощью одного и того же кода.
Понятно, что к событиям MouseDown и MouseUp каждого элемента можно привязать один и тот же обработчик, однако это может загромоздить код и усложнить сопровождение разметки. WPF предлагает более удобное решение с помощью модели маршрутизируемых событий.
Маршрутизируемые события бывают трех видов:
- Прямые (direct) события
- Подобны обычным событиям .NET. Они возникают в одном элементе и не передаются в другой. Например, прямым является событие MouseEnter, которое возникает, когда указатель мыши наводится на элемент.
- Пузырьковые (bubbling) события
- Поднимаются по иерархии содержания. Например, пузырьковым событием является MouseDown. Оно возникает в элементе, на котором был произведен щелчок, потом передается от этого элемента к родителю, затем к родителю этого родителя, и т.д., пока WPF не достигнет вершины дерева элементов.
- Туннелируемые (tunneling) события
- Опускаются по иерархии содержания. Они позволяют предварительно просматривать (и, возможно, останавливать) событие, прежде чем оно дойдет до подходящего элемента управления. Например, PreviewKeyDown позволяет перехватить нажатие клавиши, сначала на уровне окна, а затем в более специфических контейнерах, вплоть до элемента, содержавшего фокус в момент нажатия клавиши.
При регистрации маршрутизируемого события с помощью метода EventManager.RegisterEvent() ему передается значение из перечисления RoutingStrategy, которое задает необходимое поведение для события.
Поскольку события MouseUp и MouseDown являются пузырьковыми событиями, вы уже можете определить, что произойдет в примере с составной меткой. При щелчке на довольном смайлике событие MouseDown возникнет в следующем порядке:
- Image.MouseDown
- StackPanel.MouseDown
- Label.MouseDown
После того как событие MouseDown возникнет в метке, оно передается следующему элементу управления (в данном случае это сетка Grid для разметки вмещающего окна), а затем его родителю (окно). Окно находится на самом верху иерархии содержания и в самом конце в последовательности пузырькового распространения события. Здесь последний шанс обработать пузырьковое событие наподобие MouseDown. Если пользователь отпускает кнопку мыши, в такой же последовательности возникает событие MouseUp.
Пузырьковые события не обязательно обрабатывать в одном месте: например, ничто не мешает обрабатывать события MouseDown и MouseUp на каждом уровне. Однако, как правило, для каждой задачи выбирается наиболее подходящая маршрутизация событий.
Класс RoutedEventArgs
При обработке пузырькового события параметр отправителя содержит ссылку на последнее звено в цепочке. Например, если событие перед обработкой всплывает от изображения до метки, то параметр отправителя будет ссылаться на объект метки.
В некоторых случаях требуется знать, где первоначально произошло событие. Эту информацию, а также другие подробности, можно получить из свойств класса RoutedEventArgs (которые перечислены ниже). Поскольку все классы аргументов событий WPF являются наследниками RoutedEventArgs, эти свойства доступны в любом обработчике события.
| Имя | Описание |
|---|---|
| Source | Указывает, какой объект сгенерировал событие. Если речь идет о событии клавиатуры, то это элемент управления, имевший фокус ввода в момент возникновения события (например, когда была нажата клавиша). В случае события мыши это самый верхний элемент под указателем мыши в момент возникновения события (например, когда был произведен щелчок кнопкой мыши). |
| OriginalSource | Указывает, какой объект первоначально сгенерировал событие. Как правило, совпадает с Source. Однако в некоторых случаях OriginalSource спускается глубже по дереву объектов, чтобы дойти до внутреннего элемента, являющегося частью элемента более высокого уровня. Например, если вы щелкнете кнопкой мыши близко к границе окна, то получите объект Window в качестве источника события и Border в качестве первоначального источника. Это объясняется тем, что Window состоит из отдельных меньших элементов. Чтобы разобраться с этой сборной моделью более детально (и узнать, как ее можно изменить), обратитесь к шаблонам элементов управления. |
| RoutedEvent | Предоставляет объект RoutedEvent для события, сгенерированного вашим обработчиком события (например, статический объект UIElement.MouseUpEvent). Эта информация бывает полезна при обработке разных событий одним и тем же обработчиком. |
| Handled | Позволяет остановить процесс пузырькового распространения или туннелирования события. Если элемент управления заносит в свойство Handled значение true, событие прекращает продвижение и не будет возникать в любых других элементах. |