属性更改事件

Windows Presentation Foundation (WPF) 定义几个为响应属性值的更改而引发的事件。 该属性通常是依赖项属性。 事件本身有时是路由事件,有时是标准公共语言运行时 (CLR) 事件。 事件的定义因具体情况而异,因为有些属性更改更适于通过元素树路由,而其他属性更改则通常只与属性发生更改的对象有关。

标识属性更改事件

并非所有报告属性更改的事件都通过签名模式或命名模式显式地标识为属性更改事件。 通常,SDK 文档中的事件描述会指出事件是否直接与属性值更改相关,以及是否提供属性与事件之间的交叉引用。

RoutedPropertyChanged 事件

某些事件使用显式用于属性更改事件的事件数据类型和委托。 事件数据类型为 RoutedPropertyChangedEventArgs<T>,委托为 RoutedPropertyChangedEventHandler<T>。 事件数据和委托都具有用于在定义处理程序时指定更改属性实际类型的泛型类型参数。 事件数据包含两个属性 OldValueNewValue,它们稍后都将作为事件数据中的类型参数传递。

名称中的“Routed”部分表示属性更改事件注册为路由事件。 路由属性更改事件的好处是,如果子元素(控件的复合部分)上的属性更改了值,控件的顶层可以接收到属性更改事件。 例如,可以创建一个包含 RangeBase 控件的控件,例如 Slider。 如果 Value 属性的值在滑块部分发生更改,则可能需要在父控件(而非该部分)上处理此更改。

因为有一个旧值和一个新值,所以很可能使用此事件处理程序作为属性值的验证程序。 但是,这不是大多数属性更改事件的设计意图。 通常,提供这些值是为了能够在代码的其他逻辑领域处理这些值,但实际上,在事件处理程序内更改值并不明智,并且可能导致意外的递归,具体取决于处理程序的实现方式。

如果属性是自定义依赖属性,或者处理的是定义了实例化代码的派生类,则有一种更好的机制来跟踪属性更改,并且此机制是内置于 WPF 属性系统中的,即:属性系统回调 CoerceValueCallbackPropertyChangedCallback。 若要深入了解如何使用 WPF 属性系统进行验证和强制转换,请参阅依赖属性回调和验证自定义依赖属性

DependencyPropertyChanged 事件

属性更改事件场景中的另一对类型是 DependencyPropertyChangedEventArgsDependencyPropertyChangedEventHandler。 这些属性更改的事件不会路由;它们是标准 CLR 事件。 DependencyPropertyChangedEventArgs 是一种罕见的事件数据报告类型,因为它不派生自 EventArgsDependencyPropertyChangedEventArgs 是一个结构,而不是一个类。

使用 DependencyPropertyChangedEventArgsDependencyPropertyChangedEventHandler 的事件比 RoutedPropertyChanged 事件稍微常见一些。 使用这些类型的事件的一个示例是 IsMouseCapturedChanged

RoutedPropertyChangedEventArgs<T> 一样,DependencyPropertyChangedEventArgs 也会报告属性的新旧值。 此外,关于如何处理这些值的注意事项也同样适用;通常不建议为响应事件而尝试在发送方再次更改值。

属性触发器

与属性更改事件密切相关的一个概念是属性触发器。 属性触发器是在样式或模板内创建的,通过它可以创建基于分配了属性触发器的属性的值的条件行为。

属性触发器的属性必须是依赖项属性。 它可以是(且通常是)只读依赖项属性。 判断控件公开的依赖项属性是否至少部分设计为属性触发器的一个好方法是查看属性名称是否以“Is”开头。 采用此命名规则的属性通常是只读的布尔依赖项属性,其属性的主要作用是报告可能与实时 UI 具有因果关系的控件状态,因此它是一个属性触发器候选。

其中一些属性还具有专用属性更改事件。 例如,属性 IsMouseCaptured 具有属性更改事件 IsMouseCapturedChanged。 该属性本身是只读的,其值由输入系统调整,并且输入系统对每次实时更改都引发 IsMouseCapturedChanged

与真正的属性更改事件相比,使用属性触发器来处理属性更改具有一些限制。

属性触发器通过完全匹配逻辑来工作。 指定一个属性和一个值,该值指示触发器将执行操作的特定值。例如:<Setter Property="IsMouseCaptured" Value="true"> ... </Setter>。 由于此限制,大多数属性触发器用法都将针对布尔属性或者采用专用枚举值的属性,其可能值范围是可管理的,足以为每种情况定义一个触发器。 或者属性触发器可能仅仅用于特殊值,例如,当项计数达到零,却没有触发器来处理当属性值再次从零变为其他值(此处可能需要代码事件处理程序,或者当值不为零时再次从触发器状态切换回来的默认行为,而非针对所有情况的触发器)。

属性触发器语法与编程中的“if”语句类似。 如果触发器条件为 true,将“执行”属性触发器的“主体”。 属性触发器的“主体”是标记,而不是代码。 该标记被限制为只能使用一个或多个 Setter 元素来设置正在应用样式或模板的对象的其他属性。

为了抵消具有各种可能值的属性触发器的“if”条件,通常建议使用 Setter 将此相同的属性值设置为默认值。 这样,当触发器条件为 true 时,Trigger 包含的 setter 将具有优先级,而只要触发器条件为 false,则不在 Trigger 内的 Setter 就具有优先级。

属性触发器通常适合于一个或多个外观属性应基于同一元素的其他属性的状态而更改的情况。

若要深入了解属性触发器,请参阅样式设置和模板化

另请参阅