Question

Comment utiliser RelativeSource avec les liaisons WPF et quels sont les différents cas d'utilisation?

Était-ce utile?

La solution

Si vous souhaitez créer un lien vers une autre propriété de l'objet:

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

Si vous souhaitez obtenir une propriété sur un ancêtre:

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

Si vous souhaitez obtenir une propriété sur le parent basé sur un modèle (vous pouvez donc effectuer des liaisons à double sens dans un ControlTemplate)

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

ou, plus court (cela ne fonctionne que pour les liaisons OneWay):

{TemplateBinding Path=PathToProperty}

Autres conseils

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

L'attribut par défaut de RelativeSource est la propriété Mode . Un ensemble complet de valeurs valides est donné ici ( à partir de MSDN ):

  • PreviousData Vous permet de lier l'élément de données précédent (pas le contrôle qui contient l'élément de données) dans la liste des éléments de données affichés.

  • TemplatedParent Fait référence à l'élément auquel le modèle (dans lequel l'élément lié aux données existe) est appliqué. Ceci est similaire à la définition d'une TemplateBindingExtension et n'est applicable que si la liaison est dans un modèle.

  • Self Fait référence à l'élément sur lequel vous définissez la liaison et vous permet de lier une propriété de cet élément à une autre propriété du même élément.

  • FindAncestor Fait référence à l'ancêtre dans la chaîne parente de l'élément lié aux données. Vous pouvez l'utiliser pour vous lier à un ancêtre d'un type spécifique ou à ses sous-classes. C'est le mode que vous utilisez si vous souhaitez spécifier AncestorType et / ou AncestorLevel.

Voici une explication plus visuelle dans le contexte d’une architecture MVVM:

entrer la description de l'image ici

Imaginons ce cas, un rectangle dans lequel nous voulons que sa hauteur soit toujours égale à sa largeur, un carré, disons. Nous pouvons le faire en utilisant le nom de l'élément

<Rectangle Fill="Red" Name="rectangle" 
                    Height="100" Stroke="Black" 
                    Canvas.Top="100" Canvas.Left="100"
                    Width="{Binding ElementName=rectangle,
                    Path=Height}"/>

Mais dans ce cas, nous sommes obligés d'indiquer le nom de l'objet de liaison, à savoir le rectangle. Nous pouvons atteindre le même but différemment en utilisant RelativeSource

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

Dans ce cas, nous ne sommes pas obligés de mentionner le nom de l'objet de liaison et la largeur sera toujours égale à la hauteur chaque fois que la hauteur est modifiée.

Si vous souhaitez paramétrer la largeur comme étant la moitié de la hauteur, vous pouvez le faire en ajoutant un convertisseur à l'extension de balisage Binding. Imaginons maintenant un autre cas:

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

Le cas ci-dessus est utilisé pour lier une propriété donnée d'un élément donné à l'une de ses propriétés parent directes, car cet élément contient une propriété appelée Parent. Cela nous conduit à un autre mode source relatif, celui de FindAncestor.

Bechir Bejaoui expose les cas d'utilisation de RelativeSources dans WPF dans son article ici :

  

RelativeSource est une extension de balisage utilisée notamment   cas de liaison lorsque nous essayons de lier une propriété d'un objet donné à   une autre propriété de l'objet lui-même, lorsque nous essayons de lier une propriété   d'un objet à un autre de ses parents par rapport, lors de la liaison d'un   Propriété de dépendance valeur à un morceau de XAML en cas de contrôle personnalisé   développement et enfin en cas d'utilisation d'un différentiel d'une série de   une donnée liée. Toutes ces situations sont exprimées en tant que source relative   modes. Je vais exposer tous ces cas un par un.

     
      
  1. Mode auto:
  2.   
     

Imaginez ce cas, un rectangle que nous voulons que sa hauteur soit toujours   égal à sa largeur, un carré, disons. Nous pouvons le faire en utilisant le   nom d'élément

<Rectangle Fill="Red" Name="rectangle" 
                Height="100" Stroke="Black" 
                Canvas.Top="100" Canvas.Left="100"
                Width="{Binding ElementName=rectangle,
                Path=Height}"/>
     

Mais dans ce cas ci-dessus, nous sommes obligés d'indiquer le nom du   objet de liaison, à savoir le rectangle. Nous pouvons atteindre le même but   différemment en utilisant RelativeSource

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

Dans ce cas, nous ne sommes pas obligés de mentionner le nom de la liaison.   objet et la largeur sera toujours égale à la hauteur chaque fois que le   la hauteur est changée.

     

Si vous voulez paramétrer la largeur comme étant la moitié de la hauteur, alors   vous pouvez le faire en ajoutant un convertisseur à l'extension de balisage de liaison.   Imaginons maintenant un autre cas:

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

Le cas ci-dessus est utilisé pour lier une propriété donnée d'un élément donné à   l'un de ses parents directs car cet élément est titulaire d'une propriété qui est   appelé Parent. Cela nous conduit à un autre mode source relatif qui est   celui de FindAncestor.

     
      
  1. Mode FindAncestor
  2.   
     

Dans ce cas, une propriété d'un élément donné sera liée à l'un de ses   parents, de Corse. La principale différence avec le cas ci-dessus est le fait   cela, c'est à vous de déterminer le type d'ancêtre et l'ancêtre   rang dans la hiérarchie pour lier la propriété. Au fait, essayez de jouer avec   ce morceau 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 situation ci-dessus est constituée de deux éléments TextBlock incorporés.   au sein d’une série de bordures et d’éléments de la toile représentant ceux   parents hiérarchiques. Le second TextBlock affichera le nom de   le parent donné au niveau source relatif.

     

Essayez donc de changer AncestorLevel = 2 en AncestorLevel = 1 et voyez quoi   arrive. Puis essayez de changer le type de l'ancêtre de   AncestorType = Border to AncestorType = Canvas et voyez ce qui se passe.

     

Le texte affiché changera en fonction du type d'ancêtre et de   niveau. Alors que se passe-t-il si le niveau d'ancêtre n'est pas adapté à la   type d'ancêtre? C'est une bonne question, je sais que vous êtes sur le point de   demande-le. La réponse est qu'aucune exception ne sera jetée et rien ne sera   être affiché au niveau TextBlock.

     
      
  1. TemplatedParent
  2.   
     

Ce mode permet de lier une propriété ControlTemplate donnée à une propriété.   du contrôle auquel le ControlTemplate est appliqué. À bien   comprendre la question ici est un exemple ci-dessous

<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 je veux appliquer les propriétés d'un contrôle donné à son contrôle   modèle alors je peux utiliser le mode TemplatedParent. Il y a aussi   semblable à cette extension de balisage qui est le TemplateBinding   qui est une sorte de main courte du premier, mais le   TemplateBinding est évalué au moment de la compilation, au contraire de la   TemplatedParent qui est évalué juste après la première exécution. Comme   vous pouvez remarquer dans la figure ci-dessous, l'arrière-plan et le contenu   sont appliqués depuis le bouton vers le modèle de contrôle.

Dans WPF RelativeSource , la liaison expose trois propriétés à définir:

1. Mode: il s'agit d'un enum pouvant avoir quatre valeurs:

  

a. PreviousData ( value = 0 ): Il attribue la valeur précédente de la propriété à   le lié

     

b. TemplatedParent ( value = 1 ): Ceci est utilisé lors de la définition des modèles de   n’importe quel contrôle et que vous souhaitez vous lier à une valeur / propriété du contrôle .

     

Par exemple, définissez ControlTemplate :

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

c. Self ( value = 2 ): Lorsque nous voulons créer une liaison à partir d'un self ou d'une propriété de self.

     

Par exemple: envoyez l'état vérifié de la case à cocher en tant que CommandParameter lors de la définition de Command sur CheckBox

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

d. FindAncestor ( value = 3 ): Lorsque vous souhaitez créer une liaison à partir d'un contrôle parent   dans Visual Tree .

     

Par exemple: lier une case à cocher dans enregistrements si une grille , si en-tête la case à cocher est cochée

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

2. AncestorType: lorsque le mode est FindAncestor , définissez le type d'ancêtre

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

3. AncestorLevel: lorsque le mode est FindAncestor , quel niveau d'ancêtre (s'il existe deux mêmes types de parents dans arbre visuel )

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

Ci-dessus figurent tous les cas d'utilisation de Liaison RelativeSource .

.

Voici un lien de référence .

N'oubliez pas TemplatedParent:

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

ou

{Binding RelativeSource={RelativeSource TemplatedParent}}

Il convient de noter que pour ceux qui tombent sur cette réflexion sur Silverlight:

Silverlight propose uniquement un sous-ensemble réduit de ces commandes

J'ai créé une bibliothèque pour simplifier la syntaxe de liaison de WPF, notamment en facilitant l'utilisation de RelativeSource. Voici quelques exemples. Avant:

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

Après:

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

Voici un exemple de simplification de la liaison de méthode. Avant:

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

Après:

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

// XAML
{BindTo SaveObject()}

Vous pouvez trouver la bibliothèque ici: http://www.simplygoodcode.com/2012/ 08 / simpler-wpf-binding.html

Notez dans l'exemple "BEFORE" que j'utilise pour la liaison de méthode que le code a déjà été optimisé à l'aide de RelayCommand que j'ai vérifié en dernier lieu ne faisant pas partie de WPF. Sans cela, l'exemple 'BEFORE' aurait été encore plus long.

Quelques éléments utiles:

Voici comment le faire principalement dans le code:

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

Je l'ai en grande partie copié depuis Relier la source relative dans le code derrière .

De plus, la page MSDN est plutôt bonne en ce qui concerne les exemples: Classe RelativeSource

Je viens de publier une autre solution pour accéder au DataContext d'un élément parent dans Silverlight qui fonctionne pour moi. Il utilise Binding ElementName .

Je n'ai pas lu toutes les réponses, mais je veux simplement ajouter ces informations dans le cas d'une liaison de commande de source relative à un bouton.

Lorsque vous utilisez une source relative avec Mode = FindAncestor , la liaison doit être semblable à:

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

Si vous n'ajoutez pas DataContext dans votre chemin, il ne peut pas récupérer la propriété au moment de l'exécution.

Ceci est un exemple d'utilisation de ce modèle qui a fonctionné pour moi sur des datagrids vides.

<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 élément ne fait pas partie de l'arborescence visuelle, RelativeSource ne fonctionnera jamais.

Dans ce cas, vous devez essayer une technique différente, mise au point par Thomas Levesque.

Il a la solution sur son blog sous [WPF] Comment lier des données lorsque le DataContext n'est pas hérité . Et ça marche absolument génial!

Dans le cas improbable où son blog serait en panne, l’annexe A contient une copie miroir de son article .

Merci de ne pas commenter ici, s'il vous plaît commentez directement sur son blog .

Annexe A: mise en miroir de la publication de blog

La propriété DataContext dans WPF est extrêmement pratique car elle est automatiquement héritée par tous les enfants de l'élément auquel vous l'attribuez. Par conséquent, vous n'avez pas besoin de le redéfinir sur chaque élément que vous souhaitez lier. Cependant, dans certains cas, le DataContext n'est pas accessible: cela se produit pour des éléments qui ne font pas partie de l'arborescence visuelle ou logique. Il peut alors être très difficile de lier une propriété sur ces éléments…

Illustrons avec un exemple simple: nous voulons afficher une liste de produits dans un DataGrid. Dans la grille, nous voulons pouvoir afficher ou masquer la colonne Price, en fonction de la valeur d'une propriété ShowPrice exposée par ViewModel. L’approche évidente consiste à lier la visibilité de la colonne à la propriété ShowPrice:

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                Visibility="{Binding ShowPrice,
                Converter={StaticResource visibilityConverter}}"/>

Malheureusement, la modification de la valeur de ShowPrice n’a aucun effet et la colonne est toujours visible… pourquoi? Si nous regardons la fenêtre de sortie dans Visual Studio, nous remarquons la ligne suivante:

  

System.Windows.Data Erreur: 2: impossible de trouver le FrameworkElement ou le FrameworkContentElement en vigueur pour l'élément cible. BindingExpression: Path = ShowPrice; DataItem = null; l’élément cible est ‘DataGridTextColumn’ (HashCode = 32685253); La propriété cible est "Visibility" (type "Visibility")

     

Le message est plutôt cryptique, mais la signification est en réalité très simple: WPF ne sait pas quel FrameworkElement utiliser pour obtenir le DataContext, car la colonne n'appartient pas à l'arborescence visuelle ou logique du DataGrid.

Nous pouvons essayer d'ajuster la liaison pour obtenir le résultat souhaité, en définissant par exemple RelativeSource sur le DataGrid lui-même:

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                Visibility="{Binding DataContext.ShowPrice,
                Converter={StaticResource visibilityConverter},
                RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}}"/>

Ou nous pouvons ajouter un CheckBox lié à ShowPrice et essayer de lier la visibilité de la colonne à la propriété IsChecked en spécifiant le nom de l'élément:

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                Visibility="{Binding IsChecked,
                Converter={StaticResource visibilityConverter},
                ElementName=chkShowPrice}"/>

Mais aucune de ces solutions ne semble fonctionner, nous obtenons toujours le même résultat…

À ce stade, il semble que la seule approche viable consiste à modifier la visibilité de la colonne dans code-behind, ce que nous préférons généralement éviter en utilisant le modèle MVVM… Mais je ne vais pas abandonner si tôt, du moins pas tant qu'il y a d'autres options à considérer

La solution à notre problème est en fait assez simple et tire parti de la classe Freezable. Le but principal de cette classe est de définir des objets modifiables et en lecture seule, mais la fonctionnalité intéressante dans notre cas est que les objets Freezable peuvent hériter du DataContext même s’ils ne sont pas dans l’arborescence visuelle ou logique. Je ne connais pas le mécanisme exact qui permet ce comportement, mais nous allons en tirer parti pour que notre liaison fonctionne.

L’idée est de créer une classe (je l’ai appelée BindingProxy pour des raisons qui devraient bientôt devenir évidentes) qui hérite de Freezable et déclare une propriété de dépendance des données:

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));
}

Nous pouvons ensuite déclarer une instance de cette classe dans les ressources du DataGrid et lier la propriété Data au DataContext en cours:

<DataGrid.Resources>
    <local:BindingProxy x:Key="proxy" Data="{Binding}" />
</DataGrid.Resources>

La dernière étape consiste à spécifier cet objet BindingProxy (facilement accessible avec StaticResource) en tant que source de la liaison:

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                Visibility="{Binding Data.ShowPrice,
                Converter={StaticResource visibilityConverter},
                Source={StaticResource proxy}}"/>

Notez que le chemin de liaison a été préfixé par "Données", car il est maintenant relatif à l'objet BindingProxy.

La liaison fonctionne désormais correctement et la colonne est correctement affichée ou masquée en fonction de la propriété ShowPrice.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top