Greifen Sie über DataTemplate auf den übergeordneten DataContext zu

112

Ich habe eine, ListBoxdie an eine untergeordnete Sammlung in einem ViewModel gebunden ist. Die Listbox-Elemente werden in einer Datenvorlage basierend auf einer Eigenschaft im übergeordneten ViewModel gestaltet:

<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>

Ich erhalte folgenden Ausgabefehler:

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')

Wenn ich also den Bindungsausdruck so ändere, "Path=DataContext.CurrentItem.CurveSpeedMustBeSpecified"funktioniert er, aber nur solange der Datenkontext des übergeordneten Benutzersteuerelements a ist BindingListCollectionView. Dies ist nicht akzeptabel, da der Rest des Benutzersteuerelements automatisch an die Eigenschaften von CurrentItemon the BindingListgebunden wird.

Wie kann ich den Bindungsausdruck innerhalb des Stils angeben, damit er funktioniert, unabhängig davon, ob der übergeordnete Datenkontext eine Sammlungsansicht oder ein einzelnes Element ist?

Marius
quelle

Antworten:

161

Ich hatte Probleme mit der relativen Quelle in Silverlight. Nach dem Suchen und Lesen habe ich keine geeignete Lösung gefunden, ohne eine zusätzliche Bindungsbibliothek zu verwenden. Hier ist jedoch ein anderer Ansatz, um Zugriff auf den übergeordneten DataContext zu erhalten, indem direkt auf ein Element verwiesen wird, dessen Datenkontext Sie kennen. Es verwendet Binding ElementNameund funktioniert recht gut, solange Sie Ihre eigene Benennung respektieren und keine starke Wiederverwendung von templates/ styleszwischen Komponenten haben:

<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>

Dies funktioniert auch, wenn Sie die Schaltfläche in 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>

Zuerst dachte ich, dass die x:Namesübergeordneten Elemente nicht innerhalb eines Vorlagenelements zugänglich sind, aber da ich keine bessere Lösung gefunden habe, habe ich es einfach versucht und es funktioniert einwandfrei.

Juve
quelle
1
Ich habe genau diesen Code in meinem Projekt, aber es leckt ViewModels (Finalizer wird nicht aufgerufen, Befehlsbindung scheint DataContext beizubehalten). Können Sie überprüfen, ob dieses Problem auch für Sie besteht?
Joris Weimar
@Juve das funktioniert, aber ist es möglich, dies so zu tun, dass es für alle itemcontrols ausgelöst wird, die dieselbe Vorlage implementieren? Der Name ist eindeutig, daher benötigen wir für jede Vorlage eine eigene Vorlage, es sei denn, mir fehlt etwas.
Chris
1
@Juve ignoriere mein letztes, ich habe es zum Laufen gebracht, indem ich Verwandtschaftsquelle mit Findancestor verwendet und nach Ahnen-Typ gesucht habe (also egal, außer nicht nach Namen zu suchen). In meinem Fall wiederhole ich die Verwendung von ItemsControls, wobei jede eine Vorlage implementiert, sodass meine so aussieht: Command = "{Binding RelativeSource = {RelativeSource FindAncestor, AncestorType = {x: Typ ItemsControl}}, Path = DataContext.OpenDocumentBtnCommand}"
Chris
47

Sie können verwenden RelativeSource, um das übergeordnete Element wie folgt zu finden:

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

Weitere Informationen zu finden Sie in dieser SO-FrageRelativeSource .

Akjoshi
quelle
10
Ich musste angeben, Mode=FindAncestordass es funktioniert, aber dies funktioniert und ist in einem MVVM-Szenario viel besser, da es die Benennung von Steuerelementen vermeidet. Binding="{Binding Path=DataContext.CurveSpeedMustBeSpecified, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:YourParentElementType}}}"
Aphex
1
arbeiten wie ein Zauber <3 und mussten den Modus nicht angeben, .net 4.6.1
user2475096
30

RelativeSource vs. ElementName

Diese beiden Ansätze können das gleiche Ergebnis erzielen:

RelativeSrouce

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

Diese Methode sucht nach einem Steuerelement vom Typ Fenster (in diesem Beispiel) im visuellen Baum. Wenn es gefunden wird, können Sie im Grunde genommen DataContextüber das Steuerelement auf das Steuerelement zugreifen Path=DataContext..... Die Vorteile dieser Methode sind, dass Sie nicht an einen Namen gebunden sein müssen und sie dynamisch ist. Änderungen an Ihrem visuellen Baum können sich jedoch auf diese Methode auswirken und sie möglicherweise beschädigen.

Elementname

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

Diese Methode bezieht sich auf eine feste statische Aufladung. NameSolange Ihr Gültigkeitsbereich dies erkennen kann, ist alles in Ordnung. Sie sollten sich an Ihre Namenskonvention halten, um diese Methode natürlich nicht zu brechen. Der Ansatz ist sehr einfach und Sie müssen nur angeben a Name="..."für Ihr Window / UserControl.

Obwohl alle drei Typen ( RelativeSource, Source, ElementName) in der Lage sind, dasselbe zu tun, sollte gemäß dem folgenden MSDN-Artikel jeder besser in seinem eigenen Fachgebiet verwendet werden.

Gewusst wie: Geben Sie die Bindungsquelle an

Die kurze Beschreibung der einzelnen Elemente sowie einen Link zu weiteren Details finden Sie in der Tabelle unten auf der Seite.

Mehrad
quelle
18

Ich habe nach einer ähnlichen Vorgehensweise in WPF gesucht und diese Lösung erhalten:

<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>

Ich hoffe das funktioniert für jemand anderen. Ich habe einen Datenkontext, der automatisch auf die ItemsControls gesetzt wird, und dieser Datenkontext hat zwei Eigenschaften: MyItems- eine Sammlung - und einen Befehl 'CustomCommand'. Aufgrund der ItemTemplateVerwendung von a DataTemplateist die DataContextder oberen Ebenen nicht direkt zugänglich. Die Problemumgehung zum Abrufen des DC des übergeordneten Elements besteht darin, einen relativen Pfad zu verwenden und nach ItemsControlTyp zu filtern .

hmadrigal
quelle
0

Das Problem ist, dass ein DataTemplate nicht Teil eines Elements ist, das auf es angewendet wird.

Dies bedeutet, wenn Sie an die Vorlage binden, binden Sie an etwas, das keinen Kontext hat.

Wenn Sie jedoch ein Element in die Vorlage einfügen, erhält dieses Element, wenn es auf das übergeordnete Element angewendet wird, einen Kontext, und die Bindung funktioniert dann

Das wird also nicht funktionieren

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

aber das funktioniert perfekt

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

Denn nachdem die Datenvorlage angewendet wurde, wird das Gruppenfeld im übergeordneten Element abgelegt und hat Zugriff auf seinen Kontext

Sie müssen also nur den Stil aus der Vorlage entfernen und in ein Element in der Vorlage verschieben

Beachten Sie, dass der Kontext für ein Elementsteuerelement das Element und nicht das Steuerelement ist, dh ComboBoxItem für ComboBox, nicht die ComboBox selbst. In diesem Fall sollten Sie stattdessen die Steuerelemente ItemContainerStyle verwenden

MikeT
quelle
0

Ja, Sie können es mit dem ElementName=Somethingvon Juve vorgeschlagenen lösen .

ABER!

Wenn ein untergeordnetes Element (für das Sie diese Art der Bindung verwenden) ein Benutzersteuerelement ist, das denselben Elementnamen verwendet, den Sie im übergeordneten Steuerelement angegeben haben, wird die Bindung an das falsche Objekt gesendet !!

Ich weiß, dass dieser Beitrag keine Lösung ist, aber ich dachte, jeder, der den ElementName in der Bindung verwendet, sollte dies wissen, da es sich um einen möglichen Laufzeitfehler handelt.

<UserControl x:Class="MyNiceControl"
             x:Name="TheSameName">
   the content ...
</UserControl>

<UserControl x:Class="AnotherUserControl">
        <ListView x:Name="TheSameName">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <MyNiceControl Width="{Binding DataContext.Width, ElementName=TheSameName}" />
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
</UserControl>
Lumo
quelle