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

其 Extensible Application Markup Language (XAML) 处理器的 Windows Presentation Foundation (WPF) 实现本质上是依赖属性感知的。 因此,XAML 处理器使用 WPF 属性系统方法来加载 XAML 和处理依赖属性特性,并通过使用 WPF 属性系统方法(如 GetValueSetValue)完全绕过依赖属性包装器。 因此,如果将自定义逻辑添加到自定义依赖属性的属性包装器,则在 XAML 中设置属性值时,XAML 处理器不会调用该逻辑。

重要

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

先决条件

本文假定你对依赖属性有基本的了解,并且已阅读依赖属性概述。 若要理解本文中的示例,还应当熟悉 Extensible Application Markup Language (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 字段,并且除了支持依赖属性值的必要 WPF 属性系统方法之外,getset 访问器不包含其他任何代码。 如果需要在依赖属性的值发生更改时运行某段代码,请考虑将该代码置于依赖属性的 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

另请参阅