属性值继承 (WPF .NET)
属性值继承是 Windows Presentation Foundation (WPF) 属性系统的一项功能,适用于依赖属性。 属性值继承允许元素树中的子元素从最近的父元素获取特定属性的值。 由于父元素可能还通过属性值继承获取其属性值,系统可能会重新返回页根。
WPF 属性系统默认不启用属性值继承,除非在依赖属性中专门启用 元数据,否则值继承处于非活动状态。 即使启用了属性值继承,子元素也只会在缺少较高 优先级 值的情况下继承属性值。
先决条件
本文假设对依赖属性有一个基本的了解,并且你已阅读 依赖项属性概述。 若要遵循本文中的示例,如果熟悉可扩展应用程序标记语言(XAML),并且知道如何编写 WPF 应用程序,则很有帮助。
通过元素树继承
属性值继承与面向对象的编程中的类继承的概念不同,其中派生类继承基类成员。 这种继承在 WPF 中也处于活动状态,但在 XAML 中,继承的基类属性作为表示派生类的 XAML 元素的属性公开。
属性值继承是依赖属性值从父元素传播到包含该属性的元素树中的子元素的机制。 在 XAML 标记中,元素树显示为嵌套元素。
以下示例演示 XAML 中的嵌套元素。 WPF 通过属性元数据注册 AllowDrop 依赖属性到 UIElement类,该属性启用属性值继承,并将默认值设置为 false
。 AllowDrop
依赖属性存在于 Canvas、StackPanel和 Label 元素上,因为它们都派生自 UIElement
。 由于 canvas1
上的 AllowDrop
依赖属性设置为 true
,因此子代 stackPanel1
和 label1
元素继承 true
作为其 AllowDrop
值。
<Canvas x:Name="canvas1" Grid.Column="0" Margin="20" Background="Orange" AllowDrop="True">
<StackPanel Name="stackPanel1" Margin="20" Background="Green">
<Label Name="label1" Margin="20" Height="40" Width="40" Background="Blue"/>
</StackPanel>
</Canvas>
还可以通过将元素对象添加到另一个元素对象的子元素集合,以编程方式创建元素树。 在运行时,属性值继承对生成的对象树执行操作。 在以下示例中,stackPanel2
被添加到 canvas2
中的 子集合。 同样,label2
添加到 stackPanel2
的子集合中。 由于 canvas2
上的 AllowDrop 依赖属性设置为 true
,因此子代 stackPanel2
和 label2
元素继承 true
作为其 AllowDrop
值。
Canvas canvas2 = new()
{
AllowDrop = true
};
StackPanel stackPanel2 = new();
Label label2 = new();
canvas2.Children.Add(stackPanel2);
stackPanel2.Children.Add(label2);
Dim canvas2 As New Canvas With {
.AllowDrop = True
}
Dim stackPanel2 As New StackPanel()
Dim label2 As New Label()
canvas2.Children.Add(stackPanel2)
stackPanel2.Children.Add(label2)
属性值继承的实际应用
默认情况下,特定的 WPF 依赖属性启用了值继承,例如 AllowDrop 和 FlowDirection。 通常,默认情况下启用值继承的属性在基 UI 元素类上实现,因此它们存在于派生类上。 例如,由于 AllowDrop
是在 UIElement 基类上实现的,因此依赖属性也存在于派生自 UIElement
的每个控件上。 WPF 对依赖属性启用值继承,用户可在父元素上设置属性值一次,并使该属性值传播到元素树中的子代元素。
根据 依赖属性值优先级,属性值继承模型分配继承和未继承的属性值。 因此,如果子元素属性没有更高的优先级值(例如本地设置值),或者通过样式、模板或数据绑定获取的值,则父元素属性值将仅应用于子元素。
FlowDirection 依赖属性设置父元素中的文本和子 UI 元素的布局方向。 通常,预期页面内文本和 UI 元素的流方向保持一致。 由于在 FlowDirection
的属性元数据中启用了值继承,因此只需要在页面的元素树顶部设置一次值。 在极少数情况下,如需在页面上有意实现混合流向,可以通过在树中的元素上分配本地设置值来设定不同的流向。 然后,新的流方向将传播到低于该级别的子代元素。
使自定义属性可继承
可以通过在 FrameworkPropertyMetadata实例中启用 Inherits 属性,然后将自定义依赖属性注册到该元数据实例,使自定义依赖属性可继承。 默认情况下,Inherits
设置为 FrameworkPropertyMetadata
中的 false
。 使属性值可继承会影响性能,因此,仅在需要时将 Inherits
设置为 true
。
注册一个在元数据中启用 Inherits
的依赖属性时,请使用注册附加属性中所述的 RegisterAttached 方法。 此外,为属性分配默认值,以便存在可继承的值。 你可能还想在所有者类型上创建具有 get
和 set
访问器的属性包装器,就像对非附加依赖属性所做的那样。 这样,就可以使用所有者或派生类型上的属性包装器来设置属性值。 以下示例创建一个名为 IsTransparent
的依赖属性,其中已启用 Inherits
,默认值为 false
。 该示例还包含一个带有 get
和 set
访问器的属性包装器。
public class Canvas_IsTransparentInheritEnabled : Canvas
{
// Register an attached dependency property with the specified
// property name, property type, owner type, and property metadata
// (default value is 'false' and property value inheritance is enabled).
public static readonly DependencyProperty IsTransparentProperty =
DependencyProperty.RegisterAttached(
name: "IsTransparent",
propertyType: typeof(bool),
ownerType: typeof(Canvas_IsTransparentInheritEnabled),
defaultMetadata: new FrameworkPropertyMetadata(
defaultValue: false,
flags: FrameworkPropertyMetadataOptions.Inherits));
// Declare a get accessor method.
public static bool GetIsTransparent(Canvas element)
{
return (bool)element.GetValue(IsTransparentProperty);
}
// Declare a set accessor method.
public static void SetIsTransparent(Canvas element, bool value)
{
element.SetValue(IsTransparentProperty, value);
}
// For convenience, declare a property wrapper with get/set accessors.
public bool IsTransparent
{
get => (bool)GetValue(IsTransparentProperty);
set => SetValue(IsTransparentProperty, value);
}
}
Public Class Canvas_IsTransparentInheritEnabled
Inherits Canvas
' Register an attached dependency property with the specified
' property name, property type, owner type, and property metadata
' (default value is 'false' and property value inheritance is enabled).
Public Shared ReadOnly IsTransparentProperty As DependencyProperty =
DependencyProperty.RegisterAttached(
name:="IsTransparent",
propertyType:=GetType(Boolean),
ownerType:=GetType(Canvas_IsTransparentInheritEnabled),
defaultMetadata:=New FrameworkPropertyMetadata(
defaultValue:=False,
flags:=FrameworkPropertyMetadataOptions.[Inherits]))
' Declare a get accessor method.
Public Shared Function GetIsTransparent(element As Canvas) As Boolean
Return element.GetValue(IsTransparentProperty)
End Function
' Declare a set accessor method.
Public Shared Sub SetIsTransparent(element As Canvas, value As Boolean)
element.SetValue(IsTransparentProperty, value)
End Sub
' For convenience, declare a property wrapper with get/set accessors.
Public Property IsTransparent As Boolean
Get
Return GetValue(IsTransparentProperty)
End Get
Set(value As Boolean)
SetValue(IsTransparentProperty, value)
End Set
End Property
End Class
附加属性在概念上类似于全局属性。 在任何 DependencyObject 上检查其值并获得有效的结果。 附加属性的典型方案是在子元素上设置属性值,如果有问题的属性隐式呈现为树中每个 DependencyObject 元素的附加属性,则该方案更有效。
跨树边界继承属性值
属性继承的工作原理是遍历元素树。 此树通常与逻辑树并行。 但是,每当在定义元素树的标记中包含 WPF 核心级对象(如 Brush),就创建了一个不连续的逻辑树。 真正的逻辑树在概念上不会通过 Brush
进行扩展,因为逻辑树是 WPF 框架级概念。 可以使用 LogicalTreeHelper 的帮助程序方法来分析和查看逻辑树的范围。 只要可继承的属性注册为附加属性,且没有遇到有意的继承阻止边界(如 Frame),那么属性值继承就能够通过不连续的逻辑树传递继承的值。
说明
尽管属性值继承可能适用于非附加依赖属性,但未定义通过运行时树中的某些元素边界的非附加属性的继承行为。 每当在属性元数据中指定 Inherits 时,请使用 RegisterAttached 注册属性。