属性更改事件 (WPF .NET)

Windows Presentation Foundation (WPF) 定义几个为响应属性值的更改而引发的事件。 该属性通常是依赖项属性。 事件本身可以是路由事件,也可以是标准公共语言运行时 (CLR) 事件,具体取决于事件是应通过元素树路由,还是仅在属性发生更改的对象上发生。 当属性更改仅与属性值发生更改的对象相关时,后一种方案适用。

重要

面向 .NET 7 和 .NET 6 的桌面指南文档正在撰写中。

先决条件

本文假定你对依赖有基本的了解,并且已阅读路由事件概述

标识属性更改事件

并非所有报告属性更改的事件都通过签名或命名模式显式地标识为属性更改事件。 SDK 文档交叉引用事件和属性,并指示事件是否直接与属性值更改相关。

某些事件使用特定于属性更改事件的事件数据类型和委托。 例如,RoutedPropertyChangedDependencyPropertyChanged 事件都有特定的签名。 以下几节中讨论了这些事件类型。

RoutedPropertyChanged 事件

RoutedPropertyChanged 事件具有 RoutedPropertyChangedEventArgs<T> 事件数据和 RoutedPropertyChangedEventHandler<T> 委托。 事件数据和委托都有泛型类型参数 T。 定义处理程序时,指定更改的属性的实际类型。 事件数据包含 OldValueNewValue 属性,其运行时类型与更改的属性相同。

名称中的“Routed”部分表示属性更改事件注册为路由事件。 属性更改路由事件的优势在于,每当子元素属性发生更改时,父元素都会收到通知。 这意味着当控件的任何复合部件的值发生更改时,控件的顶层元素会接收属性更改事件。 例如,假设创建一个合并 RangeBase 控件的控件,例如 Slider。 如果 Value 属性的值在滑块部分发生更改,你可在父控件(而非该部分)上处理此更改。

避免使用属性更改事件处理程序来验证属性值,因为这不是大多数属性更改事件的设计意图。 通常,提供属性更改事件是为了你能够在代码的其他逻辑区域响应值更改。 在属性更改事件处理程序内再次更改属性值并不明智,并且可能导致意外的递归,具体取决于处理程序的实现方式。

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

DependencyPropertyChanged 事件

DependencyPropertyChanged 事件具有 DependencyPropertyChangedEventArgs 事件数据和 DependencyPropertyChangedEventHandler 委托。 这些事件是标准 CLR 事件,而不是路由事件。 DependencyPropertyChangedEventArgs 不是通常的事件数据报告类型,因为它不派生自 EventArgs,而且它是一个结构,并非一个类。

DependencyPropertyChanged 事件的一个示例是 IsMouseCapturedChangedDependencyPropertyChanged 事件比 RoutedPropertyChanged 事件稍微常见一些。

与 RoutedPropertyChanged 事件数据类似,DependencyPropertyChanged 事件数据包含 OldValueNewValue 属性。 出于前面提到的原因,请避免使用属性更改事件处理程序再次更改属性值。

属性触发器

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

属性触发器操作的属性必须是依赖属性。 它可以是(且通常是)只读依赖属性。 如果控件公开的依赖属性的名称以“Is”开头,则表明该属性至少部分设计为属性触发器。 采用此命名规则的属性通常是只读的 Boolean 依赖属性,其属性的主要作用是报告控件状态。 如果控件状态影响实时 UI,则该依赖属性是一个属性触发器候选项。

有些属性具有专用属性更改事件。 例如,IsMouseCaptured 具有 IsMouseCapturedChanged 属性更改事件。 IsMouseCaptured 属性是只读的,其值由输入系统修改。 输入系统在每次实时更改时都将引发 IsMouseCapturedChanged 事件。

属性触发器限制

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

属性触发器通过完全匹配逻辑来工作,在该逻辑中指定将激活触发器的属性名称和特定值。 示例为 <Setter Property="IsMouseCaptured" Value="true"> ... </Setter>。 属性触发器语法限制大多数属性触发器用于 Boolean 属性或采用专用枚举值的属性。 可能值的范围必须可管理,这样你才能为每种情况定义一个触发器。 有时,属性触发器仅针对特殊值存在,例如当项计数达到零时。 单个触发器不能设置为在属性值偏离特定值(如零)时激活。 请考虑实现代码事件处理程序,或实现在值不为零时从触发器状态切换回来的默认行为,而不是针对所有非零情况使用多个触发器。

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

当属性触发器的“if”条件具有各种可能值时,建议使用触发器外的 Setter 将此相同的属性值设置为默认值。 这样,当触发器条件为 true 时,触发器内的 setter 将优先,否则触发器外的 Setter 将优先。

对于一个或多个外观属性应基于同一元素的其他属性的状态而更改的情况,属性触发器非常有用。

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

另请参阅