.net - ¿Cómo uso los enlaces WPF con RelativeSource?




xaml data-binding (10)

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


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


Algunos bits y piezas útiles:

Aquí está 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 parte copié esto de Binding Relative Source en el código Detrás .

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


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

El RelativeSource es una extensión de marcado que se usa en casos de vinculación 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 otra de sus padres relativos, al vincular 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 de fuente relativos. Voy a exponer todos esos casos uno por uno.

  1. Modo auto

Imagina este caso, un rectángulo que queremos que su altura sea siempre igual a su anchura, 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, es decir, el rectángulo. Podemos alcanzar el mismo propósito de manera diferente utilizando 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 enlace y el Ancho siempre será igual a la Altura siempre 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 vinculación. Imaginemos otro caso ahora:

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

El caso anterior se usa para vincular una propiedad dada de un elemento dado a una de sus propiedades primarias directas, ya que este elemento posee una propiedad que se llama Padre. Esto nos lleva a otro modo de fuente relativa que es el FindAncestor.

  1. Modo FindAncestor

En este caso, una propiedad de un elemento dado estará vinculada 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 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. A continuación, intente cambiar el tipo de antepasado de AncestorType = Border to AncestorType = Canvas y vea qué sucede.

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

  1. TemplatedParent

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

<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 el TemplateBinding que es una especie de mano corta de la primera, pero el TemplateBinding se evalúa en el momento de la compilación en contraste con el TemplatedParent que se evalúa justo después del primer tiempo de ejecución. Como puede observar en la figura de abajo, el fondo y el contenido se aplican desde dentro del botón a la plantilla de control.


Creé una biblioteca para simplificar la sintaxis de enlace de WPF, incluso para facilitar 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 del método. 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 que en el ejemplo "ANTES" que uso para el enlace de métodos, el código que ya estaba optimizado mediante RelayCommand que fue el último que verifiqué, no es una parte nativa de WPF. Sin eso, el ejemplo 'ANTES' hubiera sido aún más largo.


Este es un ejemplo del uso de este patrón que me funcionó en áreas 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>

Imagina este caso, un rectángulo que queremos que su altura sea siempre igual a su anchura, 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, es decir, el rectángulo. Podemos alcanzar el mismo propósito de manera diferente utilizando 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 enlace y el Ancho siempre será igual a la Altura siempre 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 vinculación. Imaginemos otro caso ahora:

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

El caso anterior se usa para vincular una propiedad dada de un elemento dado a una de sus propiedades primarias directas, ya que este elemento posee una propiedad que se llama Padre. Esto nos lleva a otro modo de fuente relativa que es el FindAncestor.


No te olvides de TemplatedParent:

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

o

{Binding RelativeSource={RelativeSource TemplatedParent}}

Si desea enlazar a otra propiedad en el objeto:

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

Si desea obtener una propiedad en un antepasado:

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

Si desea obtener una propiedad en el padre con plantilla (por lo que puede hacer enlaces de dos vías en una plantilla ControlTemplate)

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

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

{TemplateBinding Path=PathToProperty}

En el enlace WPF RelativeSource exponen 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 a la unida

segundo. TemplatedParent ( value=1 ): se usa cuando se definen las templates de cualquier control y se desean vincular a un valor / propiedad del control .

Por ejemplo, defina ControlTemplate :

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

do. Self ( value=2 ): cuando queremos vincularnos desde un self o una property de self.

Por ejemplo: envíe el estado marcado de la checkbox de checkbox como CommandParameter mientras configura el Command en CheckBox

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

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

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

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

2. AncestorType: cuando el modo es FindAncestor , defina qué tipo de antepasado

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

3. AncestorLevel: cuando el modo es FindAncestor entonces qué nivel de antepasado (si hay dos tipos iguales de padre en visual tree )

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

Más arriba están todos los casos de uso para el RelativeSource binding .

Aquí hay un enlace de referencia .


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 ):

  • Datos anteriores 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 Se refiere al elemento al que se aplica la plantilla (en la que existe el elemento enlazado a datos). Esto es similar a establecer una TemplateBindingExtension y solo es aplicable si el enlace está dentro de una plantilla.

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

  • FindAncestor Se refiere al antepasado en la cadena principal 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.







relativesource