it-swarm-eu.dev

Wie verwende ich WPF-Bindungen mit RelativeSource?

Wie verwende ich RelativeSource mit WPF-Bindungen und welche Anwendungsfälle gibt es?

555
David Schmitt

Wenn Sie an eine andere Eigenschaft des Objekts binden möchten:

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

Wenn Sie eine Eigenschaft eines Vorfahren erhalten möchten:

{Binding Path=PathToProperty,
    RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}}

Wenn Sie eine Eigenschaft für das übergeordnete Templat erhalten möchten (damit Sie in einem ControlTemplate 2-Wege-Bindungen ausführen können)

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

oder kürzer (dies funktioniert nur bei OneWay-Bindungen):

{TemplateBinding Path=PathToProperty}
743
Abe Heidebrecht
Binding RelativeSource={
    RelativeSource Mode=FindAncestor, AncestorType={x:Type ItemType}
}
...

Das Standardattribut von RelativeSource ist die Eigenschaft Mode. Ein vollständiger Satz gültiger Werte wird hier angegeben ( von MSDN ):

  • PreviousData Ermöglicht das Binden des vorherigen Datenelements (nicht des Steuerelements, das das Datenelement enthält) in der Liste der angezeigten Datenelemente.

  • TemplatedParent Verweist auf das Element, auf das die Vorlage (in der das datengebundene Element vorhanden ist) angewendet wird. Dies ähnelt dem Festlegen einer TemplateBindingExtension und gilt nur, wenn sich die Bindung in einer Vorlage befindet.

  • Self Bezieht sich auf das Element, für das Sie die Bindung festlegen, und ermöglicht das Binden einer Eigenschaft dieses Elements an eine andere Eigenschaft desselben Elements.

  • FindAncestor Verweist auf den Vorfahren in der übergeordneten Kette des datengebundenen Elements. Sie können dies verwenden, um an einen Vorfahren eines bestimmten Typs oder seiner Unterklassen zu binden. Dies ist der Modus, den Sie verwenden, wenn Sie AncestorType und/oder AncestorLevel angeben möchten.

127
Drew Noakes

Im Folgenden finden Sie eine anschaulichere Erklärung im Zusammenhang mit einer MVVM-Architektur:

enter image description here

121
Jeffrey Knight

Stellen Sie sich diesen Fall vor, ein Rechteck, dessen Höhe immer der Breite entspricht, beispielsweise ein Quadrat. Wir können dies mit dem Elementnamen tun

<Rectangle Fill="Red" Name="rectangle" 
                    Height="100" Stroke="Black" 
                    Canvas.Top="100" Canvas.Left="100"
                    Width="{Binding ElementName=rectangle,
                    Path=Height}"/>

In diesem Fall sind wir jedoch verpflichtet, den Namen des verbindlichen Objekts, nämlich das Rechteck, anzugeben. Mit der RelativeSource können wir den gleichen Zweck unterschiedlich erreichen

<Rectangle Fill="Red" Height="100" 
                   Stroke="Black" 
                   Width="{Binding RelativeSource={RelativeSource Self},
                   Path=Height}"/>

In diesem Fall sind wir nicht verpflichtet, den Namen des Bindungsobjekts anzugeben, und die Breite entspricht immer der Höhe, wenn die Höhe geändert wird.

Wenn Sie die Breite auf die Hälfte der Höhe einstellen möchten, können Sie der Binding-Markup-Erweiterung einen Konverter hinzufügen. Stellen wir uns jetzt einen anderen Fall vor:

 <TextBlock Width="{Binding RelativeSource={RelativeSource Self},
                   Path=Parent.ActualWidth}"/>

Der obige Fall wird verwendet, um eine bestimmte Eigenschaft eines bestimmten Elements an eine seiner direkten übergeordneten Eigenschaften zu binden, da dieses Element eine Eigenschaft enthält, die als Parent bezeichnet wird. Dies führt uns zu einem anderen relativen Quellmodus, dem FindAncestor.

41

Bechir Bejaoui enthüllt die Anwendungsfälle der RelativeSources in WPF in sein Artikel hier :

Die RelativeSource ist eine Markup-Erweiterung, die in bestimmten Bindungsfällen verwendet wird, wenn versucht wird, eine Eigenschaft eines bestimmten Objekts an eine andere Eigenschaft des Objekts selbst zu binden, wenn versucht wird, eine Eigenschaft eines Objekts an eine andere der relativen Eltern zu binden. beim Binden eines Abhängigkeitseigenschaftswerts an eine XAML-Komponente im Falle der Entwicklung eines benutzerdefinierten Steuerelements und schließlich im Falle der Verwendung eines Differentials aus einer Reihe gebundener Daten. Alle diese Situationen werden als relative Quellenmodi ausgedrückt. Ich werde alle diese Fälle einzeln aufdecken.

  1. Modus Selbst:

Stellen Sie sich diesen Fall vor, ein Rechteck, dessen Höhe immer der Breite entspricht, beispielsweise ein Quadrat. Wir können dies mit dem Elementnamen tun

<Rectangle Fill="Red" Name="rectangle" 
                Height="100" Stroke="Black" 
                Canvas.Top="100" Canvas.Left="100"
                Width="{Binding ElementName=rectangle,
                Path=Height}"/>

In diesem Fall sind wir jedoch verpflichtet, den Namen des verbindlichen Objekts, nämlich das Rechteck, anzugeben. Mit der RelativeSource können wir den gleichen Zweck unterschiedlich erreichen

<Rectangle Fill="Red" Height="100" 
               Stroke="Black" 
               Width="{Binding RelativeSource={RelativeSource Self},
               Path=Height}"/>

In diesem Fall sind wir nicht verpflichtet, den Namen des Bindungsobjekts anzugeben, und die Breite entspricht immer der Höhe, wenn die Höhe geändert wird.

Wenn Sie die Breite auf die Hälfte der Höhe einstellen möchten, können Sie der Binding-Markup-Erweiterung einen Konverter hinzufügen. Stellen wir uns jetzt einen anderen Fall vor:

 <TextBlock Width="{Binding RelativeSource={RelativeSource Self},
               Path=Parent.ActualWidth}"/>

Der obige Fall wird verwendet, um eine bestimmte Eigenschaft eines bestimmten Elements an eine seiner direkten übergeordneten Eigenschaften zu binden, da dieses Element eine Eigenschaft enthält, die als Parent bezeichnet wird. Dies führt uns zu einem anderen relativen Quellmodus, dem FindAncestor.

  1. Modus FindAncestor

In diesem Fall wird eine Eigenschaft eines bestimmten Elements an eines seiner Elternteile, Of Corse, gebunden. Der Hauptunterschied zum obigen Fall besteht darin, dass Sie den Vorfahrentyp und den Vorfahrenrang in der Hierarchie bestimmen müssen, um die Eigenschaft zu binden. Übrigens versuchen Sie, mit diesem Stück XAML zu spielen

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

Die obige Situation besteht aus zwei TextBlock-Elementen, die in eine Reihe von Rahmen eingebettet sind, und Canvas-Elementen, die ihre hierarchischen Eltern darstellen. Der zweite TextBlock zeigt den Namen des angegebenen übergeordneten Elements auf der relativen Quellenebene an.

Versuchen Sie also, AncestorLevel = 2 in AncestorLevel = 1 zu ändern, und sehen Sie, was passiert. Versuchen Sie dann, den Typ des Vorfahren von AncestorType = Border in AncestorType = Canvas zu ändern, und sehen Sie, was passiert.

Der angezeigte Text ändert sich je nach Vorfahrentyp und -stufe. Was passiert dann, wenn die Ahnenstufe nicht zum Ahnen-Typ passt? Das ist eine gute Frage, ich weiß, dass Sie sie stellen werden. Die Antwort ist, dass keine Ausnahmen geworfen werden und nichts auf der TextBlock-Ebene angezeigt wird.

  1. TemplatedParent

In diesem Modus können Sie eine bestimmte ControlTemplate-Eigenschaft mit einer Eigenschaft des Steuerelements verknüpfen, auf das die ControlTemplate angewendet wird. Das Problem hier gut zu verstehen, ist ein Beispiel unten

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

Wenn ich die Eigenschaften eines bestimmten Steuerelements auf seine Steuerungsvorlage anwenden möchte, kann ich den TemplatedParent-Modus verwenden. Es gibt auch eine ähnliche Markup-Erweiterung wie die TemplateBinding, die eine Art Kurzform der ersten ist, aber die TemplateBinding wird zur Kompilierungszeit im Gegensatz zur TemplatedParent-Erweiterung ausgewertet, die unmittelbar nach der ersten Laufzeit ausgewertet wird. Wie Sie in der folgenden Abbildung sehen können, werden der Hintergrund und der Inhalt über die Schaltfläche auf die Steuerungsvorlage angewendet.

34
Cornel Marian

In WPF macht die RelativeSource-Bindung drei properties-Objekte verfügbar, die festgelegt werden sollen:

1. Modus: Dies ist ein enum, der vier Werte annehmen kann:

a. PreviousData (_value=0_): Weist dem gebundenen Wert den vorherigen Wert von property zu

b. TemplatedParent (_value=1_): Dies wird verwendet, wenn das templates eines Steuerelements definiert wird und an einen Wert/eine Eigenschaft des control gebunden werden soll.

Zum Beispiel definieren Sie ControlTemplate:

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

c. Selbst (_value=2_): Wenn wir von einem self oder einem property Selbst binden wollen.

Zum Beispiel: Überprüften Status von checkbox als CommandParameter senden, während Command auf CheckBox gesetzt wird

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

d. FindAncestor (_value=3_): Wenn von einem übergeordneten control in _Visual Tree_ gebunden werden soll.

Zum Beispiel: Bindet ein checkbox in records, wenn ein grid, wenn headercheckbox aktiviert ist

_<CheckBox IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid}}, Path=DataContext.IsHeaderChecked, Mode=TwoWay}" />
_

2. AncestorType: Wenn mode FindAncestor ist, definieren Sie den Typ des Vorfahren

_RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid}}
_

3. AncestorLevel: wenn mode FindAncestor ist, dann welche Ebene des Vorfahren (wenn es zwei gleiche Arten von Eltern in _visual tree_ gibt)

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

Vorstehend sind alle Anwendungsfälle für RelativeSource binding aufgeführt.

Hier ist ein Verweis .

25
Kylo Ren

TemplatedParent nicht vergessen:

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

oder

{Binding RelativeSource={RelativeSource TemplatedParent}}
18
Bob King

Ich habe eine Bibliothek erstellt, um die Bindungssyntax von WPF zu vereinfachen und die Verwendung von RelativeSource zu vereinfachen. Hier sind einige Beispiele. Vor:

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

Nach:

{BindTo PathToProperty}
{BindTo Ancestor.typeOfAncestor.PathToProperty}
{BindTo Template.PathToProperty}
{BindTo #MyTextBox.Text}

Hier ist ein Beispiel, wie die Methodenbindung vereinfacht wird. Vor:

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

Nach:

// C# code
private void SaveObject() {
 // do something
}

// XAML
{BindTo SaveObject()}

Sie finden die Bibliothek hier: http://www.simplygoodcode.com/2012/08/simpler-wpf-binding.html

Beachten Sie im BEFORE-Beispiel, dass ich für die Methodenbindung diesen Code verwende, der bereits mithilfe von RelayCommand optimiert wurde. Dieser Code, den ich zuletzt überprüft habe, ist kein nativer Bestandteil von WPF. Ohne das wäre das BEFORE-Beispiel noch länger gewesen.

13
Luis Perez

Es ist bemerkenswert, dass für diejenigen, die über dieses Denken von Silverlight stolpern:

Silverlight bietet nur eine reduzierte Teilmenge dieser Befehle

13
Matthew Black

Einige nützliche Kleinigkeiten:

So geht's meistens im Code:

Binding b = new Binding();
b.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, this.GetType(), 1);
b.Path = new PropertyPath("MyElementThatNeedsBinding");
MyLabel.SetBinding(ContentProperty, b);

Ich habe dies größtenteils von Binding Relative Source im Code Behind kopiert.

Auch die MSDN-Seite ist in Bezug auf Beispiele ziemlich gut: RelativeSource Class

12
Nathan Cooper

Ich habe gerade eine andere Lösung für den Zugriff auf den DataContext eines übergeordneten Elements in Silverlight gepostet, die für mich funktioniert. Es wird Binding ElementName verwendet.

10
Juve

Ich habe nicht jede Antwort gelesen, aber ich möchte diese Informationen nur für den Fall der relativen Quellbefehlsbindung einer Schaltfläche hinzufügen.

Wenn Sie eine relative Quelle mit Mode=FindAncestor verwenden, muss die Bindung wie folgt aussehen:

Command="{Binding Path=DataContext.CommandProperty, RelativeSource={...}}"

Wenn Sie Ihrem Pfad keinen DataContext hinzufügen, kann die Eigenschaft zur Ausführungszeit nicht abgerufen werden.

9
Kevin VDF

Dies ist ein Beispiel für die Verwendung dieses Musters, das bei leeren Datagrids funktioniert hat.

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

Wenn ein Element nicht Teil des visuellen Baums ist, funktioniert RelativeSource niemals.

In diesem Fall müssen Sie eine andere Technik ausprobieren, die von Thomas Levesque entwickelt wurde.

Er hat die Lösung in seinem Blog unter [WPF] Wie man Daten bindet, wenn der DataContext nicht vererbt wird . Und es funktioniert absolut genial!

Für den unwahrscheinlichen Fall, dass sein Blog nicht funktioniert, enthält Anhang A eine Kopie von seinem Artikel .

Bitte hier nicht kommentieren, bitte Kommentar direkt in seinem Blog Post .

Anhang A: Spiegel des Blogposts

Die DataContext-Eigenschaft in WPF ist äußerst praktisch, da sie automatisch von allen untergeordneten Elementen des Elements übernommen wird, dem Sie sie zuweisen. Daher müssen Sie es nicht für jedes Element, das Sie binden möchten, erneut festlegen. In einigen Fällen kann jedoch nicht auf den DataContext zugegriffen werden: Dies geschieht für Elemente, die nicht Teil des visuellen oder logischen Baums sind. Es kann dann sehr schwierig sein, eine Eigenschaft an diese Elemente zu binden ...

Veranschaulichen wir dies anhand eines einfachen Beispiels: Wir möchten eine Liste von Produkten in einem DataGrid anzeigen. Im Raster möchten wir die Price-Spalte basierend auf dem Wert einer ShowPrice-Eigenschaft, die vom ViewModel verfügbar gemacht wird, ein- oder ausblenden können. Der naheliegende Ansatz besteht darin, die Sichtbarkeit der Spalte an die ShowPrice-Eigenschaft zu binden:

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                Visibility="{Binding ShowPrice,
                Converter={StaticResource visibilityConverter}}"/>

Leider hat das Ändern des ShowPrice-Werts keine Auswirkung, und die Spalte ist immer sichtbar. Warum? Wenn wir uns das Ausgabefenster in Visual Studio ansehen, bemerken wir die folgende Zeile:

System.Windows.Data-Fehler: 2: Das maßgebliche FrameworkElement oder FrameworkContentElement für das Zielelement wurde nicht gefunden. BindingExpression: Path = ShowPrice; DataItem = null; Zielelement ist "DataGridTextColumn" (HashCode = 32685253). Zieleigenschaft ist "Sichtbarkeit" (Typ "Sichtbarkeit")

Die Nachricht ist eher kryptisch, aber die Bedeutung ist eigentlich recht einfach: WPF weiß nicht, welches FrameworkElement zum Abrufen des DataContext verwendet werden soll, da die Spalte nicht zum visuellen oder logischen Baum des DataGrid gehört.

Wir können versuchen, die Bindung zu optimieren, um das gewünschte Ergebnis zu erzielen, indem wir beispielsweise die RelativeSource auf das DataGrid selbst setzen:

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                Visibility="{Binding DataContext.ShowPrice,
                Converter={StaticResource visibilityConverter},
                RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}}"/>

Alternativ können wir ein an ShowPrice gebundenes Kontrollkästchen hinzufügen und versuchen, die Sichtbarkeit der Spalte durch Angabe des Elementnamens an die IsChecked-Eigenschaft zu binden:

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                Visibility="{Binding IsChecked,
                Converter={StaticResource visibilityConverter},
                ElementName=chkShowPrice}"/>

Aber keine dieser Problemumgehungen scheint zu funktionieren, wir erhalten immer das gleiche Ergebnis ...

An diesem Punkt scheint es der einzig gangbare Weg zu sein, die Sichtbarkeit der Spalten im Code-Behind zu ändern, was wir normalerweise bevorzugen, wenn wir das MVVM-Muster verwenden. Aber ich werde nicht so schnell aufgeben, zumindest nicht während es andere Optionen zu prüfen gibt ????

Die Lösung für unser Problem ist eigentlich recht einfach und nutzt die Freezable-Klasse. Der Hauptzweck dieser Klasse besteht darin, Objekte zu definieren, die einen änderbaren und einen schreibgeschützten Status haben. In unserem Fall ist jedoch interessant, dass Freezable-Objekte den DataContext erben können, auch wenn sie sich nicht in der visuellen oder logischen Struktur befinden. Ich kenne den genauen Mechanismus, der dieses Verhalten ermöglicht, nicht, aber wir werden ihn nutzen, um unsere verbindliche Arbeit zu leisten.

Die Idee ist, eine Klasse (ich habe sie aus Gründen, die bald offensichtlich werden sollten, BindingProxy genannt) zu erstellen, die Freezable erbt und eine Datenabhängigkeitseigenschaft deklariert:

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));
}

Anschließend können wir eine Instanz dieser Klasse in den Ressourcen des DataGrid deklarieren und die Data-Eigenschaft an den aktuellen DataContext binden:

<DataGrid.Resources>
    <local:BindingProxy x:Key="proxy" Data="{Binding}" />
</DataGrid.Resources>

Der letzte Schritt besteht darin, dieses BindingProxy-Objekt (mit StaticResource leicht zugänglich) als Quelle für die Bindung anzugeben:

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                Visibility="{Binding Data.ShowPrice,
                Converter={StaticResource visibilityConverter},
                Source={StaticResource proxy}}"/>

Beachten Sie, dass dem Bindungspfad "Daten" vorangestellt wurde, da der Pfad jetzt relativ zum BindingProxy-Objekt ist.

Die Bindung funktioniert jetzt ordnungsgemäß und die Spalte wird basierend auf der ShowPrice-Eigenschaft ordnungsgemäß angezeigt oder ausgeblendet.

4
Contango