¿Cómo lo uso RelativeSource
con enlaces WPF y cuáles son los diferentes casos de uso?
¿Cómo lo uso RelativeSource
con enlaces WPF y cuáles son los diferentes casos de uso?
Respuestas:
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 un ControlTemplate)
{Binding Path=PathToProperty, RelativeSource={RelativeSource TemplatedParent}}
o, más corto (esto solo funciona para enlaces OneWay):
{TemplateBinding Path=PathToProperty}
AncestorType
.
FindAncestor
, antes AncestorType
, aparece el siguiente error: "RelativeSource no está en modo FindAncestor". (En VS2013, versión comunitaria)
{Binding Path=DataContext.SomeProperty, RelativeSource=...
. Esto fue algo inesperado para mí como novato cuando intentaba vincularme al DataContext de un padre dentro de un DataTemplate.
Binding RelativeSource={
RelativeSource Mode=FindAncestor, AncestorType={x:Type ItemType}
}
...
El atributo predeterminado de RelativeSource
es la Mode
propiedad. Aquí se proporciona un conjunto completo de valores válidos ( de MSDN ):
PreviousData Le permite vincular el elemento de datos anterior (no ese 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 enlazado a datos). Esto es similar a establecer una extensión de TemplateBinding y solo es aplicable si el enlace está dentro de una plantilla.
Auto Se refiere al elemento en el que está estableciendo el enlace y le permite vincular una propiedad de ese elemento a otra propiedad en el mismo elemento.
FindAncestor Se refiere al antepasado en la cadena padre del elemento enlazado a datos. Puede usar esto para enlazar a un antepasado de un tipo específico o sus subclases. Este es el modo que utiliza si desea especificar AncestorType y / o AncestorLevel.
Aquí hay una explicación más visual en el contexto de una arquitectura MVVM:
{Binding Message}
(un poco más simple ...)
Path=DataContext.Message
para que el enlace funcione. Esto tiene sentido, dado que puede hacer enlaces relativos a ancho / alto / etc. de un control.
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 vinculación particulares cuando intentamos vincular una propiedad de un objeto determinado 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 pieza de XAML en caso de desarrollo de control personalizado y, finalmente, en caso de utilizar un diferencial de una serie de datos vinculados. Todas esas situaciones se expresan como modos de fuente relativa. Expondré todos esos casos uno por uno.
- Modo propio:
Imagine este caso, un rectángulo que queremos que su altura siempre sea igual a su ancho, digamos un cuadrado. Podemos hacer esto usando el nombre del elemento
<Rectangle Fill="Red" Name="rectangle" Height="100" Stroke="Black" Canvas.Top="100" Canvas.Left="100" Width="{Binding ElementName=rectangle, Path=Height}"/>
Pero en este caso anterior, estamos obligados a indicar el nombre del objeto de enlace, es decir, el rectángulo. Podemos alcanzar el mismo propósito de manera diferente usando RelativeSource
<Rectangle Fill="Red" Height="100" Stroke="Black" Width="{Binding RelativeSource={RelativeSource Self}, Path=Height}"/>
Para ese caso, no estamos obligados a mencionar el nombre del objeto de enlace y el ancho siempre será igual a la altura 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 enlace. Imaginemos otro caso ahora:
<TextBlock Width="{Binding RelativeSource={RelativeSource Self}, Path=Parent.ActualWidth}"/>
El caso anterior se utiliza para vincular una propiedad dada de un elemento dado a uno de sus elementos primarios directos, ya que este elemento posee una propiedad que se llama Principal. Esto nos lleva a otro modo de fuente relativa que es el FindAncestor.
- Modo FindAncestor
En este caso, una propiedad de un elemento dado estará vinculada a uno de sus padres, Of Corse. La principal diferencia con el caso anterior es el hecho de que depende de usted determinar el tipo de antepasado y el rango de antepasado en la jerarquía para vincular la propiedad. Por cierto, intenta jugar con esta pieza de XAML
<Canvas Name="Parent0"> <Border Name="Parent1" Width="{Binding RelativeSource={RelativeSource Self}, Path=Parent.ActualWidth}" Height="{Binding RelativeSource={RelativeSource Self}, Path=Parent.ActualHeight}"> <Canvas Name="Parent2"> <Border Name="Parent3" Width="{Binding RelativeSource={RelativeSource Self}, Path=Parent.ActualWidth}" Height="{Binding RelativeSource={RelativeSource Self}, Path=Parent.ActualHeight}"> <Canvas Name="Parent4"> <TextBlock FontSize="16" Margin="5" Text="Display the name of the ancestor"/> <TextBlock FontSize="16" Margin="50" Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Border}, AncestorLevel=2},Path=Name}" Width="200"/> </Canvas> </Border> </Canvas> </Border> </Canvas>
La situación anterior es de dos elementos TextBlock que están incrustados dentro de una serie de bordes y elementos de lienzo que representan a sus padres jerárquicos. El segundo TextBlock mostrará el nombre del padre dado en el nivel de fuente relativo.
Así que trate de cambiar AncestorLevel = 2 a AncestorLevel = 1 y vea qué sucede. Luego intente cambiar el tipo del antepasado de AncestorType = Border a AncestorType = Canvas y vea qué sucede.
El texto que se muestra cambiará de acuerdo con el tipo y nivel de Ancestro. Entonces, ¿qué sucede si el nivel de ancestro no es adecuado para el tipo de ancestro? Esta es una buena pregunta, sé que estás a punto de hacerla. La respuesta es que no se lanzarán excepciones y no se mostrará nada en el nivel de TextBlock.
- Padre con plantilla
Este modo permite vincular una propiedad ControlTemplate dada a una propiedad del control al que se aplica ControlTemplate. Para comprender bien el problema aquí hay un ejemplo a continuación
<Window.Resources> <ControlTemplate x:Key="template"> <Canvas> <Canvas.RenderTransform> <RotateTransform Angle="20"/> </Canvas.RenderTransform> <Ellipse Height="100" Width="150" Fill="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Background}"> </Ellipse> <ContentPresenter Margin="35" Content="{Binding RelativeSource={RelativeSource TemplatedParent},Path=Content}"/> </Canvas> </ControlTemplate> </Window.Resources> <Canvas Name="Parent0"> <Button Margin="50" Template="{StaticResource template}" Height="0" Canvas.Left="0" Canvas.Top="0" Width="0"> <TextBlock FontSize="22">Click me</TextBlock> </Button> </Canvas>
Si quiero 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 TemplateBinding, que es una especie de abreviatura de la primera, pero TemplateBinding se evalúa en tiempo de compilación en contraste con TemplatedParent, que se evalúa justo 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.
ListView
. El padre tiene 2 ListView
niveles más por debajo. Esto me ayudó a prevenir el contagio de datos en cada máquina virtual posterior de cada ListView
'sDataTemplate
En WPF, el RelativeSource
enlace expone tres properties
para establecer:
1. Modo: este enum
podría tener cuatro valores:
a. PreviousData (
value=0
): asigna el valor anterior deproperty
al al límitesi. TemplatedParent (
value=1
): Esto se usa cuando se define eltemplates
control de cualquier control y se quiere vincular a un valor / Propiedad delcontrol
.Por ejemplo, defina
ControlTemplate
:
<ControlTemplate>
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</ControlTemplate>
C. Self (
value=2
): cuando queremos unirnos desde aself
o aproperty
of self.Por ejemplo: Enviar comprobado estado de
checkbox
queCommandParameter
mientras se ajusta laCommand
deCheckBox
<CheckBox ...... CommandParameter="{Binding RelativeSource={RelativeSource Self},Path=IsChecked}" />
re. FindAncestor (
value=3
): cuando se quiere vincular desde un padrecontrol
enVisual Tree
.Por ejemplo: enlace a
checkbox
inrecords
if agrid
, ifheader
checkbox
está marcado
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid}}, Path=DataContext.IsHeaderChecked, Mode=TwoWay}" />
2.FindAncestor
Tipo de antepasado : cuando el modo es , defina qué tipo de antepasado
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid}}
3. AncestorLevel: cuando mode esFindAncestor
entonces qué nivel de ancestro (si hay dos tipos de padres en el mismovisual tree
)
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid, AncestorLevel=1}}
Arriba están todos los casos de uso para
RelativeSource binding
.
Vale la pena señalar que para aquellos que se encuentran con este pensamiento de Silverlight:
Silverlight ofrece solo un subconjunto reducido, de estos comandos
Creé una biblioteca para simplificar la sintaxis 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 del método que el código ya estaba optimizado usando RelayCommand
el último que verifiqué que no es una parte nativa de WPF. Sin eso, el ejemplo 'ANTES' hubiera sido aún más largo.
Algunas partes útiles:
Aquí se explica 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);
Copié esto en gran parte de Binding Relative Source en el código Behind .
Además, la página MSDN es bastante buena en lo que respecta a los ejemplos: Clase RelativeSource
Acabo de publicar otra solución para acceder al DataContext de un elemento padre en Silverlight que funciona para mí. Lo utiliza Binding ElementName
.
No leí todas las respuestas, pero solo quiero agregar esta información en caso de un comando de fuente relativa 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.
Este es un ejemplo del uso de este patrón que funcionó para mí en cuadrículas de datos vacías.
<Style.Triggers>
<DataTrigger Binding="{Binding Items.Count, RelativeSource={RelativeSource Self}}" Value="0">
<Setter Property="Background">
<Setter.Value>
<VisualBrush Stretch="None">
<VisualBrush.Visual>
<TextBlock Text="We did't find any matching records for your search..." FontSize="16" FontWeight="SemiBold" Foreground="LightCoral"/>
</VisualBrush.Visual>
</VisualBrush>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
Si un elemento no es parte del árbol visual, RelativeSource nunca funcionará.
En este caso, debe probar una técnica diferente, iniciada por Thomas Levesque.
Tiene la solución en su blog en [WPF] Cómo enlazar a datos cuando el DataContext no se hereda . ¡Y funciona absolutamente brillante!
En el improbable caso de que su blog esté caído, el Apéndice A contiene una copia espejo de su artículo .
Por favor no comente aquí, comente directamente en su blog .
La propiedad DataContext en WPF es extremadamente útil, ya que es heredada automáticamente por todos los elementos secundarios del elemento donde lo asigna; por lo tanto, no necesita configurarlo de nuevo en cada elemento que desee vincular. Sin embargo, en algunos casos el DataContext no es accesible: sucede para elementos que no son parte del árbol visual o lógico. Puede ser muy difícil vincular una propiedad a esos elementos ...
Vamos a ilustrar 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, según el valor de una propiedad ShowPrice expuesta por ViewModel. El enfoque obvio es vincular la Visibilidad de la columna a la propiedad ShowPrice:
<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
Visibility="{Binding ShowPrice,
Converter={StaticResource visibilityConverter}}"/>
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:
System.Windows.Data Error: 2: No se puede encontrar FrameworkElement o FrameworkContentElement para el elemento de destino. BindingExpression: Path = ShowPrice; DataItem = nulo; 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 RelativeSource en DataGrid:
<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
Visibility="{Binding DataContext.ShowPrice,
Converter={StaticResource visibilityConverter},
RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}}"/>
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:
<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
Visibility="{Binding IsChecked,
Converter={StaticResource visibilityConverter},
ElementName=chkShowPrice}"/>
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 código subyacente, que generalmente preferimos evitar al usar el patrón MVVM ... Pero no me voy a rendir tan pronto, al menos no mientras que hay otras opciones a considerar 😉
La solución a nuestro problema es realmente bastante simple y aprovecha la clase Freezable. El propósito principal de esta clase es definir objetos que tienen 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 sé el mecanismo exacto que permite este comportamiento, pero vamos a aprovecharlo para que nuestro enlace funcione ...
La idea es crear una clase (la llamé BindingProxy por razones que deberían ser 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));
}
Luego podemos declarar una instancia de esta clase en los recursos de DataGrid y vincular la propiedad Data al DataContext actual:
<DataGrid.Resources>
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
</DataGrid.Resources>
El último paso es especificar este objeto BindingProxy (de fácil acceso con StaticResource) como el origen del enlace:
<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
Visibility="{Binding Data.ShowPrice,
Converter={StaticResource visibilityConverter},
Source={StaticResource proxy}}"/>
Tenga en cuenta que la ruta de enlace se ha prefijado 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.