事件如何在wpf中传播?

时间:2014-02-20 02:00:38

标签: c# wpf routed-events

我想确定我了解事件是如何传播的。以下是正确的吗?

例如,让我们检查在按钮内单击鼠标左键时如何调用按钮的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中找不到它)

2 个答案:

答案 0 :(得分:2)

RoutedEvents有点像那样。你给我们的例子是一个很好的演示,基本上是routedevents路由。

然而,引擎盖下还有更多内容。它并不那么简单。我会举几个例子。

当被触发时,事件可以在LogicalTree和VisualTree之间切换以找到它的路由,因为路径可以通过不从FrameworkElement继承或甚至不是Visual的数据来定义。在这种情况下,您可以使用隧道策略来反转路径,否则路由事件默认会冒泡。即使事件处理状态为真,也可以访问路由节点。事件的args包含的不仅仅是e.Source,e.OriginalSource和e.Handled。 args包含有关上一个和下一个节点的信息。 args futhermore可能包含另一个routedevents的列表,一旦某个节点到达,它们就会被触发。

没有属于特定路由事件的所有可用处理程序的大列表。寻找处理程序也是一种在内部发生的逻辑,因为您可以动态地更改模板或布局面板。

正如你所看到的那样,内部发生了100亿件其他事情并且非常复杂:)

你的例子是一个很好的演示,不要担心你有正确的routedevents的基础知识。无论如何,你不需要知道所有这些内部事物。只要它有效......

答案 1 :(得分:1)

逻辑实际上与您的建议略有不同。例如,即使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.
    }
}

路线取决于事件路由策略是BubbleTunnel还是Direct。本质上,路由是通过从源元素开始并确定其父元素,然后填充两者之间的任何中间节点并继续向上树构建的。这条路线只是针对隧道事件而逆转。

正如@devhedgehog在他的回答中指出的那样,该过程并不严格遵循视觉或逻辑树;概念父可以是视觉父,逻辑父,内容父等。它可以在2D和3D空间之间交叉,从文档到可视树,等等。