属性值继承 (WPF .NET)
属性值继承是 Windows Presentation Foundation (WPF) 属性系统的一项功能,适用于依赖属性。 属性值继承允许元素树中的子元素从最近的父元素获取特定属性的值。 由于父元素也可能通过属性值继承获得其属性值,因此系统可能递归回页面根。
WPF 属性系统默认不启用属性值继承,除非在依赖属性元数据中专门启用,否则值继承处于非活动状态。 即使启用了属性值继承,子元素也仅会在缺少更高优先级值的情况下继承属性值。
先决条件
本文假定你对依赖属性有基本的了解,并且已阅读依赖属性概述。 若要理解本文中的示例,还应当熟悉 Extensible Application Markup Language (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 属性,然后向该元数据实例注册自定义依赖属性,可以使自定义依赖属性可继承。 默认情况下,FrameworkPropertyMetadata
中的 Inherits
设置为 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 注册属性。