Wie verwende ich RelativeSource
WPF-Bindungen und welche Anwendungsfälle gibt es?
Wie verwende ich RelativeSource
WPF-Bindungen und welche Anwendungsfälle gibt es?
Antworten:
Wenn Sie an eine andere Eigenschaft des Objekts binden möchten:
{Binding Path=PathToProperty, RelativeSource={RelativeSource Self}}
Wenn Sie eine Eigenschaft für einen Vorfahren erhalten möchten:
{Binding Path=PathToProperty,
RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}}
Wenn Sie eine Eigenschaft für das übergeordnete Element mit Vorlagen erhalten möchten (damit Sie in einer ControlTemplate bidirektionale Bindungen erstellen können)
{Binding Path=PathToProperty, RelativeSource={RelativeSource TemplatedParent}}
oder kürzer (dies funktioniert nur für OneWay-Bindungen):
{TemplateBinding Path=PathToProperty}
AncestorType
.
FindAncestor
vorher weglasse , AncestorType
wird folgende Fehlermeldung angezeigt: "RelativeSource befindet sich nicht im FindAncestor-Modus". (In VS2013, Community-Version)
{Binding Path=DataContext.SomeProperty, RelativeSource=...
. Dies war für mich als Neuling etwas unerwartet, als ich versuchte, innerhalb einer DataTemplate an den DataContext eines Elternteils zu binden.
Binding RelativeSource={
RelativeSource Mode=FindAncestor, AncestorType={x:Type ItemType}
}
...
Das Standardattribut von RelativeSource
ist die Mode
Eigenschaft. 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 die Liste der angezeigten Datenelemente.
TemplatedParent Bezieht sich 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.
Selbst Bezieht sich auf das Element, für das Sie die Bindung festlegen, und ermöglicht es Ihnen, eine Eigenschaft dieses Elements an eine andere Eigenschaft desselben Elements zu binden.
FindAncestor Bezieht sich auf den Vorfahren in der übergeordneten Kette des datengebundenen Elements. Sie können dies verwenden, um an einen Vorfahren eines bestimmten Typs oder dessen Unterklassen zu binden. Dies ist der Modus, den Sie verwenden, wenn Sie AncestorType und / oder AncestorLevel angeben möchten.
Hier ist eine visuellere Erklärung im Kontext einer MVVM-Architektur:
{Binding Message}
(etwas einfacher ...)
Path=DataContext.Message
, dass die Bindung funktioniert. Dies ist sinnvoll, da Sie relative Bindungen zu Breite / Höhe / etc. einer Kontrolle.
Bechir Bejaoui stellt in seinem Artikel hier die Anwendungsfälle der RelativeSources in WPF vor :
Die RelativeSource ist eine Markup-Erweiterung, die in bestimmten Bindungsfällen verwendet wird, wenn wir versuchen, eine Eigenschaft eines bestimmten Objekts an eine andere Eigenschaft des Objekts selbst zu binden, wenn wir versuchen, eine Eigenschaft eines Objekts an einen anderen seiner relativen Eltern zu binden. beim Binden eines Abhängigkeitseigenschaftswerts an ein Stück XAML im Falle einer benutzerdefinierten Steuerelemententwicklung und schließlich im Fall der Verwendung eines Differentials einer Reihe gebundener Daten. Alle diese Situationen werden als relative Quellmodi ausgedrückt. Ich werde alle diese Fälle einzeln aufdecken.
- Modus Selbst:
Stellen Sie sich diesen Fall vor, ein Rechteck, dessen Höhe immer gleich seiner Breite ist, sagen wir 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 obigen Fall müssen wir jedoch den Namen des Bindungsobjekts angeben, nämlich das Rechteck. 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 dies tun, indem Sie der Bindungs-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 zu binden, da dieses Element eine Eigenschaft enthält, die als übergeordnet bezeichnet wird. Dies führt uns zu einem anderen relativen Quellmodus, dem FindAncestor.
- Modus FindAncestor
In diesem Fall wird eine Eigenschaft eines bestimmten Elements an einen seiner Elternteile, Of Corse, gebunden. Der Hauptunterschied zum obigen Fall besteht darin, dass Sie den Ahnen-Typ und den Ahnen-Rang in der Hierarchie bestimmen müssen, um die Eigenschaft zu verknüpfen. Versuchen Sie übrigens, 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 Ahnenart und -stufe. Was passiert dann, wenn die Ahnenebene nicht für den Ahnen-Typ geeignet ist? Das ist eine gute Frage, ich weiß, dass Sie sie gleich stellen werden. Die Antwort ist, dass keine Ausnahmen ausgelöst werden und auf der TextBlock-Ebene nichts angezeigt wird.
- TemplatedParent
In diesem Modus kann eine bestimmte ControlTemplate-Eigenschaft mit einer Eigenschaft des Steuerelements verknüpft werden, auf das die ControlTemplate angewendet wird. Um das Problem hier gut zu verstehen, sehen Sie 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 Steuerelementvorlage anwenden möchte, kann ich den TemplatedParent-Modus verwenden. Es gibt auch eine ähnliche zu dieser Markup-Erweiterung, nämlich die TemplateBinding, eine Art Kurzschrift der ersten, aber die TemplateBinding wird zur Kompilierungszeit im Gegensatz zum TemplatedParent ausgewertet, der 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.
ListView
. Der Elternteil hat 2 weitere ListView
Ebenen darunter. Das half mir verhindern Gabe von Daten in jedem nachfolgenden vm jedes ListView
‚sDataTemplate
In der WPF- RelativeSource
Bindung werden drei properties
zu setzen:
1. Modus: Dies ist ein Modusenum
, der vier Werte haben kann:
ein. PreviousData (
value=0
): Weistproperty
dem gebundenenden vorherigen Wert vonzub. TemplatedParent (
value=1
): Dies wird verwendet, wenn Sietemplates
ein Steuerelement definieren und an einen Wert / eine Eigenschaft des Steuerelements binden möchtencontrol
.Definieren Sie zum Beispiel
ControlTemplate
:
<ControlTemplate>
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</ControlTemplate>
c. Selbst (
value=2
): Wenn wir uns von einemself
oder einemproperty
Selbstbinden wollen.Zum Beispiel: Senden geprüft Zustand
checkbox
wieCommandParameter
beim Einstellen derCommand
aufCheckBox
<CheckBox ...... CommandParameter="{Binding RelativeSource={RelativeSource Self},Path=IsChecked}" />
d. FindAncestor (
value=3
): Wenn von einem Elternteil binden wollencontrol
inVisual Tree
.Zum Beispiel: Binden Sie ein
checkbox
in ,records
wenn eingrid
, wennheader
checkbox
geprüft wird
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid}}, Path=DataContext.IsHeaderChecked, Mode=TwoWay}" />
2. AncestorType: Wenn der Modus aktiviert ist, FindAncestor
definieren Sie, welcher Ahnen-Typ
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid}}
3. AncestorLevel: Wenn der ModusFindAncestor
dann die Ebene des Vorfahren ist (wenn zwei gleiche Elterntypen vorhanden sindvisual tree
)
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid, AncestorLevel=1}}
Oben sind alle Anwendungsfälle für
RelativeSource binding
.
Es ist erwähnenswert, dass für diejenigen, die über dieses Denken von Silverlight stolpern:
Silverlight bietet nur eine reduzierte Teilmenge dieser Befehle
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 dafür, 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 Beispiel 'BEFORE', das ich für die Methodenbindung verwende, dass der Code bereits optimiert wurde, indem RelayCommand
der zuletzt überprüfte Code kein nativer Teil von WPF ist. Ohne das wäre das Beispiel "VORHER" noch länger gewesen.
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 aus Binding Relative Source im Code Behind kopiert .
Außerdem ist die MSDN-Seite in Bezug auf Beispiele ziemlich gut: RelativeSource-Klasse
Ich habe gerade gepostet andere Lösung für den Zugriff auf den DataContext eines übergeordneten Elements in Silverlight veröffentlicht, die für mich funktioniert. Es verwendetBinding ElementName
.
Ich habe nicht jede Antwort gelesen, aber ich möchte diese Informationen nur im Falle einer relativen Quellbefehlsbindung einer Schaltfläche hinzufügen.
Wenn Sie eine relative Quelle mit verwenden Mode=FindAncestor
, 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.
Dies ist ein Beispiel für die Verwendung dieses Musters, das bei leeren Datagrids für mich 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>
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 an Daten bindet, wenn der DataContext nicht vererbt wird . Und es funktioniert absolut hervorragend!
Für den unwahrscheinlichen Fall, dass sein Blog nicht verfügbar ist, enthält Anhang A eine Spiegelkopie seines Artikels .
Bitte hier nicht kommentieren, bitte direkt in seinem Blogbeitrag kommentieren .
Die DataContext-Eigenschaft in WPF ist äußerst praktisch, da sie automatisch von allen untergeordneten Elementen des Elements geerbt 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 ist der DataContext jedoch nicht verfügbar: 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…
Lassen Sie uns anhand eines einfachen Beispiels veranschaulichen: Wir möchten eine Liste der Produkte in einem DataGrid anzeigen. Im Raster möchten wir in der Lage sein, die Spalte Preis basierend auf dem Wert einer ShowPrice-Eigenschaft, die vom ViewModel verfügbar gemacht wird, ein- oder auszublenden. 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 Werts von ShowPrice keine Auswirkung, und die Spalte ist immer sichtbar. Warum? Wenn wir uns das Ausgabefenster in Visual Studio ansehen, sehen 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); Die Zieleigenschaft ist 'Sichtbarkeit' (Typ 'Sichtbarkeit').
Die Nachricht ist ziemlich 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}}"/>
Oder wir können ein an ShowPrice gebundenes Kontrollkästchen hinzufügen und versuchen, die Spaltensichtbarkeit an die IsChecked-Eigenschaft zu binden, indem wir den Elementnamen angeben:
<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
Visibility="{Binding IsChecked,
Converter={StaticResource visibilityConverter},
ElementName=chkShowPrice}"/>
Aber keine dieser Problemumgehungen scheint zu funktionieren. Wir erzielen immer das gleiche Ergebnis.
An diesem Punkt scheint es der einzig praktikable Ansatz zu sein, die Spaltensichtbarkeit in Code-Behind zu ändern, was wir normalerweise lieber vermeiden, wenn wir das MVVM-Muster verwenden… Aber ich werde nicht so schnell aufgeben, zumindest nicht während es andere Optionen gibt, die berücksichtigt werden müssen 😉
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 veränderbaren und schreibgeschützten Status haben. Das Interessante in unserem Fall ist jedoch, dass Freezable-Objekte den DataContext erben können, auch wenn sie sich nicht im visuellen oder logischen Baum befinden. Ich kenne den genauen Mechanismus nicht, der dieses Verhalten ermöglicht, aber wir werden ihn nutzen, damit unsere Bindung funktioniert…
Die Idee ist, eine Klasse zu erstellen (ich habe sie aus Gründen, die sehr bald offensichtlich werden sollten, BindingProxy genannt), 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));
}
Wir können dann 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.