Pregunta

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

¿Fue útil?

Solución

Si desea vincularse a otra propiedad del objeto:

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

Si desea obtener una propiedad sobre un antepasado:

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

Si desea obtener una propiedad en la plantilla principal (para que pueda realizar enlaces bidireccionales en un ControlTemplate)

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

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

{TemplateBinding Path=PathToProperty}

Otros consejos

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

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

  • AnteriorDatos 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.

  • Padre con plantilla Se refiere al elemento al que se aplica la plantilla (en el que existe el elemento vinculado a datos).Esto es similar a configurar TemplateBindingExtension y solo se aplica si el enlace está dentro de una plantilla.

  • Ser 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.

  • EncontrarAncestro Se refiere al antepasado en la cadena principal del elemento vinculado a datos.Puede utilizar esto para vincularse a un ancestro 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:

enter image description here

Imaginemos este caso, un rectángulo que queremos que su alto sea siempre igual a su ancho, un cuadrado digamos.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 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

<Rectangle Fill="Red" Height="100" 
                   Stroke="Black" 
                   Width="{Binding RelativeSource={RelativeSource Self},
                   Path=Height}"/>

En ese caso no estamos obligados a mencionar el nombre del objeto de encuadernación y el Ancho siempre será igual al Alto cada vez que se cambie el alto.

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 ahora otro caso:

 <TextBlock Width="{Binding RelativeSource={RelativeSource Self},
                   Path=Parent.ActualWidth}"/>

El caso anterior se utiliza para vincular una propiedad determinada de un elemento determinado a uno de sus padres directos, ya que este elemento contiene una propiedad que se llama Padre.Esto nos lleva a otro modo de fuente relativo que es el de FindAncestor.

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

El relativource es una extensión de marcado que se usa en casos vinculantes particulares cuando intentamos vincular una propiedad de un objeto dado a otra propiedad del objeto en sí, cuando intentamos unir una propiedad de un objeto a otro de sus padres relativos, Al vincular un valor de propiedad de dependencia a una pieza de XAML en caso de desarrollo de control personalizado y finalmente en el caso de usar un diferencial de una serie de datos unidos.Todas esas situaciones se expresan como modos de origen relativo.Expondré todos esos casos uno por uno.

  1. 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 vinculante, a saber, el rectángulo.Podemos alcanzar el mismo propósito de manera diferente utilizando el relativource

<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 vinculante y el ancho siempre será igual a la altura cada vez que se cambie la altura.

Si desea parámetro el ancho para ser la mitad de la altura, puede hacerlo agregando un convertidor a la extensión de marcado de enlace.Imaginemos ahora otro caso:

 <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 los principales directos, ya que este elemento contiene una propiedad que se llama Parent.Esto nos lleva a otro modo de fuente relativa, que es el Findancestor.

  1. Modo BuscarAncestro

En este caso, una propiedad de un elemento dado estará vinculado a uno de sus padres, de Corse.La principal diferencia con el caso anterior es el hecho de que depende de usted determinar el tipo de antepasado y el ancestro en la jerarquía para vincular la propiedad.Por cierto, trate de 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 de bloques de texto que están integrados dentro de una serie de fronteras y elementos de lienzo que representan a sus padres jerárquicos.El segundo bloqueo de texto mostrará el nombre del padre dado en el nivel de origen relativo.

Así que trate de cambiar AncestorLevel = 2 a AncestorLevel = 1 y vea qué sucede.Luego trate de cambiar el tipo de antepasado de Ancestortype = Border a Ancestortype = Canvas y vea lo que sucede.

El texto mostrado cambiará de acuerdo con el tipo y nivel del antepasado.Entonces, ¿qué pasa si el nivel del antepasado no es adecuado para el tipo de antepasado?Esta es una buena pregunta, sé que estás a punto de preguntarla.La respuesta es que no se lanzarán excepciones y no se mostrarán nada a nivel de bloqueo text.

  1. Padre con plantilla

Este modo permite atar una propiedad de control de control dada a una propiedad del control al que se aplica la placa de control.Para comprender bien el problema aquí es 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 la plantilla, que es una especie de mano corta de la primera, pero la plantilla se evalúa en el tiempo de compilación al contraste del paratelado que se evalúa justo después del primer tiempo de ejecución.Como puede comentar en la figura a continuación, el fondo y el contenido se aplican desde el botón hasta la plantilla de control.

En WPF RelativeSource la unión expone tres properties para configurar:

1.Modo: Esto es un enum que podría tener cuatro valores:

a.Datos anteriores(value=0): Asigna el valor anterior de la property al límite

b.Padre con plantilla (value=1): Esto se utiliza al definir el templates de cualquier control y desea unirse a un valor/propiedad del control.

Por ejemplo, definir ControlTemplate:

  <ControlTemplate>
        <CheckBox IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
 </ControlTemplate>

C.Ser(value=2): Cuando queremos enlazar desde un self o un property de sí mismo.

Por ejemplo: Enviar estado verificado de checkbox como CommandParameter mientras configura el Command en CheckBox

<CheckBox ...... CommandParameter="{Binding RelativeSource={RelativeSource Self},Path=IsChecked}" />

d.BuscarAncestro(value=3): Cuando quiero vincularme desde un padre controlen Visual Tree.

Por ejemplo: Enlazar un checkbox en records si un grid,si header checkbox está chequeado

<CheckBox IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid}}, Path=DataContext.IsHeaderChecked, Mode=TwoWay}" />

2.Tipo de antepasado: cuando el modo es FindAncestor luego define qué tipo de antepasado

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

3.Nivel de antepasado: cuando el modo es FindAncestor entonces, ¿qué nivel de antepasado (si hay dos padres del mismo tipo en visual tree)

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

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

Aquí hay un enlace de referencia..

No olvides TemplatedParent:

<Binding RelativeSource="{RelativeSource TemplatedParent}"/>

o

{Binding RelativeSource={RelativeSource TemplatedParent}}

Es digno de mención que para aquellos que se topan con este pensamiento de Silverlight:

Silverlight ofrece solo un subconjunto reducido de estos comandos

Creé una biblioteca para simplificar la sintaxis vinculante de WPF, incluida la facilitación del uso de RelativeSource.Aquí hay unos ejemplos.Antes:

{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}

A continuación se muestra un ejemplo de cómo se simplifica la vinculación de métodos.Antes:

// 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()}

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

Tenga en cuenta que en el ejemplo 'ANTES' que utilizo para el enlace de métodos, ese código ya estaba optimizado mediante el uso RelayCommand que verifiqué por última vez no es una parte nativa de WPF.Sin eso, el ejemplo 'ANTES' habría sido aún más largo.

Algunos fragmentos ú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é en gran medida esto de Enlace de fuente relativa en el código subyacente.

Además, la página de MSDN es bastante buena en cuanto a ejemplos: Clase de fuente relativa

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

No leí todas las respuestas, pero solo quiero agregar esta información en caso de que un botón se vincule con un comando de origen relativo.

Cuando utilizas 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 podrá 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 forma parte del árbol visual, RelativeSource nunca funcionará.

En este caso, es necesario probar una técnica diferente, de la que fue pionero Thomas Levesque.

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

En el improbable caso de que su blog no funcione, el Apéndice A contiene una copia reflejada de su articulo.

Por favor no comentes aquí, por favor. comenta directamente en la publicación de su blog.

Apéndice A:Espejo de la publicación del blog

La propiedad DataContext en WPF es extremadamente útil, porque todos los hijos del elemento donde la asigna la heredan automáticamente;por lo tanto, no es necesario volver a configurarlo en cada elemento que desee vincular.Sin embargo, en algunos casos no se puede acceder al DataContext:sucede con elementos que no forman parte del árbol visual o lógico.Puede ser muy difícil entonces vincular una propiedad a esos elementos...

Ilustremos con un ejemplo sencillo: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:

Error de sistema.Windows.datos:2:No se puede encontrar el FrameworkElement o el FrameworkContentElement que rigen para el elemento de destino.BindingExpression:Path=MostrarPrecio;Elemento de datos=nulo;el elemento de destino es 'DataGridTextColumn' (HashCode=32685253);la propiedad de destino es "Visibilidad" (escriba "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 del DataGrid.

Podemos intentar modificar el enlace para obtener el resultado deseado, por ejemplo configurando RelativeSource en el propio 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…

A estas alturas, parece que el único enfoque viable sería cambiar la visibilidad de las columnas en el código subyacente, algo que normalmente preferimos evitar cuando usamos el patrón MVVM… Pero no voy a rendirme tan pronto, al menos no. si bien hay otras opciones a considerar 😉

La solución a nuestro problema es 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 hacer que nuestro enlace funcione...

La idea es crear una clase (la llamé BindingProxy por razones que deberían resultar obvias muy pronto) que herede Freezable y declare 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 (fácilmente accesible con StaticResource) como fuente para el 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 tiene el prefijo "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.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top