.net - wpf binding relativesource




Comment utiliser les liaisons WPF avec RelativeSource? (10)

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

RelativeSource est une extension de balisage qui est utilisée dans des cas de liaison particuliers 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 parent lors de la liaison d'une valeur de propriété de dépendance à une partie de XAML dans le cas d'un développement de contrôle personnalisé et, finalement, dans le cas d'utilisation d'un différentiel d'une série de données liées. Toutes ces situations sont exprimées en tant que modes de source relatifs. Je vais exposer tous ces cas un par un.

  1. Mode Auto:

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

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 voulez paramétrer la largeur à la moitié de la hauteur, vous pouvez le faire en ajoutant un convertisseur à l'extension Binding Binding. 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 éléments parents directs car cet élément contient une propriété appelée Parent. Cela nous amène à un autre mode source relatif qui est le FindAncestor.

  1. Mode FindAncestor

Dans ce cas, une propriété d'un élément donné sera liée à l'un de ses parents, Of Corse. La principale différence avec le cas ci-dessus est le fait que c'est à vous de déterminer le type d'ancêtre et le rang d'ancêtre dans la hiérarchie pour lier la propriété. En passant, 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 des éléments de canevas qui représentent leurs parents hiérarchiques. Le second TextBlock affichera le nom du parent donné au niveau de la source relative.

Donc, essayez de changer AncestorLevel = 2 à AncestorLevel = 1 et voir ce qui se passe. Ensuite, essayez de changer le type de l'ancêtre de AncestorType = Border à AncestorType = Canvas et voir ce qui se passe.

Le texte affiché changera en fonction du type et du niveau de l'Ancêtre. Alors que se passe-t-il si le niveau d'ancêtre n'est pas adapté au type d'ancêtre? C'est une bonne question, je sais que vous êtes sur le point de le demander. La réponse est aucune exception sera levée et rien ne sera 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é. Pour 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 modèle de contrôle, je peux utiliser le mode TemplatedParent. Il existe également un similaire à cette extension de balisage qui est le TemplateBinding qui est une sorte de raccourci du premier, mais TemplateBinding est évalué au moment de la compilation au contraste du TemplatedParent qui est évalué juste après la première exécution. Comme vous pouvez le remarquer dans la figure ci-dessous, l'arrière-plan et le contenu sont appliqués depuis le bouton jusqu'au modèle de contrôle.

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


Ceci est un exemple de l'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>

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

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 voulez paramétrer la largeur à la moitié de la hauteur, vous pouvez le faire en ajoutant un convertisseur à l'extension Binding Binding. 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 éléments parents directs car cet élément contient une propriété appelée Parent. Cela nous amène à un autre mode source relatif qui est le FindAncestor.


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é en utilisant RelayCommand dont la dernière vérification n'est pas une partie native de WPF. Sans cela, l'exemple 'BEFORE' aurait été encore plus long.


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 .


Ne pas oublier TemplatedParent:

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

ou

{Binding RelativeSource={RelativeSource TemplatedParent}}

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 aux données lorsque le DataContext n'est pas hérité . Et cela fonctionne absolument avec brio!

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

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

Annexe A: Miroir de l'article 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'avez assignée; par conséquent, vous n'avez pas besoin de le redéfinir sur chaque élément que vous voulez lier. Cependant, dans certains cas, le DataContext n'est pas accessible: cela arrive pour les éléments qui ne font pas partie de l'arbre visuel ou logique. Il peut ê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 être en mesure d'afficher ou de masquer la colonne Price, basée sur 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, changer la valeur de ShowPrice n'a aucun effet, et la colonne est toujours visible ... pourquoi? Si nous regardons la fenêtre Sortie dans Visual Studio, nous remarquons la ligne suivante:

Erreur System.Windows.Data: 2: Impossible de trouver la règle FrameworkElement ou FrameworkContentElement pour l'élément cible. BindingExpression: Path = ShowPrice; DataItem = null; l'élément cible est 'DataGridTextColumn' (HashCode = 32685253); La propriété target est 'Visibility' (type 'Visibility')

Le message est plutôt cryptique, mais la signification est en fait assez simple: WPF ne sait pas quel FrameworkElement utiliser pour obtenir le DataContext, parce que la colonne n'appartient pas à l'arbre visuel 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 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 de contournement ne semble fonctionner, nous obtenons toujours le même résultat ...

À ce stade, il semble que la seule approche viable serait de changer la visibilité de la colonne en code-behind, ce que nous préférons généralement éviter en utilisant le pattern MVVM ... Mais je ne vais pas abandonner si tôt, du moins pas alors 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. L'objectif principal de cette classe est de définir des objets qui ont un état 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 se trouvent pas dans l'arborescence logique ou visuelle. Je ne connais pas le mécanisme exact qui permet ce comportement, mais nous allons en profiter pour faire fonctionner notre reliure ...

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 de Freezable et déclare une propriété de dépendance aux 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 actuel:

<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é avec "Data", puisque le chemin 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.


Si vous voulez 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 basé sur un modèle (vous pouvez donc faire des liaisons bidirectionnelles dans un ControlTemplate)

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

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

{TemplateBinding Path=PathToProperty}

Dans la liaison RelativeSource WPF expose 3 properties à définir:

1. Mode: Ceci est une enum qui pourrait avoir quatre valeurs:

une. PreviousData ( value=0 ): Assigne la valeur précédente de la property à la property liée

b. TemplatedParent ( value=1 ): Ceci est utilisé lors de la définition des templates de tout contrôle et veut se lier à une valeur / propriété du control .

Ex. define ControlTemplate

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

c. Soi ( value=2 ): Quand vouloir se lier d'un self ou d'une property de soi.

Ex. Envoyer l'état vérifié de checkbox tant que CommandParameter lors de la définition de la Command sur CheckBox

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

ré. FindAncestor ( value=3 ): lorsque vous souhaitez lier à partir d'un control parent dans Visual Tree .

Ex. lier une checkbox à checkbox dans les records si une checkbox à checkbox , si l'en- header est cochée, 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: quand le mode est FindAncestor alors quel niveau d'ansector (s'il y a deux types de parents dans visual tree )

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

Ci-dessus sont tous les cas d'utilisation de la RelativeSource binding .

Voici un lien ref


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 celui 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 se trouve l'élément lié aux données) est appliqué. Cela est similaire à la définition d'un 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é sur le 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 lier un ancêtre d'un type spécifique ou ses sous-classes. C'est le mode que vous utilisez si vous voulez spécifier AncestorType et / ou AncestorLevel.





relativesource