Маршрутизация событий

86

Многие элементы управления в 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 возникнет в следующем порядке:

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

Пузырьковые события не обязательно обрабатывать в одном месте: например, ничто не мешает обрабатывать события MouseDown и MouseUp на каждом уровне. Однако, как правило, для каждой задачи выбирается наиболее подходящая маршрутизация событий.

Класс RoutedEventArgs

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

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

Свойства класса RoutedEventArgs
Имя Описание
Source Указывает, какой объект сгенерировал событие. Если речь идет о событии клавиатуры, то это элемент управления, имевший фокус ввода в момент возникновения события (например, когда была нажата клавиша). В случае события мыши это самый верхний элемент под указателем мыши в момент возникновения события (например, когда был произведен щелчок кнопкой мыши).
OriginalSource Указывает, какой объект первоначально сгенерировал событие. Как правило, совпадает с Source. Однако в некоторых случаях OriginalSource спускается глубже по дереву объектов, чтобы дойти до внутреннего элемента, являющегося частью элемента более высокого уровня. Например, если вы щелкнете кнопкой мыши близко к границе окна, то получите объект Window в качестве источника события и Border в качестве первоначального источника. Это объясняется тем, что Window состоит из отдельных меньших элементов. Чтобы разобраться с этой сборной моделью более детально (и узнать, как ее можно изменить), обратитесь к шаблонам элементов управления.
RoutedEvent Предоставляет объект RoutedEvent для события, сгенерированного вашим обработчиком события (например, статический объект UIElement.MouseUpEvent). Эта информация бывает полезна при обработке разных событий одним и тем же обработчиком.
Handled Позволяет остановить процесс пузырькового распространения или туннелирования события. Если элемент управления заносит в свойство Handled значение true, событие прекращает продвижение и не будет возникать в любых других элементах.
Лучший чат для C# программистов