¿Cuál es la diferencia entre una propiedad de dependencia y una propiedad adjunta en WPF?

¿Cuál es la diferencia entre una propiedad de dependencia (personalizada) y una propiedad adjunta en WPF? ¿Cuáles son los usos para cada uno? ¿Cómo difieren típicamente las implementaciones?

Las propiedades adjuntas son un tipo de propiedad de dependencia. La diferencia está en cómo se usan.

Con una propiedad adjunta, la propiedad se define en una clase que no es la misma clase para la que se está utilizando. Esto se usa generalmente para el diseño. Buenos ejemplos son Panel.ZIndex o Grid.Row: usted aplica esto a un control (es decir, Button), pero en realidad está definido en Panel o Grid. La propiedad está “adjunta” a la instancia del botón.

Esto permite que un contenedor, por ejemplo, cree propiedades que se puedan usar en cualquier elemento de UI.

En cuanto a las diferencias de implementación, básicamente se trata simplemente de utilizar Registrarse frente a Registrarse cuando se define la propiedad.

Las propiedades adjuntas están pensadas básicamente para los elementos del contenedor. Por ejemplo, si tiene una cuadrícula y tiene grid.row, esto se considera una propiedad adjunta de un elemento de cuadrícula. También puede usar esta propiedad en texbox, botón, etc. para establecer su colocar en la grilla

La propiedad de dependencia es como que la propiedad pertenece básicamente a otra clase y se usa en otra clase. Por ejemplo: al igual que tiene un rectángulo, aquí la altura y el ancho son propiedades regulares de un rectángulo, pero la izquierda y la parte superior son propiedad de la propiedad, ya que pertenecen a la clase Lienzos.

Abstracto

Como encontré poca o ninguna documentación sobre el asunto, me costó un poco hurgar en el código fuente , pero aquí hay una respuesta.

Existe una diferencia entre registrar una propiedad de dependencia como una propiedad regular y como una propiedad adjunta, que no sea “filosófica” ( las propiedades regulares están destinadas a ser utilizadas por el tipo declarante y sus tipos derivados, las propiedades adjuntas están destinadas a ser utilizadas como extensiones en instancias de DependencyObject arbitrarias ). “Filosófico”, porque, como @MarqueIV notó en su comentario a la respuesta de @ ReedCopsey, las propiedades regulares también se pueden usar con instancias de DependencyObject arbitrarias.

Además, tengo que estar en desacuerdo con otras respuestas que afirman que la propiedad adjunta es “tipo de propiedad de dependencia”, porque es engañosa: no hay ningún “tipo” de propiedades de dependencia. Al marco no le importa si la propiedad se registró como adjunta o no; ni siquiera es posible determinarlo (en el sentido de que esta información no se registra porque es irrelevante). De hecho, todas las propiedades se registran como si fueran propiedades adjuntas, pero en el caso de las regulares se hacen algunas cosas adicionales que modifican ligeramente su comportamiento.

Extracto del código

Para ahorrarte el problema de pasar por el código fuente por ti mismo, aquí hay una versión reducida de lo que sucede.

Al registrar una propiedad sin metadatos especificados, llamando

 DependencyProperty.Register( name: "MyProperty", propertyType: typeof(object), ownerType: typeof(MyClass)) 

produce exactamente el mismo resultado que llamar

 DependencyProperty.RegisterAttached( name: "MyProperty", propertyType: typeof(object), ownerType: typeof(MyClass)) 

Sin embargo, al especificar metadatos, llamar

 DependencyProperty.Register( name: "MyProperty", propertyType: typeof(object), ownerType: typeof(MyClass), typeMetadata: new FrameworkPropertyMetadata { CoerceValueCallback = CoerceCallback, DefaultValue = "default value", PropertyChangedCallback = ChangedCallback }); 

es equivalente a llamar

 var property = DependencyProperty.RegisterAttached( name: "MyProperty", propertyType: typeof(object), ownerType: typeof(MyClass), defaultMetadata: new PropertyMetadata { DefaultValue = "default value", }); property.OverrideMetadata( forType: typeof(MyClass), typeMetadata: new FrameworkPropertyMetadata { CoerceValueCallback = CoerceCallback, DefaultValue = "default value", PropertyChangedCallback = ChangedCallback }); 

Conclusiones

La diferencia clave (y única) entre las propiedades de dependencia normal y adjunta es el metadato predeterminado disponible a través de la propiedad DependencyProperty.DefaultMetadata . Esto incluso se menciona en la sección Comentarios :

Para las propiedades no vinculadas, el tipo de metadatos devuelto por esta propiedad no se puede convertir a tipos derivados del tipo PropertyMetadata , incluso si la propiedad se registró originalmente con un tipo de metadata derivado. Si desea que los metadatos originalmente registrados incluyan su tipo original de metadata posiblemente derivado, llame a GetMetadata (Type) en su lugar, pasando el tipo de registro original como parámetro.

Para las propiedades adjuntas, el tipo de metadatos devuelto por esta propiedad coincidirá con el tipo proporcionado en el método de registro original de RegisterAttached .

Esto es claramente visible en el código provisto. También se ocultan pequeños indicios en los métodos de registro, es decir, para RegisterAttached el parámetro de metadata se llama defaultMetadata , mientras que para Register se denomina typeMetadata . Para las propiedades adjuntas, los metadatos proporcionados se convierten en los metadatos predeterminados. Sin embargo, en el caso de las propiedades normales, los metadatos predeterminados siempre son una instancia nueva de PropertyMetadata con solo el valor DefaultValue (ya sea de los metadatos proporcionados o de forma automática). Solo la llamada subsiguiente a OverrideMetadata realmente usa los metadatos proporcionados.

Consecuencias

La principal diferencia práctica es que, en el caso de las propiedades normales, CoerceValueCallback y PropertyChangedCallback son aplicables solo para los tipos derivados del tipo declarado como tipo de propietario, y para las propiedades adjuntas son aplicables para todos los tipos. Por ejemplo, en este escenario:

 var d = new DependencyObject(); d.SetValue(SomeClass.SomeProperty, "some value"); 

se llamará al PropertyChangedCallback registrado si la propiedad se registró como una propiedad adjunta, pero no se llamará si se registró como una propiedad regular. Lo mismo vale para CoerceValueCallback .

Una diferencia secundaria se deriva del hecho de que OverrideMetadata requiere que el tipo suministrado se derive de DependencyObject . En la práctica, significa que el tipo de propietario para las propiedades normales debe derivarse de DependencyObject , mientras que para las propiedades adjuntas puede ser de cualquier tipo (incluidas las clases estáticas, estructuras, enumeraciones, delegates, etc.).

Suplemento

Además de la sugerencia de @ MarqueIV, en varias ocasiones he encontrado opiniones de que las propiedades regulares y adjuntas difieren en la forma en que se pueden usar en XAML . A saber, que las propiedades regulares requieren una syntax de nombre implícita en oposición a la syntax de nombre explícita requerida por las propiedades adjuntas. Esto técnicamente no es cierto , aunque en la práctica suele ser el caso. Para mayor claridad:

     

En XAML puro , las únicas reglas que rigen el uso de estas syntax son las siguientes:

  • La syntax de nombres implícitos se puede usar en un elemento si y solo si la clase que este elemento representa tiene una propiedad CLR de ese nombre
  • La syntax de nombre explícito se puede usar en un elemento si y solo si la clase especificada por la primera parte del nombre completo expone métodos de obtención / establecimiento estáticos apropiados (denominados accesadores ) con nombres que coinciden con la segunda parte del nombre completo

Satisfacer estas condiciones le permite usar la syntax correspondiente independientemente de si la propiedad de dependencia de respaldo se registró como regular o adjunta.

Ahora el concepto erróneo mencionado es causado por el hecho de que la gran mayoría de los tutoriales (junto con los fragmentos de código de Visual Studio ) le indican que use la propiedad CLR para las propiedades de dependencia regulares, y obtenga / configure los descriptores de acceso para los adjuntos. Pero no hay nada que te impida usar ambos al mismo tiempo, lo que te permite usar la syntax que prefieras.

Creo que puede definir la propiedad adjunta en la clase o puede definirla en otra clase. Siempre podemos usar propiedades adjuntas para extender los controles estándar de microsoft. Pero la propiedad de dependencia la defines en tu propio control personalizado. Por ejemplo, puede heredar su control de un control estándar y definir una propiedad de dependencia bajo su propio control y usarlo. Esto es equivalente a definir una propiedad adjunta, y usar esta propiedad adjunta en el control estándar.

Las propiedades adjuntas son un tipo especial de DependencyProperties. Le permiten asociar un valor a un objeto que no sabe nada sobre este valor. Un buen ejemplo para este concepto son los paneles de diseño. Cada panel de diseño necesita datos diferentes para alinear sus elementos secundarios. El canvas necesita Top e Left, DockPanel necesita Dock, etc. Dado que puede escribir su propio panel de diseño, la lista es infinita. Como puede ver, no es posible tener todas esas propiedades en todos los controles de WPF. La solución son propiedades adjuntas. Están definidos por el control que necesita los datos de otro control en un contexto específico. Por ejemplo, un elemento alineado por un panel de diseño principal.