119 votes

Accès au DataContext parent à partir du DataTemplate

J'ai un ListBox qui se lie à une collection enfant sur un ViewModel. Les éléments de la boîte de liste sont stylisés dans un modèle de données basé sur une propriété du ViewModel parent :

<Style x:Key="curveSpeedNonConstantParameterCell">
   <Style.Triggers>
      <DataTrigger Binding="{Binding Path=DataContext.CurveSpeedMustBeSpecified, 
          ElementName=someParentElementWithReferenceToRootDataContext}" 
          Value="True">
          <Setter Property="Control.Visibility" Value="Hidden"></Setter>
      </DataTrigger>
   </Style.Triggers>
</Style>

J'obtiens l'erreur de sortie suivante :

System.Windows.Data Error: 39 : BindingExpression path error: 
 'CurveSpeedMustBeSpecified' property not found on 
   'object' ''BindingListCollectionView' (HashCode=20467555)'. 
 BindingExpression:Path=DataContext.CurveSpeedMustBeSpecified; 
 DataItem='Grid' (Name='nonConstantCurveParametersGrid');
 target element is 'TextBox' (Name=''); 
 target property is 'NoTarget' (type 'Object')

Donc si je change l'expression de liaison en "Path=DataContext.CurrentItem.CurveSpeedMustBeSpecified" cela fonctionne, mais seulement si le contexte de données du contrôle utilisateur parent est un BindingListCollectionView . Ceci n'est pas acceptable car le reste du contrôle de l'utilisateur se lie aux propriétés de l'objet CurrentItem sur le BindingList automatiquement.

Comment puis-je spécifier l'expression de liaison à l'intérieur du style pour qu'elle fonctionne indépendamment du fait que le contexte de données parent soit une vue de collection ou un élément unique ?

167voto

Juve Points 2968

J'ai eu des problèmes avec la source relative dans Silverlight. Après avoir cherché et lu, je n'ai pas trouvé de solution appropriée sans utiliser une bibliothèque Binding supplémentaire. Mais voici une autre approche pour obtenir l'accès au DataContext parent en faisant directement référence à un élément dont vous connaissez le contexte des données. Il utilise Binding ElementName et fonctionne assez bien, tant que vous respectez votre propre nommage et que vous ne réutilisez pas beaucoup de templates / styles à travers les composants :

<ItemsControl x:Name="level1Lister" ItemsSource={Binding MyLevel1List}>
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <Button Content={Binding MyLevel2Property}
              Command={Binding ElementName=level1Lister,
                       Path=DataContext.MyLevel1Command}
              CommandParameter={Binding MyLevel2Property}>
      </Button>
    <DataTemplate>
  <ItemsControl.ItemTemplate>
</ItemsControl>

Cela fonctionne également si vous placez le bouton dans Style / Template :

<Border.Resources>
  <Style x:Key="buttonStyle" TargetType="Button">
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="Button">
          <Button Command={Binding ElementName=level1Lister,
                                   Path=DataContext.MyLevel1Command}
                  CommandParameter={Binding MyLevel2Property}>
               <ContentPresenter/>
          </Button>
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>
</Border.Resources>

<ItemsControl x:Name="level1Lister" ItemsSource={Binding MyLevel1List}>
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <Button Content="{Binding MyLevel2Property}" 
              Style="{StaticResource buttonStyle}"/>
    <DataTemplate>
  <ItemsControl.ItemTemplate>
</ItemsControl>

Au début, je pensais que le x:Names des éléments parents ne sont pas accessibles à partir d'un élément de modèle, mais comme je n'ai pas trouvé de meilleure solution, j'ai essayé, et cela fonctionne bien.

1 votes

J'ai exactement le même code dans mon projet mais il y a une fuite des ViewModels (le Finalizer n'est pas appelé, le Command binding semble retenir le DataContext). Pouvez-vous vérifier que ce problème existe également pour vous ?

0 votes

@Juve cela fonctionne, mais est-il possible de faire en sorte que cela se déclenche pour tous les itemscontrols qui mettent en œuvre le même modèle ? Le nom est unique, donc nous aurions besoin d'un modèle distinct pour chacun, à moins que je ne manque quelque chose.

1 votes

@Juve ne tenez pas compte de mon dernier commentaire, j'ai réussi à le faire fonctionner en utilisant relativesource avec findancestor et en recherchant par type d'ancêtre, (donc tout est identique sauf la recherche par nom). Dans mon cas, je répète l'utilisation d'ItemsControls, chacun implémentant un modèle, donc le mien ressemble à ceci : Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}, Path=DataContext.OpenDocumentBtnCommand}"

51voto

akjoshi Points 6711

Vous pouvez utiliser RelativeSource pour trouver l'élément parent, comme ceci -

Binding="{Binding Path=DataContext.CurveSpeedMustBeSpecified, 
RelativeSource={RelativeSource AncestorType={x:Type local:YourParentElementType}}}"

Voir cette question SO pour plus de détails sur RelativeSource .

11 votes

J'ai dû préciser Mode=FindAncestor pour que cela fonctionne, mais cela fonctionne et c'est bien mieux dans un scénario MVVM car cela évite de nommer les contrôles. Binding="{Binding Path=DataContext.CurveSpeedMustBeSpecified, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:YourParentElementType}}}"

1 votes

Fonctionne comme un charme <3 et je n'ai pas eu à spécifier le mode , .net 4.6.1

38voto

Mehrad Points 445

RelativeSource vs. Nom de l'élément

Ces deux approches permettent d'obtenir le même résultat,

RelativeSource

Binding="{Binding Path=DataContext.MyBindingProperty, 
          RelativeSource={RelativeSource AncestorType={x:Type Window}}}"

Cette méthode recherche un contrôle de type Window (dans cet exemple) dans l'arborescence visuelle et lorsqu'elle le trouve, vous pouvez accéder à sa propriété DataContext en utilisant le Path=DataContext.... . L'avantage de cette méthode est que vous n'avez pas besoin d'être lié à un nom et qu'elle est plutôt dynamique. Cependant, les modifications apportées à votre arbre visuel peuvent affecter cette méthode et éventuellement la casser.

Nom de l'élément

Binding="{Binding Path=DataContext.MyBindingProperty, ElementName=MyMainWindow}

Cette méthode fait référence à un solide statique Name Vous devez respecter votre convention d'appellation pour ne pas enfreindre cette méthode, bien entendu. L'approche est très simple et tout ce dont vous avez besoin, c'est de spécifier un nom de domaine. Name="..." pour votre fenêtre/UserControl.

Bien que les trois types ( RelativeSource, Source, ElementName ) sont capables de faire la même chose, mais selon l'article MSDN suivant, chacun d'eux doit être utilisé dans son propre domaine de spécialité.

Comment : Spécifier la source de liaison

Vous trouverez une brève description de chacun d'entre eux ainsi qu'un lien vers un document plus détaillé dans le tableau situé en bas de la page.

18voto

hmadrigal Points 515

J'ai cherché comment faire quelque chose de similaire dans WPF et j'ai trouvé cette solution :

<ItemsControl ItemsSource="{Binding MyItems,Mode=OneWay}">
<ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
        <StackPanel Orientation="Vertical" />
    </ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
    <DataTemplate>
        <RadioButton 
            Content="{Binding}" 
            Command="{Binding Path=DataContext.CustomCommand, 
                        RelativeSource={RelativeSource Mode=FindAncestor,      
                        AncestorType={x:Type ItemsControl}} }"
            CommandParameter="{Binding}" />
    </DataTemplate>
</ItemsControl.ItemTemplate>

J'espère que ça marchera pour quelqu'un d'autre. J'ai un contexte de données qui est automatiquement défini sur les ItemsControls, et ce contexte de données a deux propriétés : MyItems -qui est une collection, et une commande 'CustomCommand'. En raison de la ItemTemplate utilise un DataTemplate le DataContext des niveaux supérieurs n'est pas directement accessible. La solution de contournement pour obtenir le DC du parent est d'utiliser un chemin relatif et de filtrer par ItemsControl type.

0voto

MikeT Points 139

Le problème est qu'un DataTemplate ne fait pas partie d'un élément, il lui est appliqué.

cela signifie que si vous vous liez au modèle, vous vous liez à quelque chose qui n'a pas de contexte.

Cependant, si vous placez un élément à l'intérieur du modèle, lorsque cet élément est appliqué au parent, il obtient un contexte et la liaison fonctionne.

donc cela ne fonctionnera pas

<DataTemplate >
    <DataTemplate.Resources>
        <CollectionViewSource x:Key="projects" Source="{Binding Projects}" >

mais ceci fonctionne parfaitement

<DataTemplate >
    <GroupBox Header="Projects">
        <GroupBox.Resources>
            <CollectionViewSource x:Key="projects" Source="{Binding Projects}" >

car après l'application du modèle de données, la boîte de groupe est placée dans le parent et aura accès à son contexte.

Il suffit donc de supprimer le style du modèle et de le déplacer dans un élément du modèle.

note que le contexte pour un itemcontrol est l'item et non le contrôle par exemple, ComboBoxItem pour ComboBox et non pour le ComboBox lui-même, auquel cas vous devez utiliser ItemContainerStyle à la place du contrôle.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X