Compartir a través de


Propiedades de dependencia personalizadas

Aquí se explica cómo definir e implementar sus propias propiedades de dependencia para una aplicación de Windows Runtime mediante C++, C#o Visual Basic. Enumeramos las razones por las que es posible que los desarrolladores de aplicaciones y los autores de componentes quieran crear propiedades de dependencia personalizadas. Se describen los pasos de implementación de una propiedad de dependencia personalizada, así como algunos procedimientos recomendados que pueden mejorar el rendimiento, la facilidad de uso o la versatilidad de la propiedad de dependencia.

Requisitos previos

Se supone que ha leído la información general sobre las propiedades de dependencia y que comprende las propiedades de dependencia desde la perspectiva de un consumidor de las propiedades de dependencia existentes. Para seguir los ejemplos de este tema, también debe comprender XAML y saber cómo escribir una aplicación básica de Windows Runtime con C++, C# o Visual Basic.

¿Qué es una propiedad de dependencia?

Para admitir estilos, enlaces de datos, animaciones y valores predeterminados para una propiedad, debe implementarse como una propiedad de dependencia. Los valores de propiedad de dependencia no se almacenan como campos en la clase , se almacenan en el marco xaml y se hace referencia a ellos mediante una clave, que se recupera cuando la propiedad se registra con el sistema de propiedades de Windows Runtime llamando al método DependencyProperty.Register. Las propiedades de dependencia solo se pueden usar mediante tipos derivados de DependencyObject. Pero DependencyObject es bastante alto en la jerarquía de clases, por lo que la mayoría de las clases diseñadas para la compatibilidad con la interfaz de usuario y la presentación pueden admitir propiedades de dependencia. Para obtener más información sobre las propiedades de dependencia y algunas de las terminologías y convenciones que se usan para describirlas en esta documentación, consulte Introducción a las propiedades de dependencia.

Algunos ejemplos de propiedades de dependencia en Windows Runtime son: Control.Background, FrameworkElement.Width y TextBox.Text, entre muchos otros.

Convención es que cada propiedad de dependencia expuesta por una clase tiene una propiedad de lectura estática pública correspondiente de tipo DependencyProperty que se expone en esa misma clase, que proporciona el identificador para la propiedad de dependencia. El nombre del identificador sigue esta convención: el nombre de la propiedad de dependencia, con la cadena "Property" agregada al final del nombre. Por ejemplo, el identificador DependencyProperty correspondiente para la propiedad Control.Background es Control.BackgroundProperty. El identificador almacena la información sobre la propiedad de dependencia tal como se registró y, a continuación, se puede usar para otras operaciones que implican la propiedad de dependencia, como llamar a SetValue.

Contenedores de propiedades

Normalmente, las propiedades de dependencia tienen una implementación de contenedor. Sin el contenedor, la única manera de obtener o establecer las propiedades sería usar los métodos de utilidad de la propiedad de dependencia GetValue y SetValue y pasar el identificador a ellas como parámetro. Se trata de un uso bastante poco natural para algo que es ostensibly una propiedad. Pero con el contenedor, el código y cualquier otro código que haga referencia a la propiedad de dependencia pueden usar una sintaxis sencilla de propiedad de objeto que es natural para el lenguaje que está usando.

Si implementa una propiedad de dependencia personalizada usted mismo y quiere que sea pública y fácil de llamar, defina también los contenedores de propiedades. Los contenedores de propiedades también son útiles para notificar información básica sobre la propiedad de dependencia a los procesos de reflexión o análisis estático. En concreto, el contenedor es donde se colocan atributos como ContentPropertyAttribute.

Cuándo implementar una propiedad como una propiedad de dependencia

Siempre que implemente una propiedad de lectura y escritura pública en una clase, siempre que la clase derive de DependencyObject, tiene la opción de hacer que la propiedad funcione como una propiedad de dependencia. A veces, la técnica típica de respaldar la propiedad con un campo privado es adecuada. Definir la propiedad personalizada como una propiedad de dependencia no siempre es necesaria o adecuada. La elección dependerá de los escenarios que quiera admitir la propiedad.

Puedes considerar la posibilidad de implementar tu propiedad como una propiedad de dependencia cuando quieras que admita una o varias de estas características de Windows Runtime o de aplicaciones de Windows Runtime:

  • Establecimiento de la propiedad a través de un estilo
  • Actuar como propiedad de destino válida para el enlace de datos con {Binding}
  • Compatibilidad con valores animados a través de un guión gráfico
  • Informar cuando el valor de la propiedad ha cambiado:
    • Acciones realizadas por el propio sistema de propiedades
    • El entorno de
    • Acciones del usuario
    • Lectura y escritura de estilos

Lista de comprobación para definir una propiedad de dependencia

Definir una propiedad de dependencia se puede considerar como un conjunto de conceptos. Estos conceptos no son necesariamente pasos de procedimiento, ya que se pueden abordar varios conceptos en una sola línea de código en la implementación. Esta lista proporciona información general rápida. Explicaremos cada concepto con más detalle más adelante en este tema y le mostraremos código de ejemplo en varios lenguajes.

  • Registre el nombre de propiedad con el sistema de propiedades (llame a Register), especificando un tipo de propietario y el tipo del valor de propiedad.
    • Hay un parámetro necesario para Register que espera metadatos de propiedad. Especifique null para esto, o si desea un comportamiento cambiado de propiedad o un valor predeterminado basado en metadatos que se puede restaurar llamando a ClearValue, especifique una instancia de PropertyMetadata.
  • Defina un identificador DependencyProperty como miembro de propiedad estático público de solo lectura en el tipo de propietario.
  • Defina una propiedad contenedora, siguiendo el modelo de descriptor de acceso de propiedad que se usa en el lenguaje que está implementando. El nombre de la propiedad contenedora debe coincidir con la cadena de nombre que usó en Register. Implemente los descriptores de acceso get y set para conectar el contenedor con la propiedad de dependencia que encapsula, llamando a GetValue y SetValue y pasando el identificador de su propia propiedad como parámetro.
  • (Opcional) Coloque atributos como ContentPropertyAttribute en el contenedor.

Nota:

Si va a definir una propiedad adjunta personalizada, normalmente omite el contenedor. En su lugar, escribes un estilo diferente de descriptor de acceso que un procesador XAML puede usar. Consulte Propiedades adjuntas personalizadas

Registro de la propiedad

Para que la propiedad sea una propiedad de dependencia, debe registrar la propiedad en un almacén de propiedades mantenido por el sistema de propiedades de Windows Runtime. Para registrar la propiedad, llame al método Register.

En el caso de los lenguajes .NET de Microsoft (C# y Microsoft Visual Basic), llame a Register dentro del cuerpo de la clase (dentro de la clase, pero fuera de las definiciones de miembro). La llamada al método Register proporciona el identificador como valor devuelto. La llamada register se realiza normalmente como un constructor estático o como parte de la inicialización de una propiedad estática estática pública de tipo DependencyProperty como parte de la clase. Esta propiedad expone el identificador de la propiedad de dependencia. Estos son ejemplos de la llamada Register.

Nota:

Registrar la propiedad de dependencia como parte de la definición de propiedad de identificador es la implementación típica, pero también puede registrar una propiedad de dependencia en el constructor estático de clase. Este enfoque puede tener sentido si necesita más de una línea de código para inicializar la propiedad de dependencia.

Para C++/CX, tiene opciones para dividir la implementación entre el encabezado y el archivo de código. La división típica consiste en declarar el propio identificador como propiedad estática pública en el encabezado, con una implementación get pero sin establecer. La implementación get hace referencia a un campo privado, que es una instancia de DependencyProperty no inicializada. También puede declarar los contenedores y las implementaciones get y set del contenedor. En este caso, el encabezado incluye una implementación mínima. Si el contenedor necesita la atribución de Windows Runtime, el atributo también se encuentra en el encabezado. Coloque la llamada Register en el archivo de código, dentro de una función auxiliar que solo se ejecute cuando la aplicación inicializa la primera vez. Use el valor devuelto de Register para rellenar los identificadores estáticos pero sin inicializar que declaró en el encabezado, que inicialmente estableció en nullptr en el ámbito raíz del archivo de implementación.

public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(
  nameof(Label),
  typeof(String),
  typeof(ImageWithLabelControl),
  new PropertyMetadata(null)
);
Public Shared ReadOnly LabelProperty As DependencyProperty = 
    DependencyProperty.Register("Label", 
      GetType(String), 
      GetType(ImageWithLabelControl), 
      New PropertyMetadata(Nothing))
// ImageWithLabelControl.idl
namespace ImageWithLabelControlApp
{
    runtimeclass ImageWithLabelControl : Windows.UI.Xaml.Controls.Control
    {
        ImageWithLabelControl();
        static Windows.UI.Xaml.DependencyProperty LabelProperty{ get; };
        String Label;
    }
}

// ImageWithLabelControl.h
...
struct ImageWithLabelControl : ImageWithLabelControlT<ImageWithLabelControl>
{
...
public:
    static Windows::UI::Xaml::DependencyProperty LabelProperty()
    {
        return m_labelProperty;
    }

private:
    static Windows::UI::Xaml::DependencyProperty m_labelProperty;
...
};

// ImageWithLabelControl.cpp
...
Windows::UI::Xaml::DependencyProperty ImageWithLabelControl::m_labelProperty =
    Windows::UI::Xaml::DependencyProperty::Register(
        L"Label",
        winrt::xaml_typename<winrt::hstring>(),
        winrt::xaml_typename<ImageWithLabelControlApp::ImageWithLabelControl>(),
        Windows::UI::Xaml::PropertyMetadata{ nullptr }
);
...
//.h file
//using namespace Windows::UI::Xaml::Controls;
//using namespace Windows::UI::Xaml::Interop;
//using namespace Windows::UI::Xaml;
//using namespace Platform;

public ref class ImageWithLabelControl sealed : public Control
{
private:
    static DependencyProperty^ _LabelProperty;
...
public:
    static void RegisterDependencyProperties();
    static property DependencyProperty^ LabelProperty
    {
        DependencyProperty^ get() {return _LabelProperty;}
    }
...
};

//.cpp file
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml.Interop;

DependencyProperty^ ImageWithLabelControl::_LabelProperty = nullptr;

// This function is called from the App constructor in App.xaml.cpp
// to register the properties
void ImageWithLabelControl::RegisterDependencyProperties()
{ 
    if (_LabelProperty == nullptr)
    { 
        _LabelProperty = DependencyProperty::Register(
          "Label", Platform::String::typeid, ImageWithLabelControl::typeid, nullptr);
    } 
}

Nota:

Para el código de C++/CX, el motivo por el que tiene un campo privado y una propiedad pública de solo lectura que expone DependencyProperty es para que otros autores de llamadas que usen la propiedad de dependencia también puedan usar las API de utilidad del sistema de propiedades que requieren que el identificador sea público. Si mantiene el identificador privado, las personas no pueden usar estas API de utilidad. Entre los ejemplos de estas API y escenarios se incluyen GetValue o SetValue por elección, ClearValue, GetAnimationBaseValue, SetBinding y Setter.Property. No puedes usar un campo público para esto, ya que las reglas de metadatos de Windows Runtime no permiten campos públicos.

Convenciones de nombre de propiedad de dependencia

Existen convenciones de nomenclatura para las propiedades de dependencia; seguirlos en todas las circunstancias, pero excepcionales. La propia propiedad de dependencia tiene un nombre básico ("Label" en el ejemplo anterior) que se da como primer parámetro de Register. El nombre debe ser único dentro de cada tipo de registro y el requisito de unicidad también se aplica a los miembros heredados. Las propiedades de dependencia heredadas a través de tipos base se consideran parte del tipo de registro ya; Los nombres de las propiedades heredadas no se pueden volver a registrar.

Advertencia

Aunque el nombre que proporciones aquí puede ser cualquier identificador de cadena que sea válido en la programación del lenguaje que prefieras, normalmente quieres poder establecer la propiedad de dependencia en XAML también. Para establecerse en XAML, el nombre de propiedad que elijas debe ser un nombre XAML válido. Para obtener más información, consulta Información general sobre XAML.

Al crear la propiedad de identificador, combine el nombre de la propiedad a medida que lo registró con el sufijo "Property" ("LabelProperty", por ejemplo). Esta propiedad es el identificador de la propiedad de dependencia y se usa como entrada para las llamadas SetValue y GetValue que realiza en sus propios contenedores de propiedades. También lo usa el sistema de propiedades y otros procesadores XAML, como {x:Bind}

Implementación del contenedor

El contenedor de propiedades debe llamar a GetValue en la implementación get y SetValue en la implementación del conjunto.

Advertencia

En todas las circunstancias, pero excepcionales, las implementaciones del contenedor solo deben realizar las operaciones GetValue y SetValue. De lo contrario, obtendrás un comportamiento diferente cuando la propiedad se establezca a través de XAML en lugar de cuando se establezca a través del código. Para mejorar la eficacia, el analizador XAML omite los contenedores al establecer propiedades de dependencia; y se comunica con el almacén de respaldo a través de SetValue.

public String Label
{
    get { return (String)GetValue(LabelProperty); }
    set { SetValue(LabelProperty, value); }
}
Public Property Label() As String
    Get
        Return DirectCast(GetValue(LabelProperty), String) 
    End Get 
    Set(ByVal value As String)
        SetValue(LabelProperty, value)
    End Set
End Property
// ImageWithLabelControl.h
...
winrt::hstring Label()
{
    return winrt::unbox_value<winrt::hstring>(GetValue(m_labelProperty));
}

void Label(winrt::hstring const& value)
{
    SetValue(m_labelProperty, winrt::box_value(value));
}
...
//using namespace Platform;
public:
...
  property String^ Label
  {
    String^ get() {
      return (String^)GetValue(LabelProperty);
    }
    void set(String^ value) {
      SetValue(LabelProperty, value);
    }
  }

Metadatos de propiedad para una propiedad de dependencia personalizada

Cuando se asignan metadatos de propiedad a una propiedad de dependencia, se aplican los mismos metadatos a esa propiedad para cada instancia del tipo de propietario de propiedad o sus subclases. En los metadatos de propiedad, puede especificar dos comportamientos:

  • Valor predeterminado que el sistema de propiedades asigna a todos los casos de la propiedad.
  • Método de devolución de llamada estático que se invoca automáticamente dentro del sistema de propiedades cada vez que se detecta un cambio de valor de propiedad.

Llamada al registro con metadatos de propiedad

En los ejemplos anteriores de llamar a DependencyProperty.Register, pasamos un valor NULL para el parámetro propertyMetadata. Para permitir que una propiedad de dependencia proporcione un valor predeterminado o use una devolución de llamada modificada por propiedades, debe definir una instancia PropertyMetadata que proporcione una o ambas funcionalidades.

Normalmente, se proporciona un PropertyMetadata como una instancia creada en línea, dentro de los parámetros de DependencyProperty.Register.

Nota:

Si va a definir una implementación createDefaultValueCallback, debe usar el método de utilidad PropertyMetadata.Create en lugar de llamar a un constructor PropertyMetadata para definir la instancia propertyMetadata.

En este ejemplo siguiente se modifican los ejemplos de DependencyProperty.Register mostrados anteriormente haciendo referencia a una instancia PropertyMetadata con un valor PropertyChangedCallback. La implementación de la devolución de llamada "OnLabelChanged" se mostrará más adelante en esta sección.

public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(
  nameof(Label),
  typeof(String),
  typeof(ImageWithLabelControl),
  new PropertyMetadata(null,new PropertyChangedCallback(OnLabelChanged))
);
Public Shared ReadOnly LabelProperty As DependencyProperty =
    DependencyProperty.Register("Label",
      GetType(String),
      GetType(ImageWithLabelControl),
      New PropertyMetadata(
        Nothing, new PropertyChangedCallback(AddressOf OnLabelChanged)))
// ImageWithLabelControl.cpp
...
Windows::UI::Xaml::DependencyProperty ImageWithLabelControl::m_labelProperty =
    Windows::UI::Xaml::DependencyProperty::Register(
        L"Label",
        winrt::xaml_typename<winrt::hstring>(),
        winrt::xaml_typename<ImageWithLabelControlApp::ImageWithLabelControl>(),
        Windows::UI::Xaml::PropertyMetadata{ nullptr, Windows::UI::Xaml::PropertyChangedCallback{ &ImageWithLabelControl::OnLabelChanged } }
);
...
DependencyProperty^ ImageWithLabelControl::_LabelProperty =
    DependencyProperty::Register("Label",
    Platform::String::typeid,
    ImageWithLabelControl::typeid,
    ref new PropertyMetadata(nullptr,
      ref new PropertyChangedCallback(&ImageWithLabelControl::OnLabelChanged))
    );

Valor predeterminado

Puede especificar un valor predeterminado para una propiedad de dependencia de modo que la propiedad siempre devuelva un valor predeterminado determinado cuando no se establece. Este valor puede ser diferente del valor predeterminado inherente para el tipo de esa propiedad.

Si no se especifica un valor predeterminado, el valor predeterminado de una propiedad de dependencia es NULL para un tipo de referencia o el valor predeterminado del tipo para un tipo de valor o primitivo de idioma (por ejemplo, 0 para un entero o una cadena vacía para una cadena). La razón principal para establecer un valor predeterminado es que este valor se restaura cuando se llama a ClearValue en la propiedad . Establecer un valor predeterminado por propiedad puede ser más conveniente que establecer valores predeterminados en constructores, especialmente para los tipos de valor. Sin embargo, para los tipos de referencia, asegúrese de que establecer un valor predeterminado no crea un patrón singleton involuntaria. Para obtener más información, consulte Procedimientos recomendados más adelante en este tema.

// ImageWithLabelControl.cpp
...
Windows::UI::Xaml::DependencyProperty ImageWithLabelControl::m_labelProperty =
    Windows::UI::Xaml::DependencyProperty::Register(
        L"Label",
        winrt::xaml_typename<winrt::hstring>(),
        winrt::xaml_typename<ImageWithLabelControlApp::ImageWithLabelControl>(),
        Windows::UI::Xaml::PropertyMetadata{ winrt::box_value(L"default label"), Windows::UI::Xaml::PropertyChangedCallback{ &ImageWithLabelControl::OnLabelChanged } }
);
...

Nota:

No se registre con un valor predeterminado de UnsetValue. Si lo hace, confundirá a los consumidores de propiedades y tendrá consecuencias imprevistas dentro del sistema de propiedades.

CreateDefaultValueCallback

En algunos escenarios, va a definir propiedades de dependencia para objetos que se usan en más de un subproceso de interfaz de usuario. Este puede ser el caso si va a definir un objeto de datos que usan varias aplicaciones o un control que se usa en más de una aplicación. Puede habilitar el intercambio del objeto entre distintos subprocesos de interfaz de usuario proporcionando una implementación CreateDefaultValueCallback en lugar de una instancia de valor predeterminada, que está vinculada al subproceso que registró la propiedad. Básicamente, CreateDefaultValueCallback define un generador para los valores predeterminados. El valor devuelto por CreateDefaultValueCallback siempre está asociado al subproceso CreateDefaultValueCallback actual de la interfaz de usuario que usa el objeto .

Para definir metadatos que especifican un CreateDefaultValueCallback, debe llamar a PropertyMetadata.Create para devolver una instancia de metadatos; los constructores PropertyMetadata no tienen una firma que incluya un parámetro CreateDefaultValueCallback.

El patrón de implementación típico para un CreateDefaultValueCallback es crear una nueva clase DependencyObject, establecer el valor de propiedad específico de cada propiedad de DependencyObject en el valor predeterminado previsto y, a continuación, devolver la nueva clase como una referencia object a través del valor devuelto del método CreateDefaultValueCallback.

Método de devolución de llamada cambiado por propiedad

Puede definir un método de devolución de llamada cambiado por propiedades para definir las interacciones de la propiedad con otras propiedades de dependencia, o para actualizar una propiedad interna o un estado del objeto siempre que cambie la propiedad. Si se invoca la devolución de llamada, el sistema de propiedades ha determinado que hay un cambio de valor de propiedad efectivo. Dado que el método de devolución de llamada es estático, el parámetro d de la devolución de llamada es importante porque indica qué instancia de la clase ha notificado un cambio. Una implementación típica usa la propiedad NewValue de los datos del evento y procesa ese valor de alguna manera, normalmente realizando algún otro cambio en el objeto pasado como d. Las respuestas adicionales a un cambio de propiedad son rechazar el valor notificado por NewValue, restaurar OldValue o establecer el valor en una restricción mediante programación aplicada a NewValue.

En este ejemplo siguiente se muestra una implementación propertyChangedCallback. Implementa el método al que se ha visto al que se hace referencia en los ejemplos de Registro anteriores, como parte de los argumentos de construcción de PropertyMetadata. El escenario abordado por esta devolución de llamada es que la clase también tiene una propiedad calculada de solo lectura denominada "HasLabelValue" (no se muestra la implementación). Siempre que se vuelva a evaluar la propiedad "Label", se invoca este método de devolución de llamada y la devolución de llamada permite que el valor calculado dependiente permanezca sincronizado con los cambios en la propiedad de dependencia.

private static void OnLabelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
    ImageWithLabelControl iwlc = d as ImageWithLabelControl; //null checks omitted
    String s = e.NewValue as String; //null checks omitted
    if (s == String.Empty)
    {
        iwlc.HasLabelValue = false;
    } else {
        iwlc.HasLabelValue = true;
    }
}
    Private Shared Sub OnLabelChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
        Dim iwlc As ImageWithLabelControl = CType(d, ImageWithLabelControl) ' null checks omitted
        Dim s As String = CType(e.NewValue,String) ' null checks omitted
        If s Is String.Empty Then
            iwlc.HasLabelValue = False
        Else
            iwlc.HasLabelValue = True
        End If
    End Sub
void ImageWithLabelControl::OnLabelChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const& e)
{
    auto iwlc{ d.as<ImageWithLabelControlApp::ImageWithLabelControl>() };
    auto s{ winrt::unbox_value<winrt::hstring>(e.NewValue()) };
    iwlc.HasLabelValue(s.size() != 0);
}
static void OnLabelChanged(DependencyObject^ d, DependencyPropertyChangedEventArgs^ e)
{
    ImageWithLabelControl^ iwlc = (ImageWithLabelControl^)d;
    Platform::String^ s = (Platform::String^)(e->NewValue);
    if (s->IsEmpty()) {
        iwlc->HasLabelValue=false;
    }
}

Comportamiento cambiado de propiedad para estructuras y enumeraciones

Si el tipo de dependencyProperty es una enumeración o una estructura, se puede invocar la devolución de llamada incluso si los valores internos de la estructura o el valor de enumeración no cambiaron. Esto es diferente de un primitivo del sistema, como una cadena donde solo se invoca si el valor ha cambiado. Este es un efecto secundario de las operaciones box y unbox en estos valores que se realizan internamente. Si tiene un método PropertyChangedCallback para una propiedad en la que el valor es una enumeración o estructura, debe comparar OldValue y NewValue mediante la conversión de los valores usted mismo y el uso de los operadores de comparación sobrecargados que están disponibles para los valores de conversión ahora. O bien, si no hay ningún operador de este tipo disponible (que podría ser el caso de una estructura personalizada), es posible que tenga que comparar los valores individuales. Normalmente, elegiría no hacer nada si el resultado es que los valores no han cambiado.

private static void OnVisibilityValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
    if ((Visibility)e.NewValue != (Visibility)e.OldValue)
    {
        //value really changed, invoke your changed logic here
    } // else this was invoked because of boxing, do nothing
}
Private Shared Sub OnVisibilityValueChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
    If CType(e.NewValue,Visibility) != CType(e.OldValue,Visibility) Then
        '  value really changed, invoke your changed logic here
    End If
    '  else this was invoked because of boxing, do nothing
End Sub
static void OnVisibilityValueChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const& e)
{
    auto oldVisibility{ winrt::unbox_value<Windows::UI::Xaml::Visibility>(e.OldValue()) };
    auto newVisibility{ winrt::unbox_value<Windows::UI::Xaml::Visibility>(e.NewValue()) };

    if (newVisibility != oldVisibility)
    {
        // The value really changed; invoke your property-changed logic here.
    }
    // Otherwise, OnVisibilityValueChanged was invoked because of boxing; do nothing.
}
static void OnVisibilityValueChanged(DependencyObject^ d, DependencyPropertyChangedEventArgs^ e)
{
    if ((Visibility)e->NewValue != (Visibility)e->OldValue)
    {
        //value really changed, invoke your changed logic here
    } 
    // else this was invoked because of boxing, do nothing
    }
}

procedimientos recomendados

Tenga en cuenta las siguientes consideraciones como procedimientos recomendados cuando defina la propiedad de dependencia personalizada.

DependencyObject y subprocesos

Todas las instancias de DependencyObject deben crearse en el subproceso de interfaz de usuario que está asociado a la ventana actual que muestra una aplicación de Windows Runtime. Aunque se debe crear cada DependencyObject en el subproceso principal de la interfaz de usuario, se puede tener acceso a los objetos mediante una referencia del distribuidor desde otros subprocesos mediante una llamada a Dispatcher.

Los aspectos de subproceso de DependencyObject son relevantes porque, por lo general, significa que solo el código que se ejecuta en el subproceso de la interfaz de usuario puede cambiar o incluso leer el valor de una propiedad de dependencia. Normalmente, los problemas de subproceso se pueden evitar en código de interfaz de usuario típico que hace un uso correcto de patrones asincrónicos y subprocesos de trabajo en segundo plano. Normalmente, solo se producen problemas de subprocesos relacionados con DependencyObject si define sus propios tipos dependencyObject e intenta usarlos para orígenes de datos u otros escenarios en los que dependencyObject no es necesariamente adecuado.

Evitar singletons involuntarias

Un singleton accidental puede ocurrir si declara una propiedad de dependencia que toma un tipo de referencia y llama a un constructor para ese tipo de referencia como parte del código que establece propertyMetadata. Lo que sucede es que todos los usos de la propiedad de dependencia comparten solo una instancia de PropertyMetadata y, por tanto, intentan compartir el tipo de referencia único que ha construido. Las subpropiedades de ese tipo de valor que establezca a través de la propiedad de dependencia se propagan a otros objetos de manera que no haya pensado.

Puede usar constructores de clase para establecer valores iniciales para una propiedad de dependencia de tipo de referencia si desea un valor distinto de NULL, pero tenga en cuenta que se consideraría un valor local para fines de información general de las propiedades de dependencia. Puede que sea más adecuado usar una plantilla para este propósito, si la clase admite plantillas. Otra manera de evitar un patrón singleton, pero seguir proporcionando un valor predeterminado útil, es exponer una propiedad estática en el tipo de referencia que proporciona un valor predeterminado adecuado para los valores de esa clase.

Propiedades de dependencia de tipo de colección

Las propiedades de dependencia de tipo de colección presentan algunos problemas de implementación adicionales que se deben tener en cuenta.

Las propiedades de dependencia de tipo de colección son relativamente poco frecuentes en la API de Windows Runtime. En la mayoría de los casos, puede usar colecciones en las que los elementos son una subclase DependencyObject , pero la propia propiedad de colección se implementa como una propiedad CLR o C++ convencional. Esto se debe a que las colecciones no se ajustan necesariamente a algunos escenarios típicos en los que intervienen las propiedades de dependencia. Por ejemplo:

  • Normalmente no animas una colección.
  • Normalmente no rellena previamente los elementos de una colección con estilos o una plantilla.
  • Aunque el enlace a colecciones es un escenario principal, no es necesario que una colección sea una propiedad de dependencia para ser un origen de enlace. En el caso de los destinos de enlace, es más habitual usar subclases de ItemsControl o DataTemplate para admitir elementos de colección o usar patrones de modelo de vista. Para obtener más información sobre el enlace hacia y desde colecciones, consulte Enlace de datos en profundidad.
  • Las notificaciones de los cambios de recopilación se abordan mejor a través de interfaces como INotifyPropertyChanged o INotifyCollectionChanged, o mediante la derivación del tipo de colección de ObservableCollection<T>.

No obstante, existen escenarios para propiedades de dependencia de tipo de colección. En las tres secciones siguientes se proporcionan algunas instrucciones sobre cómo implementar una propiedad de dependencia de tipo de colección.

Inicialización de la colección

Al crear una propiedad de dependencia, puede establecer un valor predeterminado mediante metadatos de propiedad de dependencia. Pero tenga cuidado de no usar una colección estática singleton como valor predeterminado. En su lugar, debe establecer deliberadamente el valor de la colección en una colección única (instancia) como parte de la lógica del constructor de clases para la clase propietaria de la propiedad de colección.

// WARNING - DO NOT DO THIS
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register(
  nameof(Items),
  typeof(IList<object>),
  typeof(ImageWithLabelControl),
  new PropertyMetadata(new List<object>())
);

// DO THIS Instead
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register(
  nameof(Items),
  typeof(IList<object>),
  typeof(ImageWithLabelControl),
  new PropertyMetadata(null)
);

public ImageWithLabelControl()
{
    // Need to initialize in constructor instead
    Items = new List<object>();
}

DependencyProperty y el valor predeterminado de PropertyMetadata forman parte de la definición estática de DependencyProperty. Al proporcionar un valor de colección predeterminado (u otra instancia) como valor predeterminado, se compartirá entre todas las instancias de la clase en lugar de cada clase que tenga su propia colección, como se haría normalmente.

Notificaciones de cambios

La definición de la colección como propiedad de dependencia no proporciona automáticamente una notificación de cambio para los elementos de la colección en virtud del sistema de propiedades que invoca el método de devolución de llamada "PropertyChanged". Si desea recibir notificaciones para colecciones o elementos de colección (por ejemplo, para un escenario de enlace de datos), implemente la interfaz INotifyPropertyChanged o INotifyCollectionChanged . Para obtener más información, consulta el tema Enlace de datos en profundidad.

Consideraciones de seguridad de las propiedades de dependencia

Declare las propiedades de dependencia como propiedades públicas. Declare identificadores de propiedad de dependencia como miembros estáticos públicos de solo lectura. Incluso si intenta declarar otros niveles de acceso permitidos por un lenguaje (como protegido), siempre se puede acceder a una propiedad de dependencia a través del identificador en combinación con las API del sistema de propiedades. Declarar el identificador de propiedad de dependencia como interno o privado no funcionará, ya que el sistema de propiedades no puede funcionar correctamente.

Las propiedades contenedoras son realmente útiles, los mecanismos de seguridad aplicados a los contenedores se pueden omitir llamando a GetValue o SetValue en su lugar. Por lo tanto, mantenga las propiedades contenedoras públicas; de lo contrario, simplemente hace que su propiedad sea más difícil para que los autores de llamadas legítimos usen sin proporcionar ninguna ventaja de seguridad real.

Windows Runtime no proporciona una manera de registrar una propiedad de dependencia personalizada como de solo lectura.

Propiedades de dependencia y constructores de clases

Existe un principio general de que los constructores de clase no deben llamar a métodos virtuales. Esto se debe a que se puede llamar a constructores para realizar la inicialización base de un constructor de clase derivada y escribir el método virtual a través del constructor podría producirse cuando la instancia de objeto que se construye aún no se inicializa completamente. Cuando derive de cualquier clase que ya derive de DependencyObject, recuerde que el propio sistema de propiedades llama a y expone métodos virtuales internamente como parte de sus servicios. Para evitar posibles problemas con la inicialización en tiempo de ejecución, no establezca valores de propiedad de dependencia en constructores de clases.

Registro de las propiedades de dependencia para aplicaciones de C++/CX

La implementación para registrar una propiedad en C++/CX es más complicada que C#, tanto debido a la separación en el archivo de encabezado como de implementación, y también porque la inicialización en el ámbito raíz del archivo de implementación es un procedimiento incorrecto. (Extensiones de componentes de Visual C++ (C++/CX) coloca código de inicializador estático desde el ámbito raíz directamente en DllMain, mientras que los compiladores de C# asignan los inicializadores estáticos a clases y, por tanto, evitan problemas de bloqueo de carga de DllMain . El procedimiento recomendado aquí es declarar una función auxiliar que realiza todo el registro de propiedades de dependencia para una clase, una función por clase. A continuación, para cada clase personalizada que consume la aplicación, tendrás que hacer referencia a la función de registro del asistente expuesta por cada clase personalizada que quieras usar. Llame a cada función de registro del asistente una vez como parte del constructor Application (App::App()), antes de InitializeComponent. Ese constructor solo se ejecuta cuando se hace referencia a la aplicación por primera vez, no se volverá a ejecutar si se reanuda una aplicación suspendida, por ejemplo. Además, como se ha visto en el ejemplo anterior de registro de C++, la comprobación de nullptr alrededor de cada llamada register es importante: es seguro que ningún autor de llamada de la función puede registrar la propiedad dos veces. Una segunda llamada de registro probablemente bloquearía la aplicación sin dicha comprobación porque el nombre de la propiedad sería un duplicado. Puedes ver este patrón de implementación en el ejemplo de controles personalizados y de usuario XAML si examinas el código de la versión de C++/CX del ejemplo.