我想确定我了解事件是如何传播的。以下是正确的吗?
例如,让我们检查在按钮内单击鼠标左键时如何调用按钮的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中找不到它)
答案 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.
}
}
路线取决于事件路由策略是Bubble
,Tunnel
还是Direct
。本质上,路由是通过从源元素开始并确定其父元素,然后填充两者之间的任何中间节点并继续向上树构建的。这条路线只是针对隧道事件而逆转。
正如@devhedgehog在他的回答中指出的那样,该过程并不严格遵循视觉或逻辑树;概念父可以是视觉父,逻辑父,内容父等。它可以在2D和3D空间之间交叉,从文档到可视树,等等。