Comment utiliser les liaisons WPF avec RelativeSource?

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

527
demandé sur shekhar 2008-09-17 19:10:53

14 réponses

Si vous souhaitez lier à une autre propriété sur l'objet:

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

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

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

Si vous voulez obtenir une propriété sur le parent modèle (vous pouvez donc faire des liaisons à 2 voies dans un ControlTemplate)

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

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

{TemplateBinding Path=PathToProperty}
704
répondu Abe Heidebrecht 2017-01-31 09:26:46
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 des TemplateBindingExtension et n'est applicable que si la liaison se trouve dans un modèle.

  • Auto - fait référence à l'élément sur lequel vous définissez la liaison et vous permet de lier une propriété de l'élément à l'autre de la propriété sur le même élément.

  • FindAncestor fait référence à l'ancêtre de la chaîne parente de l'élément lié aux données. Vous pouvez l'utiliser pour 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.

123
répondu Drew Noakes 2009-03-03 09:24:23

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

entrez la description de l'image ici

114
répondu Jeff K. 2012-02-05 21:43:26

Imaginez ce cas, un rectangle que l'on veut 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 ci-dessus, 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}"/>

Pour 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 modification de la hauteur.

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

 <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 contient une propriété appelée Parent. Cela nous conduit à un autre mode Source relatif qui est le FindAncestor.

40
répondu lasitha edirisooriya 2012-11-09 05:47:07

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

RelativeSource est une extension de balisage utilisée en particulier 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 relatifs, lors de la liaison valeur de la propriété de dépendance à un morceau de XAML en cas de contrôle personnalisé le développement et la enfin en cas d'utilisation d'un différentiel d'une série de une limite de données. Toutes ces situations sont exprimées comme source relative mode. Je vais exposer tous ces cas un par un.

  1. Mode Auto:

Imaginez ce cas, un rectangle que nous voulons que sa hauteur est 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 ci-dessus, nous sommes obligés d'indiquer le nom du objet de liaison, à savoir le rectangle. Nous pouvons atteindre le même objectif différemment en utilisant RelativeSource

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

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

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

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

Ce qui précède cas est utilisé pour attacher une propriété donnée d'un élément donné à l'un de ses parents directs car cet élément détient une propriété qui est appelé Parent. Cela nous conduit à un autre mode Source relatif qui est le FindAncestor un.

  1. Mode FindAncestor

Dans ce cas, une propriété d'un élément donné ne sera liée à l'un de ses les parents, De la 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 le ancêtre rang dans la hiérarchie pour lier la propriété. Par ailleurs 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 de deux éléments TextBlock qui sont intégrés dans une série de bordures et d'éléments de toile ceux ci représentent leur parents hiérarchiques. Le second TextBlock affichera le nom de le parent donné au niveau de la source relative.

Essayez donc de changer AncestorLevel=2 en AncestorLevel=1 et voyez quoi arriver. Ensuite, essayez de changer le type de l'ancêtre de AncestorType = Border à AncestorType = Canvas et voir ce qui se passe.

Le texte affiché change en fonction du type niveau. Alors que se passe t il si le niveau ancêtre ne convient pas au type d'ancêtre? C'est une bonne question, je sais que vous êtes sur le point de lui demander. La réponse est aucune exception sera levée et rien ne va être affiché au niveau TextBlock.

  1. TemplatedParent

Ce mode permet de lier une propriété ControlTemplate donnée à une propriété du contrôle auquel le ControlTemplate est appliqué. Bien comprendre le problème Voici 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 un similaire à cette extension de balisage qui est le TemplateBinding ce qui est une sorte de main courte de la première, mais le TemplateBinding est évalué à temps de compilation au contraste de l' 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 au modèle de contrôle.

34
répondu Cornel Marian 2013-11-02 16:51:29

N'oubliez pas TemplatedParent:

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

Ou

{Binding RelativeSource={RelativeSource TemplatedParent}}
18
répondu Bob King 2008-09-17 15:14:34

Dans WPF RelativeSource liaison expose trois properties pour définir:

1. Mode: c'est un {[7] } qui peut avoir quatre valeurs:

un. PreviousData(value=0): Elle attribue de la valeur précédente de la property pour Le lié

B. TemplatedParent(value=1): Ceci est utilisé pour définir le templates de tout contrôle et que vous voulez lier à une valeur / propriété de la control.

Pour exemple, définir ControlTemplate:

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

c. Auto(value=2): Lorsque l'on veut se lier à partir d'un self ou property de soi.

Par exemple: Envoyer vérifié l'état de checkbox, comme CommandParameter lors du réglage de la Command sur CheckBox

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

d. FindAncestor(value=3): Quand voulez lier à partir d'un parent control dans Visual Tree.

Par exemple: Lier un checkbox dans records si grid,si header checkbox 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 {[29] } alors définissez quel type d'ancêtre

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

3. AncestorLevel: quand le mode est FindAncestor alors quel niveau d'ancêtre (s'il y a deux mêmes types de parent dans visual tree)

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

Ci-Dessus sont tous des cas d'utilisation pour RelativeSource binding.

Voici un lien de référence.

16
répondu Kylo Ren 2018-05-19 09:24:28

Il est intéressant de noter que pour ceux qui trébuchent sur cette pensée de Silverlight:

Silverlight offre un sous-ensemble réduit seulement, de ces commandes

13
répondu Matthew Black 2010-04-24 16:02:46

J'ai créé une bibliothèque pour simplifier la syntaxe de liaison de WPF, y compris pour faciliter 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 la façon dont la liaison de méthode est simplifiée. 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 était déjà optimisé en utilisant RelayCommand dont la dernière vérification n'est pas une partie native de WPF. Sans cela, l'exemple "avant" aurait été encore plus long.

13
répondu Luis Perez 2012-08-06 18:12:51

Quelques morceaux 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);

J'ai largement copié cela de lier la Source Relative dans le code derrière.

En outre, la page MSDN est assez bonne dans la mesure où les exemples vont: Classe RelativeSource

11
répondu Nathan Cooper 2018-05-19 09:23:32

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

10
répondu Juve 2017-05-23 12:26:01

Ceci est un exemple de l'utilisation de ce modèle qui a fonctionné pour moi sur vide datagrid.

<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>
8
répondu Edd 2015-04-29 13:59:41

Je n'ai pas lu toutes les réponses, mais je veux juste ajouter cette information en cas de liaison de commande source relative d'un bouton.

Lorsque vous utilisez une source par rapport à Mode=FindAncestor, la liaison doit être comme:

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

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

8
répondu Kevin VDF 2018-05-18 10:46:55

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, lancée par Thomas Levesque.

Il a la solution sur son blog sous [WPF] comment se lier aux données lorsque le DataContext n'est pas hérité. Et cela fonctionne absolument brillamment!

Dans le cas peu probable où son blog est en panne, L'Annexe A contient une copie miroir de son article .

s'il vous Plaît ne pas commentez ici, merci Commentez directement sur son blog .

Annexe A: miroir du billet 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 où vous l'affectez; vous n'avez donc pas besoin de la Réinitialiser sur chaque élément que vous souhaitez lier. Cependant, dans certains cas, le DataContext n'est pas accessible: cela arrive pour les éléments qui ne font pas partie de l'arborescence visuelle ou logique. Il peut être très difficile alors 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 de prix, en fonction de la valeur d'une propriété showprice exposée par le 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, changer 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:

Système.Windows.Erreur de données: 2: Impossible de trouver FrameworkElement ou FrameworkContentElement 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 fait assez simple: WPF ne sait pas lequel FrameworkElement à utiliser pour obtenir le DataContext, car la colonne n'appartient pas à l'arborescence visuelle ou logique du DataGrid.

Nous pouvons essayer de modifier la liaison pour obtenir le résultat souhaité, par exemple en définissant 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 une case à cocher liée à 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 de contournement ne semble fonctionner, nous toujours obtenir le même résultat...

À ce stade, il semble que la seule approche viable serait de changer la visibilité de la colonne dans code-behind, ce que nous préférons généralement éviter lors de l'utilisation du 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 profite de la classe Freezable. Le but principal de cette classe est de définir des objets qui ont un modifiable et un état en lecture seule, mais la caractéristique 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 profiter pour faire fonctionner notre liaison ...

L'idée est de créer une classe (je l'ai appelée BindingProxy pour des raisons qui devraient devenir évidentes très bientôt) qui hérite Freezable et déclare une propriété de dépendance de 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));
}

On peut déclarez ensuite une instance de cette classe dans les ressources du DataGrid, et liez la propriété Data au DataContext courant:

<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) comme Source pour 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 "Data", puisque le chemin est maintenant relatif à l'objet BindingProxy.

La liaison fonctionne maintenant correctement, et la colonne est correctement affichée ou masquée sur la propriété ShowPrice.

3
répondu Contango 2017-09-05 10:46:12