¿Cómo uso enlaces WPF con RelativeSource?

¿Cómo uso RelativeSource con enlaces WPF y cuáles son los diferentes casos de uso?

Si desea enlazar a otra propiedad en el objeto:

 {Binding Path=PathToProperty, RelativeSource={RelativeSource Self}} 

Si desea obtener una propiedad de un antepasado:

 {Binding Path=PathToProperty, RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}} 

Si desea obtener una propiedad en el padre con plantilla (para que pueda hacer enlaces bidireccionales en una plantilla ControlTemplate)

 {Binding Path=PathToProperty, RelativeSource={RelativeSource TemplatedParent}} 

o, más corto (esto solo funciona para enlaces OneWay):

 {TemplateBinding Path=PathToProperty} 
 Binding RelativeSource={ RelativeSource Mode=FindAncestor, AncestorType={x:Type ItemType} } ... 

El atributo predeterminado de RelativeSource es la propiedad Mode . Aquí se proporciona un conjunto completo de valores válidos ( de MSDN ):

  • PreviousData Le permite vincular el elemento de datos anterior (no el control que contiene el elemento de datos) en la lista de elementos de datos que se muestran.

  • TemplatedParent Hace referencia al elemento al que se aplica la plantilla (en la que existe el elemento vinculado a datos). Esto es similar a establecer una TemplateBindingExtension y solo es aplicable si el enlace está dentro de una plantilla.

  • Auto Se refiere al elemento en el que está configurando el enlace y le permite vincular una propiedad de ese elemento a otra propiedad en el mismo elemento.

  • FindAncestor Hace referencia al antecesor de la cadena principal del elemento vinculado a datos. Puede usar esto para enlazar a un antecesor de un tipo específico o sus subclases. Este es el modo que usa si quiere especificar AncestorType y / o AncestorLevel.

Aquí hay una explicación más visual en el contexto de una architecture MVVM:

enter image description here

Imagina este caso, un rectángulo que queremos que su altura sea siempre igual a su ancho, digamos un cuadrado. Podemos hacer esto usando el nombre del elemento

  

Pero en este caso anterior estamos obligados a indicar el nombre del objeto vinculante, es decir, el rectángulo. Podemos alcanzar el mismo propósito de manera diferente usando RelativeSource

  

Para ese caso no estamos obligados a mencionar el nombre del objeto vinculante y el ancho siempre será igual al alto cada vez que se cambie la altura.

Si desea parametrizar el ancho para que sea la mitad de la altura, puede hacerlo agregando un convertidor a la extensión de marcado de encuadernación. Imaginemos otro caso ahora:

   

El caso anterior se utiliza para vincular una propiedad dada de un elemento dado con uno de sus elementos primarios directos, ya que este elemento contiene una propiedad que se llama Principal. Esto nos lleva a otro modo fuente relativo que es FindAncestor.

Bechir Bejaoui expone los casos de uso de RelativeSources en WPF en su artículo aquí :

RelativeSource es una extensión de marcado que se utiliza en casos de enlace particulares cuando intentamos vincular una propiedad de un objeto dado a otra propiedad del objeto en sí, cuando intentamos vincular una propiedad de un objeto a otro de sus padres relativos. cuando se vincula un valor de propiedad de dependencia a una parte de XAML en caso de desarrollo de control personalizado y, finalmente, en caso de utilizar un diferencial de una serie de datos enlazados. Todas esas situaciones se expresan como modos fuente relativos. Expondré todos esos casos uno por uno.

  1. Modo Self:

Imagina este caso, un rectángulo que queremos que su altura sea siempre igual a su ancho, digamos un cuadrado. Podemos hacer esto usando el nombre del elemento

  

Pero en este caso anterior estamos obligados a indicar el nombre del objeto vinculante, es decir, el rectángulo. Podemos alcanzar el mismo propósito de manera diferente usando RelativeSource

  

Para ese caso no estamos obligados a mencionar el nombre del objeto vinculante y el ancho siempre será igual al alto cada vez que se cambie la altura.

Si desea parametrizar el ancho para que sea la mitad de la altura, puede hacerlo agregando un convertidor a la extensión de marcado de encuadernación. Imaginemos otro caso ahora:

   

El caso anterior se utiliza para vincular una propiedad dada de un elemento dado con uno de sus elementos primarios directos, ya que este elemento contiene una propiedad que se llama Principal. Esto nos lleva a otro modo fuente relativo que es FindAncestor.

  1. Modo FindAncestor

En este caso, una propiedad de un elemento dado estará vinculada a uno de sus padres, Of Corse. La diferencia principal con el caso anterior es el hecho de que depende de usted determinar el tipo de antepasado y el rango de ancestro en la jerarquía para vincular la propiedad. Por cierto, intenta jugar con esta pieza de XAML

             

La situación anterior es de dos elementos TextBlock que están incrustados dentro de una serie de bordes y elementos canvas que representan sus padres jerárquicos. El segundo TextBlock mostrará el nombre del padre dado en el nivel de fuente relativa.

Intente cambiar AncestorLevel = 2 a AncestorLevel = 1 y vea qué sucede. Luego intente cambiar el tipo del ancestro de AncestorType = Border a AncestorType = Canvas y vea qué sucede.

El texto mostrado cambiará de acuerdo con el tipo y nivel del Ancestro. Entonces, ¿qué ocurre si el nivel ancestro no es adecuado para el tipo ancestro? Esta es una buena pregunta, sé que estás a punto de preguntarlo. La respuesta es que no se lanzarán excepciones y nada se mostrará en el nivel de Bloqueo de texto.

  1. TemplatedParent

Este modo permite vincular una propiedad determinada de ControlTemplate a una propiedad del control al que se aplica ControlTemplate. Para entender bien el problema aquí hay un ejemplo abajo

                

Si deseo aplicar las propiedades de un control dado a su plantilla de control, entonces puedo usar el modo TemplatedParent. También hay una similar a esta extensión de marcado que es el TemplateBinding que es una especie de mano corta del primero, pero el TemplateBinding se evalúa en tiempo de comstackción en el contraste del TemplatedParent que se evalúa inmediatamente después del primer tiempo de ejecución. Como puede observar en la siguiente figura, el fondo y el contenido se aplican desde el botón a la plantilla de control.

No te olvides de TemplatedParent:

  

o

 {Binding RelativeSource={RelativeSource TemplatedParent}} 

En WPF, el enlace RelativeSource expone tres properties para establecer:

1. Modo: Esta es una enum que podría tener cuatro valores:

a. PreviousData ( value=0 ): asigna el valor anterior de la property al límite

segundo. TemplatedParent ( value=1 ): se utiliza al definir las templates de cualquier control y desea vincularse a un valor / propiedad del control .

Por ejemplo, defina ControlTemplate :

     

do. Yo ( value=2 ): cuando queremos vincularnos desde un self o una property del yo.

Por ejemplo: Enviar el estado marcado de la checkbox de checkbox como parámetro de Command al configurar el Command en CheckBox

  

re. FindAncestor ( value=3 ): cuando desea enlazar desde un control principal en Visual Tree .

Por ejemplo: enlazar una checkbox de checkbox en los records si está marcada una checkbox de checkbox de grid , si header

  

2. AncestorType: cuando el modo es FindAncestor entonces define qué tipo de ancestro

 RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid}} 

3. AncestorLevel: cuando el modo es FindAncestor entonces, ¿qué nivel de ancestro (si hay dos mismos tipos de padres en visual tree )?

 RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid, AncestorLevel=1}} 

Arriba están todos los casos de uso para el RelativeSource binding .

Aquí hay un enlace de referencia .

Vale la pena señalar que para aquellos que tropiezan con este pensamiento de Silverlight:

Silverlight ofrece un subconjunto reducido solamente, de estos comandos

Creé una biblioteca para simplificar la syntax de enlace de WPF, lo que facilita el uso de RelativeSource. Aquí hay unos ejemplos. Antes de:

 {Binding Path=PathToProperty, RelativeSource={RelativeSource Self}} {Binding Path=PathToProperty, RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}} {Binding Path=PathToProperty, RelativeSource={RelativeSource TemplatedParent}} {Binding Path=Text, ElementName=MyTextBox} 

Después:

 {BindTo PathToProperty} {BindTo Ancestor.typeOfAncestor.PathToProperty} {BindTo Template.PathToProperty} {BindTo #MyTextBox.Text} 

Aquí hay un ejemplo de cómo se simplifica el enlace de métodos. Antes de:

 // C# code private ICommand _saveCommand; public ICommand SaveCommand { get { if (_saveCommand == null) { _saveCommand = new RelayCommand(x => this.SaveObject()); } return _saveCommand; } } private void SaveObject() { // do something } // XAML {Binding Path=SaveCommand} 

Después:

 // C# code private void SaveObject() { // do something } // XAML {BindTo SaveObject()} 

Puede encontrar la biblioteca aquí: http://www.simplygoodcode.com/2012/08/simpler-wpf-binding.html

Tenga en cuenta en el ejemplo ‘ANTES’ que uso para el enlace de métodos que el código ya estaba optimizado mediante el uso de RelayCommand que la última vez que revisé no es una parte nativa de WPF. Sin eso, el ejemplo ‘ANTES’ hubiera sido aún más largo.

Algunas partes útiles:

A continuación, le mostramos cómo hacerlo principalmente en código:

 Binding b = new Binding(); b.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, this.GetType(), 1); b.Path = new PropertyPath("MyElementThatNeedsBinding"); MyLabel.SetBinding(ContentProperty, b); 

En gran medida copié esto de la Fuente Relativa Vinculante en el código Detrás .

Además, la página de MSDN es bastante buena en cuanto a ejemplos: RelativeSource Class

Acabo de publicar otra solución para acceder al DataContext de un elemento principal en Silverlight que funciona para mí. Utiliza Binding ElementName .

Este es un ejemplo del uso de este patrón que funcionó para mí en datagrids vacías.

              

No leí todas las respuestas, pero solo quiero agregar esta información en caso de un enlace de comando de fuente relativo de un botón.

Cuando utiliza una fuente relativa con Mode=FindAncestor , el enlace debe ser como:

 Command="{Binding Path=DataContext.CommandProperty, RelativeSource={...}}" 

Si no agrega DataContext en su ruta, en el momento de la ejecución no puede recuperar la propiedad.

Si un elemento no es parte del árbol visual, RelativeSource nunca funcionará.

En este caso, debes probar una técnica diferente, iniciada por Thomas Levesque.

Él tiene la solución en su blog en [WPF] Cómo enlazar a los datos cuando el DataContext no se hereda . ¡Y funciona absolutamente genial!

En el caso poco probable de que su blog esté caído, el Apéndice A contiene una copia espejo de su artículo .

Por favor, no comenten aquí, por favor comenten directamente en su publicación del blog .

Apéndice A: espejo de una publicación de blog

La propiedad DataContext en WPF es extremadamente útil, ya que es heredada automáticamente por todos los elementos secundarios del elemento donde la asigna; por lo tanto, no es necesario volver a establecerlo en cada elemento que desee vincular. Sin embargo, en algunos casos, el DataContext no es accesible: ocurre para elementos que no son parte del árbol visual o lógico. Puede ser muy difícil vincular una propiedad en esos elementos …

Vamos a ilustrarlo con un ejemplo simple: queremos mostrar una lista de productos en un DataGrid. En la cuadrícula, queremos poder mostrar u ocultar la columna Precio, en función del valor de una propiedad ShowPrice expuesta por ViewModel. El enfoque obvio es vincular la visibilidad de la columna a la propiedad ShowPrice:

  

Desafortunadamente, cambiar el valor de ShowPrice no tiene ningún efecto, y la columna siempre está visible … ¿por qué? Si miramos la ventana Salida en Visual Studio, notamos la siguiente línea:

Error de System.Windows.Data: 2: no se puede encontrar FrameworkElemento o FrameworkContentElement para el elemento de destino. BindingExpression: Path = ShowPrice; DataItem = null; el elemento de destino es ‘DataGridTextColumn’ (HashCode = 32685253); la propiedad de destino es ‘Visibilidad’ (tipo ‘Visibilidad’)

El mensaje es bastante críptico, pero el significado es bastante simple: WPF no sabe qué FrameworkElement usar para obtener el DataContext, porque la columna no pertenece al árbol visual o lógico de DataGrid.

Podemos intentar ajustar el enlace para obtener el resultado deseado, por ejemplo, estableciendo el RelativeSource en el DataGrid mismo:

  

O podemos agregar un CheckBox vinculado a ShowPrice e intentar vincular la visibilidad de la columna a la propiedad IsChecked especificando el nombre del elemento:

  

Pero ninguna de estas soluciones parece funcionar, siempre obtenemos el mismo resultado …

En este punto, parece que el único enfoque viable sería cambiar la visibilidad de la columna en el código subyacente, que generalmente preferimos evitar al usar el patrón MVVM … Pero no voy a rendirme tan pronto, al menos no mientras que hay otras opciones a considerar 😉

La solución a nuestro problema es bastante simple y aprovecha la clase Freezable. El objective principal de esta clase es definir objetos que tengan un estado modificable y de solo lectura, pero la característica interesante en nuestro caso es que los objetos Freezable pueden heredar el DataContext incluso cuando no están en el árbol visual o lógico. No conozco el mecanismo exacto que permite este comportamiento, pero vamos a aprovecharlo para que nuestro trabajo vinculante …

La idea es crear una clase (lo llamé BindingProxy por razones que deberían volverse obvias muy pronto) que hereda Freezable y declara una propiedad de dependencia de datos:

 public class BindingProxy : Freezable { #region Overrides of Freezable protected override Freezable CreateInstanceCore() { return new BindingProxy(); } #endregion public object Data { get { return (object)GetValue(DataProperty); } set { SetValue(DataProperty, value); } } // Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc... public static readonly DependencyProperty DataProperty = DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null)); } 

A continuación, podemos declarar una instancia de esta clase en los recursos de DataGrid y enlazar la propiedad Data con el DataContext actual:

    

El último paso es especificar este objeto BindingProxy (fácilmente accesible con StaticResource) como Fuente para el enlace:

  

Tenga en cuenta que la ruta de enlace ha sido prefijada con “Datos”, ya que la ruta ahora es relativa al objeto BindingProxy.

El enlace ahora funciona correctamente y la columna se muestra u oculta correctamente según la propiedad ShowPrice.