2010-08-04 5 views
84

J'ai un ListBox qui se lie à une collection enfant sur un ViewModel. Les éléments de ListBox sont décorées dans un DataTemplate basé sur une propriété sur la ViewModel mère:Accès parent DataContext à partir de DataTemplate

<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 la l'expression de liaison à "Path=DataContext.CurrentItem.CurveSpeedMustBeSpecified" il fonctionne , mais seulement tant que le datacontext du contrôle utilisateur parent est BindingListCollectionView. Ceci n'est pas acceptable car le reste du contrôle utilisateur se lie automatiquement aux propriétés du CurrentItem sur le BindingList.

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

Répondre

135

J'ai eu des problèmes avec la source relative dans Silverlight. Après la recherche et la lecture, je n'ai pas trouvé de solution appropriée sans utiliser une bibliothèque de liaison supplémentaire. Mais, voici une autre approche pour obtenir l'accès au parent DataContext en référençant directement un élément dont vous connaissez le contexte de données. Il utilise Binding ElementName et fonctionne très bien, aussi longtemps que vous respectez votre propre dénomination et ne pas lourd réutilisation des templates/styles sur 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 aussi si vous mettez le bouton en 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 article basé sur un modèle, mais depuis que je trouve pas de meilleure solution, je viens d'essayer, et il fonctionne très bien.

+0

J'ai ce code exact dans mon projet mais il fuit ViewModels (Finalizer n'est pas appelé, Command binding semble conserver DataContext). Pouvez-vous vérifier que ce problème existe aussi pour vous? –

+0

@Juve cela fonctionne, mais est-il possible de le faire afin qu'il se déclenche pour tous les itemscontrols qui implémentent le même modèle? Le nom est unique, alors nous aurions besoin d'un modèle distinct pour chacun, à moins que je ne manque quelque chose. – Chris

+1

@Juve ne pas tenir compte de mon dernier, je l'ai eu à travailler en utilisant la source de la famille avec findancestor et la recherche par ancestortype, (donc tout de même, sauf recherche par nom). Dans mon cas, je répète l'utilisation de ItemsControls chacun implémentant un template pour que le mien ressemble à ceci: Command = "{Liative RelativeSource = {RelativeSource FindAncestor, AncestorType = {x: Type ItemsControl}}, Path = DataContext.OpenDocumentBtnCommand}" – Chris

37

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

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

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

+8

J'ai dû spécifier 'Mode = FindAncestor' pour que cela fonctionne, mais cela fonctionne et est bien meilleur 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}}} "' – Aphex

+1

fonctionne comme un charme <3 et n'a pas dû spécifier le mode, .net 4.6.1 – user2475096

16

Je cherchais comment faire quelque chose de similaire à WPF et moi avons 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 à ItemsControls, et ce contexte de données a deux propriétés: MyItems -qui est une collection-, et une commande 'CustomCommand'. En raison du ItemTemplate utilise un DataTemplate, le DataContext des niveaux supérieurs n'est pas directement accessible. La solution de contournement pour obtenir le contrôleur de domaine du parent est ensuite utiliser un chemin relatif et filtrer par le type ItemsControl.

14

RelativeSource vs.elementName

Ces deux approches peuvent obtenir le même résultat,

RelativeSrouce

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

Cette méthode cherche un contrôle d'un type fenêtre (dans cet exemple) dans le visuel arbre et quand il le trouve, vous pouvez essentiellement accéder à DataContext en utilisant le Path=DataContext..... Les Pros à propos de cette méthode est que vous n'avez pas besoin d'être lié à un nom et c'est un peu dynamique, cependant, les changements apportés à votre arbre visuel peuvent affecter cette méthode et éventuellement la casser.

ElementName

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

Cette méthode à un Name référés statique solide afin que votre portée peut le voir, vous êtes fine.You devrait être coller à votre convention de nommage de 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 MSDN suivant, chacun mieux utiliser dans leur propre domaine de spécialité.

How to: Specify the Binding Source

Trouver la brève description de chacun ainsi qu'un lien vers un plus de détails dans un tableau au bas de la page.

Questions connexes