XAML 加载和依赖项属性 (WPF .NET)

Windows Presentation Foundation(WPF)的可扩展应用程序标记语言(XAML)处理器在其实现中本质上是依赖属性感知的。 因此,XAML 处理器使用 WPF 属性系统方法加载 XAML 和处理依赖属性属性,并使用 WPF 属性系统方法(如 GetValueSetValue)完全绕过依赖属性包装器。 因此,如果将自定义逻辑添加到自定义依赖属性的属性包装器,则当在 XAML 中设置属性值时,XAML 处理器不会调用它。

先决条件

本文假设对依赖属性有一个基本的了解,并且你已阅读 依赖项属性概述。 若要遵循本文中的示例,如果熟悉可扩展应用程序标记语言(XAML),并且知道如何编写 WPF 应用程序,则很有帮助。

WPF XAML 加载程序性能

对于 WPF XAML 处理器来说,直接调用 SetValue 来设置依赖属性的值,在计算上比使用依赖属性的属性包装器要更节省成本。

如果 XAML 处理器确实使用属性包装器,则需要仅根据标记中指示的类型和成员关系推断支持代码的整个对象模型。 虽然可以通过使用 xmlns 和程序集属性的组合从标记中标识类型,但要标识成员、确定哪些成员可以设置为属性,以及解析支持的属性值类型,仍然需要使用 PropertyInfo进行广泛的反射。

WPF 属性系统维护在给定的 DependencyObject 派生类型上实现的依赖属性的存储表。 XAML 处理器使用该表来推断依赖属性标识符。 例如,按照约定,名为 ABC 的依赖属性的依赖属性标识符为 ABCProperty。 XAML 处理器可以通过依赖属性标识符,在包含该属性的类型上调用 SetValue 方法,有效地设置任何依赖属性的值。

有关依赖属性包装器的详细信息,请参阅 自定义依赖属性

自定义依赖项属性的含义

WPF XAML 处理器绕过属性包装器,并直接调用 SetValue 来设置依赖属性值。 因此,请避免将任何额外的逻辑放在自定义依赖属性的 set 访问器中,因为该逻辑在 XAML 中设置属性值时不会运行。 set 访问器应仅包含 SetValue 调用。

同样,WPF XAML 处理器中用于获取属性值的功能会绕过属性包装器,并直接调用 GetValue。 因此,也避免将任何额外的逻辑放在自定义依赖属性的 get 访问器中,因为该逻辑在 XAML 中读取属性值时不会运行。 get 访问器应仅包含 GetValue 调用。

带封装器的依赖属性示例

以下示例展示了带有属性包装器的推荐依赖属性定义。 依赖属性标识符存储为 public static readonly 字段,getset 访问器不包含除依赖属性值所需的 WPF 属性系统方法之外的代码。 如果代码需要在依赖属性的值发生更改时运行,请考虑将该代码放入依赖属性的 PropertyChangedCallback 中。 有关详细信息,请参阅 属性变更回调

// Register a dependency property with the specified property name,
// property type, owner type, and property metadata. Store the dependency
// property identifier as a public static readonly member of the class.
public static readonly DependencyProperty AquariumGraphicProperty =
    DependencyProperty.Register(
      name: "AquariumGraphic",
      propertyType: typeof(Uri),
      ownerType: typeof(Aquarium),
      typeMetadata: new FrameworkPropertyMetadata(
          defaultValue: new Uri("http://www.contoso.com/aquarium-graphic.jpg"),
          flags: FrameworkPropertyMetadataOptions.AffectsRender,
          propertyChangedCallback: new PropertyChangedCallback(OnUriChanged))
    );

// Property wrapper with get & set accessors.
public Uri AquariumGraphic
{
    get => (Uri)GetValue(AquariumGraphicProperty);
    set => SetValue(AquariumGraphicProperty, value);
}

// Property-changed callback.
private static void OnUriChanged(DependencyObject dependencyObject, 
    DependencyPropertyChangedEventArgs e)
{
    // Some custom logic that runs on effective property value change.
    Uri newValue = (Uri)dependencyObject.GetValue(AquariumGraphicProperty);
    Debug.WriteLine($"OnUriChanged: {newValue}");
}
' Register a dependency property with the specified property name,
' property type, owner type, and property metadata. Store the dependency
' property identifier as a public static readonly member of the class.
Public Shared ReadOnly AquariumGraphicProperty As DependencyProperty =
    DependencyProperty.Register(
        name:="AquariumGraphic",
        propertyType:=GetType(Uri),
        ownerType:=GetType(Aquarium),
        typeMetadata:=New FrameworkPropertyMetadata(
            defaultValue:=New Uri("http://www.contoso.com/aquarium-graphic.jpg"),
            flags:=FrameworkPropertyMetadataOptions.AffectsRender,
            propertyChangedCallback:=New PropertyChangedCallback(AddressOf OnUriChanged)))

' Property wrapper with get & set accessors.
Public Property AquariumGraphic As Uri
    Get
        Return CType(GetValue(AquariumGraphicProperty), Uri)
    End Get
    Set
        SetValue(AquariumGraphicProperty, Value)
    End Set
End Property

' Property-changed callback.
Private Shared Sub OnUriChanged(dependencyObject As DependencyObject,
                                e As DependencyPropertyChangedEventArgs)
    ' Some custom logic that runs on effective property value change.
    Dim newValue As Uri = CType(dependencyObject.GetValue(AquariumGraphicProperty), Uri)
    Debug.WriteLine($"OnUriChanged: {newValue}")
End Sub

另请参阅