Как события распространяются в wpf?

Я хочу быть уверенным, что понял, как распространяются события. Правильно ли приведенное ниже?

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

Кнопка регистрирует событие Click:

public class Button : ButtonBase
{
  public static readonly RoutedEvent ClickEvent = EventManager.RegisterRoutedEvent("Click",
      RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Button));

  public event RoutedEventHandler Click
  {
    add { AddHandler(ClickEvent, value); }
    remove { RemoveHandler(ClickEvent, value); }
  }

  protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
  {
    ...       
    RaiseEvent(new RoutedEventArgs(ClickEvent, this));
    ...
  }
  ...
}
  • EventManager.RegisterRoutedEvent создает перенаправленное событие с именем Click и добавляет его в коллекцию обработчиков событий кнопки с именем EventHandlersStore. Я считаю, что коллекция (назовем ее _routedEvents) по типу похожа на Dictionary<RoutedEvent, RoutedEventHandler>. Итак, RegisterRoutedEvent делает _routedEvents.Add(ClickEvent, null).
  • AddHandler добавляет обработчик к ClickEvent записи в EventHandlersStore. Если никто не подписался на событие Click, обработчик для ClickEvent остается null.

Теперь, когда вызывается RaiseEvent в OnMouseLeftButtonDown, вот что происходит и как событие маршрутизируется в соответствии с моим пониманием:

void RaiseEvent(RoutedEventArgs e)
{        
    DependencyObject current = this;    
    do
    {
        // check if the element has handler for routed event
        var handler = current.GetHandlerFromEventHandlersStore(e.RoutedEvent);
        if (handler != null)
        {
            handler(e);
        }

        // the event was NOT handled -> route the event to the parent
        // OR
        // the event was handled but wasn't marked as handled -> route the event further to parent
        if (e.Handled == false)
        {
            // assuming that RoutingStrategy is Bubble
            current = VisualTreeHelper.GetParent(current);
        }

     // continue until either it has been handled or it reaches the root element
    } while (e.Handled == false && current != null);
} 

Буду признателен, если кто-нибудь сможет меня поправить, если я ошибаюсь, а также расскажет, как вызывается OnMouseLeftButtonDown (я не смог найти его в resharper)


person theateist    schedule 20.02.2014    source источник


Ответы (2)


RoutedEvents работает вроде как так. Пример, который вы нам дали, является хорошей демонстрацией того, как в основном routedevents.

Однако под капотом есть еще кое-что. Это не так просто. Приведу несколько примеров.

При запуске событие может переключаться между LogicalTree и VisualTree, чтобы найти свой маршрут, поскольку путь может быть определен через данные, которые не наследуются от FrameworkElement или даже не являются визуальными. Путь может быть обратным, если у вас есть стратегия туннелирования, иначе перенаправленные события всплывают по умолчанию. При маршрутизации узлы можно посещать, даже если состояние обработки события истинно. Аргументы события содержат гораздо больше, чем просто e.Source, e.OriginalSource и e.Handled. Аргументы содержат информацию о предыдущем и следующем узлах. Кроме того, аргументы могут содержать список других событий маршрута, которые должны запускаться при достижении определенного узла.

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

Как видите, внутри компании происходит около 10 миллиардов других вещей, и это довольно сложно :)

Ваш пример - хорошая демонстрация, и не волнуйтесь, вы правильно поняли основы routedevents. В любом случае вам не нужно знать все эти внутренние вещи. Пока работает ...

person dev hedgehog    schedule 20.02.2014

На самом деле логика немного отличается от того, что вы предлагаете. Например, вы продолжите туннелирование или пузырение даже после того, как для e.Handled было установлено значение true. Однако вы будете вызывать только обработчики, зарегистрированные в handledEventsToo = true.

Также есть несколько обработчиков на узел. Могут существовать обработчики классов, зарегистрированные с EventManager.RegisterClassHandler(), которые фактически являются статическими обработчиками, совместно используемыми всеми экземплярами данного типа элемента. Затем есть обычные обработчики экземпляров, зарегистрированные с помощью метода AddHandler() элемента.

Логика распространения событий выглядит примерно так (псевдокод):

void RaiseEvent(RoutedEventArgs e) {
    EventRoute route = BuildEventRoute(e);
    RouteNode currentNode = route.Head;

    while (currentNode != null) {
        DependencyObject current = currentNode.Element;

        // Update the Source of the RoutedEventArgs (OriginalSource remains the same).
        e.Source = current;

        // Invoke class handlers for current node, if any exist.
        foreach (var handler in GetClassHandlers(e)) {
            if (!e.Handled || handler.HandledEventsToo) {
                handler.Invoke(e);
            }
        }

        // Invoke instance handlers for current node, if any exist.
        foreach (var handler in GetInstanceHandlers(e)) {
            if (!e.Handled || handler.HandledEventsToo) {
                handler.Invoke(e);
            }
        }

        currentNode = currentNode.Next;
        // Continue until we reach the end of the route.
    }
}

Маршрут зависит от того, является ли стратегия маршрутизации событий Bubble, Tunnel или Direct. По сути, маршрут строится, начиная с исходного элемента и определяя его родительский элемент, затем заполняя любые промежуточные узлы между ними и продолжая движение вверх по дереву. Для событий туннелирования маршрут просто меняется на противоположный.

Как отмечает @devhedgehog в своем ответе, процесс не строго следует визуальному или логическому дереву; концептуальный родитель может быть визуальным родителем, логическим родителем, родительским элементом контента и т. д. Он может пересекаться между 2D и 3D пространством, из документа - визуальным деревом и т. д.

person Mike Strobel    schedule 20.02.2014