Carga de XAML y propiedades de dependencia (WPF .NET)

La implementación de Windows Presentation Foundation (WPF) de su procesador lenguaje XAML reconoce de manera intrínseca la propiedad de dependencia. Por lo tanto, el procesador XAML usa métodos del sistema de propiedades de WPF para cargar XAML y procesar atributos de propiedad de dependencia, y omite completamente los contenedores de propiedades de dependencia mediante métodos del sistema de propiedades de WPF como GetValue y SetValue. Por lo tanto, si agrega lógica personalizada al contenedor de propiedades de la propiedad de dependencia personalizada, el procesador XAML no lo llamará cuando se establezca un valor de propiedad en XAML.

Requisitos previos

En el artículo se da por supuesto un conocimiento básico de las propiedades de dependencia y que ha leído Información general sobre las propiedades de dependencia. Para seguir los ejemplos de este artículo, resultará útil que esté familiarizado con lenguaje XAML y sepa cómo escribir aplicaciones WPF.

Rendimiento del cargador XAML de WPF

Desde el punto de vista computacional es menos costoso que el procesador XAML de WPF llame directamente a SetValue para establecer el valor de una propiedad de dependencia, en lugar de usar el contenedor de propiedades de una propiedad de dependencia.

Si el procesador XAML usara el contenedor de propiedades, sería necesario inferir todo el modelo de objetos del código de respaldo basándose solo en las relaciones de tipo y miembro indicadas en el marcado. Aunque el tipo se puede identificar a partir del marcado mediante una combinación de atributos de ensamblado y xmlns, identificar los miembros, determinar qué miembros se pueden establecer como un atributo y la resolver los tipos de valor de propiedad admitidos requeriría una reflexión exhaustiva mediante PropertyInfo.

El sistema de propiedades de WPF mantiene una tabla de almacenamiento de propiedades de dependencia implementadas en un tipo derivado DependencyObject determinado. El procesador XAML usa esa tabla para inferir el identificador de propiedad de dependencia de una propiedad de dependencia. Por ejemplo, por convención, el identificador de propiedad de dependencia para una propiedad de dependencia denominada ABC es ABCProperty. El procesador XAML puede establecer eficazmente el valor de cualquier propiedad de dependencia llamando al método SetValue en el tipo que lo contiene mediante el identificador de propiedad de dependencia.

Para obtener más información sobre los contenedores de propiedades de dependencia, consulte Propiedades de dependencia personalizadas.

Implicaciones para las propiedades de dependencia personalizadas

El procesador XAML de WPF omite los contenedores de propiedades y llama directamente a SetValue para establecer un valor de propiedad de dependencia. Por lo tanto, evite colocar cualquier lógica adicional en el descriptor de acceso set de la propiedad de dependencia personalizada, porque esa lógica no se ejecutará cuando se establezca un valor de propiedad en XAML. El descriptor de acceso set solo debe contener una llamada SetValue.

De forma similar, los aspectos del procesador XAML de WPF que obtienen valores de propiedad omiten el contenedor de propiedades y llaman directamente a GetValue. Por lo tanto, evite colocar cualquier lógica adicional en el descriptor de acceso get de la propiedad de dependencia personalizada, porque esa lógica no se ejecutará cuando se lea un valor de propiedad en XAML. El descriptor de acceso get solo debe contener una llamada GetValue.

Ejemplo de propiedad de dependencia con contenedor

En el ejemplo siguiente se muestra una definición de propiedad de dependencia recomendada con contenedores de propiedades. El identificador de la propiedad de dependencia se almacena como un campo public static readonly y los descriptores de acceso get y set no contienen ningún código más allá de los métodos necesarios del sistema de propiedades de WPF que respaldan el valor de la propiedad de dependencia. Si tiene código que debe ejecutarse cuando cambia el valor de la propiedad de dependencia, considere la posibilidad de colocar ese código en el PropertyChangedCallback para la propiedad de dependencia. Para obtener más información, consulte Devoluciones de llamada modificadas por propiedades.

// 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

Vea también