web-dev-qa-db-fra.com

Accéder au parent DataContext à partir de DataTemplate

J'ai un ListBox qui se lie à une collection enfant sur un ViewModel. Les éléments de la zone de liste sont stylé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>

Je reçois 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 tant que le contexte de données du contrôle utilisateur parent est un BindingListCollectionView. Ce n'est pas acceptable car le reste du contrôle utilisateur est lié aux propriétés du CurrentItem sur le BindingList automatiquement.

Comment puis-je spécifier l'expression de liaison dans le style afin qu'il fonctionne indépendamment du contexte de données parent qui est une vue de collection ou un seul élément?

97
Marius

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 de liaison supplémentaire. Mais, voici ne autre approche pour accéder au DataContext parent en référençant directement un élément dont vous connaissez le contexte de données. Il utilise Binding ElementName et fonctionne assez bien, tant que vous respectez votre propre nom et que vous n'avez pas à réutiliser lourdement templates/styles entre 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 Les éléments parents ne sont pas accessibles depuis un élément basé sur un modèle, mais comme je n’ai trouvé aucune meilleure solution, j’ai juste essayé et cela fonctionne bien.

142
Juve

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 this SO question pour plus de détails sur RelativeSource.

43
akjoshi

RelativeSource et ElementName

Ces deux approches peuvent aboutir au même résultat,

RelativeSrouce

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'il le trouve, vous pouvez accéder à son nom DataContext à l'aide de la commande Path=DataContext..... Les avantages de cette méthode sont qu'il n'est pas nécessaire d'être lié à un nom et que c'est un peu dynamique. Cependant, les modifications apportées à votre arbre visuel peuvent affecter cette méthode et éventuellement la rompre.

NomElément

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

Cette méthode fait référence à un solide Name statique, aussi longtemps que votre portée peut le voir, tout va bien. Vous devriez vous en tenir à votre convention de dénomination pour ne pas casser cette méthode bien sûr.L'approche est simple et tout ce dont vous avez besoin est de spécifier un 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 suivant de MSDN, chacun devrait être mieux utilisé dans son propre domaine de spécialité.

Comment: spécifier la source de liaison

Trouvez la brève description de chacun et un lien vers un autre plus détaillé dans le tableau au bas de la page.

27
Mehrad

Je cherchais comment faire quelque chose de similaire dans WPF et j'ai eu 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 cela fonctionne pour quelqu'un d'autre. J'ai un contexte de données qui est défini automatiquement sur ItemsControls, et ce contexte de données a deux propriétés: MyItems -qui est une collection- et une commande 'CustomCommand'. En raison du fait que ItemTemplate utilise un DataTemplate, le DataContext des niveaux supérieurs n'est pas directement accessible. Ensuite, la solution de contournement pour obtenir le DC du parent) consiste à utiliser un chemin relatif et à filtrer par ItemsControl type.

17
hmadrigal

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 liez à quelque chose qui n'a pas de contexte.

cependant, si vous placez un élément dans le modèle, son application gagne le contexte et la liaison fonctionne.

donc ça ne marchera pas

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

mais cela fonctionne parfaitement

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

car après que le modèle de données est appliqué, la zone 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 d'un itemscontrol est l'élément et non le contrôle, c'est-à-dire ComboBoxItem pour ComboBox et non le ComboBox lui-même, auquel cas vous devriez utiliser les contrôles ItemContainerStyle

0
MikeT