属性值继承 (WPF .NET)

属性值继承是 Windows Presentation Foundation (WPF) 属性系统的一项功能,适用于依赖属性。 属性值继承允许元素树中的子元素从最近的父元素获取特定属性的值。 由于父元素也可能通过属性值继承获得其属性值,因此系统可能递归回页面根。

WPF 属性系统默认不启用属性值继承,除非在依赖属性元数据中专门启用,否则值继承处于非活动状态。 即使启用了属性值继承,子元素也仅会在缺少更高优先级值的情况下继承属性值。

重要

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

先决条件

本文假定你对依赖属性有基本的了解,并且已阅读依赖属性概述。 若要理解本文中的示例,还应当熟悉 Extensible Application Markup Language (XAML) 并知道如何编写 WPF 应用程序。

通过元素树继承

属性值继承与面向对象的编程中的类继承不同,在面向对象编程中,派生类继承基类成员。 这种继承在 WPF 中也处于活动状态,但在 XAML 中,继承的基类属性作为表示派生类的 XAML 元素的属性公开。

属性值继承是一种机制,通过这种机制,依赖属性值可以在包含该属性的元素树中从父元素传播到子元素。 在 XAML 标记中,元素树作为嵌套元素可见。

以下示例演示 XAML 中的嵌套元素。 WPF 通过属性元数据注册 AllowDrop 依赖属性到 UIElement类,该属性启用属性值继承,并将默认值设置为 falseAllowDrop 依赖属性存在于 CanvasStackPanelLabel 元素上,因为它们都派生自 UIElement。 由于 canvas1 上的 AllowDrop 依赖属性被设置为 true,因此后代元素 stackPanel1label1 将继承 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,因此后代元素 stackPanel2label2 将继承 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 依赖属性启用了值继承,例如 AllowDropFlowDirection。 通常,默认情况下启用值继承的属性在基 UI 元素类上实现,因此它们存在于派生类上。 例如,由于 AllowDropUIElement 基类上实现,因此每个派生自 UIElement 的控件上也存在该依赖属性。 WPF 允许对依赖属性进行值继承,用户可以方便地在父元素上设置属性值,并将该属性值传播到元素树中的后代元素。

属性值继承模型根据 依赖属性值优先级分配继承和未继承的属性值。 因此,如果子元素属性没有更高优先级的值(例如本地设置值或通过样式、模板或数据绑定获取的值),则父元素属性值将仅应用于子元素。

FlowDirection 依赖属性设置父元素中的文本和子 UI 元素的布局方向。 通常,预期页面内文本和 UI 元素的流方向保持一致。 由于在 FlowDirection 的属性元数据中启用了值继承,因此只需要在页面的元素树顶部设置一次值。 在极少数情况下,混合流方向适用于页面,可以通过分配一个本地设置的值,在树中的元素上设置不同的流方向。 然后,新的流方向将传播到该级别以下的后代元素。

使自定义属性可继承

通过在 FrameworkPropertyMetadata 实例中启用 Inherits 属性,然后向该元数据实例注册自定义依赖属性,可以使自定义依赖属性可继承。 默认情况下,FrameworkPropertyMetadata 中的 Inherits 设置为 false。 使属性值可继承会影响性能,因此仅在需要该功能时才将 Inherits 设置为 true

注册一个在元数据中启用 Inherits 的依赖属性时,请使用注册附加属性中所述的 RegisterAttached 方法。 此外,为属性分配默认值,以便存在可继承的值。 你可能还想在所有者类型上创建具有 getset 访问器的属性包装器,就像对非附加依赖属性所做的那样。 这样,就可以使用所有者或派生类型上的属性包装器来设置属性值。 以下示例创建一个名为 IsTransparent 的依赖属性,启用了 Inherits,默认值为 false。 该示例还包含一个带有 getset 访问器的属性包装器。

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 注册属性。

另请参阅