WPF附加事件与非附加事件

31
问题在于,尽管我进行了大量的研究,仍然找不到常规路由事件和附加事件之间的区别。它们有什么功能上的差异?或者其他人认为它们没有区别吗?
实现
ButtonBase类声明了一个名为ClickEvent的路由事件,这是一个普通的路由事件。
public static readonly RoutedEvent ClickEvent = EventManager.RegisterRoutedEvent("Click", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ButtonBase));

[Category("Behavior")]
public event RoutedEventHandler Click
{
    add
    {
        base.AddHandler(ClickEvent, value);
    }
    remove
    {
        base.RemoveHandler(ClickEvent, value);
    }
}

Mouse类声明了一个名为MouseDownEvent的路由事件;这是一个附加事件。

public static readonly RoutedEvent MouseDownEvent = EventManager.RegisterRoutedEvent("MouseDown", RoutingStrategy.Bubble, typeof(MouseButtonEventHandler), typeof(Mouse));

public static void AddMouseDownHandler(DependencyObject element, MouseButtonEventHandler handler)
{
    UIElement.AddHandler(element, MouseDownEvent, handler);
}

public static void RemoveMouseDownHandler(DependencyObject element, MouseButtonEventHandler handler)
{
    UIElement.RemoveHandler(element, MouseDownEvent, handler);
}

两个事件都在EventManager中注册,并以相同的方式作为公共、静态和只读字段存储。ClickEvent具有支持CLR事件字段,其自定义add和remove访问器调用base.AddHandler和base.RemoveHandler,这两个访问器在ButtonBase派生类继承的UIElement基类中声明。而MouseDownEvent则具有两个静态方法AddMouseDownHandler和RemoveMouseDownHandler,最终调用与ClickEvent相同的两个在UIElement中声明的AddHandler和RemoveHandler方法。

在静态类上声明的实际附加事件的Add*Handler和Remove*Handler静态方法必须遵循特定的命名约定,以便WPF事件系统使用反射在运行时找到适当的添加和删除处理程序。


用法

在XAML中,这两个事件都可以按照以下方式附加处理程序:

<Grid Button.Click="Grid_Click"
      Mouse.MouseDown="Grid_MouseDown">
</Grid>

以下是如何在代码中附加这两个事件:

// Attach ClickEvent handler.
myGrid.AddHandler(Button.ClickEvent, new RoutedEventHandler(Grid_Click));

// Attach MouseDownEvent handler.
Mouse.AddMouseDownHandler(myGrid, Grid_MouseDown);

正如您所看到的,这两个事件都可以附加到不拥有或声明它们的元素上。


结论 - 什么是附加事件?

MSDN文档说明: http://msdn.microsoft.com/en-us/library/bb613550.aspx

可扩展应用程序标记语言(XAML)定义了一种称为附加事件的语言组件和事件类型。附加事件的概念使您能够将特定事件的处理程序添加到任意元素,而不是添加到实际定义或继承事件的元素。在这种情况下,潜在引发事件的对象和目标处理实例都不定义或以其他方式“拥有”该事件。

此外,官方的MCTS培训套件70-511考试 - 使用Microsoft .NET Framework 4开发Windows应用程序也指出:

一个控件可以定义对其自身无法引发的事件的处理程序。这些事件称为附加事件。例如,在网格中的按钮控件。Button类定义了Click事件,但Grid类没有。但是,您仍然可以通过在XAML代码中附加Button控件的Click事件来定义网格中的按钮的处理程序。

“附加事件”这个术语在微软的学习资源中似乎有些模糊,尽管很明显这里涉及到两个不同但非常密切相关的概念:附加事件和XAML附加事件语法。我引用的两个微软资源似乎都指的是XAML附加事件语法,而不是实际的附加事件。然而,MSDN页面上的“附加事件概述”确实会告诉你如何实现一个实际的附加事件,而培训套件则不会。
Mouse.MouseDownEvent是一个静态类声明的路由事件示例,具有相应的静态add和remove处理程序,也称为附加事件。然而,ButtonBase.ClickEvent是一个普通的路由事件,虽然它仍然可以像实际的附加事件一样使用XAML附加事件语法。
实际附加事件的目的是允许开发人员为现有的UIElement派生类声明新的路由事件,而无需对它们进行子类化;这意味着您可以只附加新的路由事件,而不必将它们实际存在于您想要在其上引发或处理它们的类中。但是,等一下...这不是纯路由事件的主要目的吗?
MSDN上的路由事件概述页面说明:http://msdn.microsoft.com/en-us/library/ms742806.aspx 功能定义:路由事件是一种事件类型,可以在元素树中的多个侦听器上调用处理程序,而不仅仅是在引发事件的对象上。
从这个功能定义来看,任何路由事件本质上都提供与附加事件完全相同的功能。因此,基本上,附加事件只是在静态类上声明路由事件的一种手段,并没有比普通路由事件更多的好处。

请告诉我您的想法,因为我可能会漏掉某些东西。

谢谢, Tim Valentine


3
附加事件是一种可以“附加”到任何对象而不仅仅是定义事件的对象上的事件。路由事件是一种可以被路由到非对象自身处理程序的事件。一个事件可以同时是路由事件和附加事件。例如,“Button.Click”就是一个附加事件,因为你可以将该事件附加到除“Button”对象以外的其他对象上。它也是一个路由事件,因为它可以在UI树中由多个“Button.Click”事件处理程序处理,除非你阻止这种行为,例如在其中一个处理程序中标记事件已处理。 - Rachel
顺便提一下,这个问题很好地解释了如何从C#而不是XAML处理附加事件。大多数地方都显示后者而不是前者(例如M$文档,但也包括WPF Cookbook(这是一本非常好的书!))。 - z33k
关于这个问题,我绝不是专家,但附加事件不就是在控件上处理的路由事件吗?它会冒泡到顶层并不是在原始源上处理。那么这两个名称只是适用于不同的上下文中的同一件事情。 - z33k
2个回答

5
区别主要在于语法,两者委托引用都由WPF的EventManager处理,但是附加事件使您能够声明通用功能,而无需使所有类的实现过于臃肿。对于普通路由事件,类提供了接口,以便在某个时刻通过调用事件处理程序来响应事件。但是,WPF只需要知道它是否是从给定类型派生的对象,并且是否已注册处理程序。这意味着我们可以制作更简单的类层次结构,并支持开放封闭原则(对扩展开放,对修改关闭)。这样,程序员可以定义多个类应具有的新行为,但不需要修改原始类。另请参见附加属性

1
将评论复制到答案中,以便不会最终丢失:
附加事件是可以附加到任何对象的事件,而不仅仅是定义事件的对象。
路由事件是可以路由到不属于该对象的处理程序的事件。
一个事件既可以是路由事件,也可以是附加事件。例如,Button.Click 是一个附加事件,因为您可以将该事件附加到除 Button 对象之外的对象上。它也是一个路由事件,因为它可以被 UI 树中多个 Button.Click 事件处理程序处理,除非您停止此行为,例如在其中一个处理程序中将事件标记为已处理。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接