Xaml Binding–Relative Sources

Wenn man mit WPF Oberflächen gestaltet kommt man nicht um Binding Anweisungen herum.
Meistens sind diese Bindings einfach und sofort verständlich.

<StackPanel>
     <TextBox x:Name="textBox" Text="10" />
     <Slider Minimum="0" Maximum="100" Value="{Binding ElementName=textBox, Path=Text}" />
</StackPanel>

 

Tolle Sache - funktioniert einwandfrei. Sucht man jedoch ein Bisschen im Web herum, wird es nicht
lange dauern bis man folgenden Code trifft.

Value="{Binding RelativeSource={RelativeSource ..... }}"

Die Punkte sind dabei nur Platzhalter - da würde jedes mal was anderes stehen.
Für mich war diese Schreibweise lange Zeit sehr unverständlich.

Gehen wir ein bisschen tiefer.
Was sehen haben wir den da eigentlich vor uns?

Wir haben ein Exemplar (Ich verwende an der Stelle absichtlich nicht den Begriff Instanz, da dies
keine ganz richtige Übersetzung aus dem englischen Wort „Instance“ ist.) der Klasse Binding.
Dieses Exemplar hat eine Eigenschaft(Property) „RelativeSource“.
In C# würde das ganze bis jetzt folgendermaßen aussehen:

Binding b = new Binding();
b.RelativeSource = ...

Da sehen wir, was dieses erste RelativeSource eigentlich ist - nichts weiter als ein normales
Property vom Typ RelativeSource.
Oben steht jedoch zwei mal das Wort RelativeSource.
Das zweite Mal ist nichts weiter als ein Exemplar der Klasse RelativeSource. Sieht in C# so aus:

RelativeSource rs = new RelativeSource();

Dieses Exemplar von RelativeSource hat drei Properties.

Properties von RelativeSource Datentyp
Mode RelativeSourceMode
AncestorLevel int
AncestorType Type

 

Wie wir in der Tabelle sehen ist Mode ein Property vom Typ RelativeSourceMode.
RelativeSourceMode ist ein enum mit genau vier Werten. Die weiteren zwei Properties AncastorLevel und AncastorType werden nur bei einem bestimmten Fall benötigt. Darauf gehe ich später noch einmal ein.

RelativeSourceMode
Self
TemplateParent
FindAncestor
PreviousData

 

Diese Werte von RelativeSourceMode sind die eigentliche Stärke des RelativeSource-Bindings, denn damit bestimmt man, worauf man eigentlich binden will.  Ich werde im Folgenden auf die einzelnen Punkte im Detail eingehen. Die Verwendung von RelativeSource erfolgt immer anstatt der Verwendung des Properties ElementName.

Self

Ein Beispiel: Wir haben ein Rechteck und wollen das die Höhe des Rechtecks immer gleich ist, wie die Breite des Rechtecks. - Also ein Quadrat. Im Prinzip bedeutet das, wir müssen die „Height“ des Rechtecks auf die „Width“ desselben Exemplars binden. Genau für solche Fälle gibt es RelativeSourceMode.Self. Wir verweisen damit also auf das Exemplar selber - nicht wie in dem ersten Beispiel auf ein anderes Objekt.

<Rectangle Fill="Blue"
           Width="50" 
           Height="{Binding RelativeSource={RelativeSource Self}, Path=Width}" />

 

Mit den Infos die wir bereits in der Einleitung des Artikels bekommen haben sollte diese Zeile jetzt einfach zu verstehen sein.

TemplateParent

Bei RelativeSourceMode.TemplateParent müssen wir von der Situation ausgehen, dass wir ein Template für ein bestimmtes Control machen.
Zu Anschauung werde ich ein ganz einfaches ControlTemplate für einen Button machen.

<ControlTemplate x:Key="buttonTemplate" TargetType="Button">
    <Border BorderBrush="Aquamarine" BorderThickness="5">
        <TextBox HorizontalAlignment="Center" Text="Hallo RelativeSource" />
    </Border>
</ControlTemplate>

 

Natürlich ist es Schwachsinn in ein Template ein fixen Text zu schreiben, denn das möchte ich ja bei jedem meiner Buttons selber entscheiden. Das bedeutet wir benötigen wieder ein Binding. Diesmal möchten wir ja auf das Objekt verweisen, welches unser ControlTemplate verwendet.
Sieht dann folgendermaßen aus:

<ControlTemplate x:Key="buttonTemplate" TargetType="Button">
    <Border BorderBrush="Aquamarine" BorderThickness="5">
        <TextBox HorizontalAlignment="Center" 
                 Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}" />
    </Border>
</ControlTemplate>

<Button Template="{StaticResource buttonTemplate}" Content="MyButton" />
<Button Template="{StaticResource buttonTemplate}" Content="The most beautiful Button in the World." />

 

FindAncestor

Oft haben wir den Fall, einen verschachtelte Hierarchie aufgebaut zu haben und dann möchte man auf ein Objekt binden, welches in der Hierarchie weiter oben liegt.

<StackPanel>
    <Border Margin="20" BorderBrush="Red" BorderThickness="5">
        <StackPanel>
            <Border Margin="20" BorderBrush="Green" BorderThickness="5">
                <TextBlock Margin="20" 
                           Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
                                     AncestorType=Border, AncestorLevel=2}, Path=BorderBrush}" />
            </Border>
        </StackPanel>
    </Border>
</StackPanel>

 

Überfliegen wir mal kurz was ich da geschrieben habe.
Wir haben einen TextBlock in einem Border in einem StackPanel in einem Border in einem StackPanel. – Toll zu lesen oder? Smiley mit geöffnetem Mund

Betrachten wir das Binding. Ausgewählt wurde RelativeSourceMode.FindAncestor. Dies ist der einzige Modus, bei dem die beiden anderen, oben bereits erwähnten, Properes von RelativesSource - AncestorType und AncestorLevel - zum Tragen kommen.

Unser Programm sieht so aus:

 image

Wie wir sehen wird in dem Textblock der Farbwert unseres ersten Borders angezeigt.
Wie können wir das verstehen?
Mit dem Property AncestorType sagen wir ihm auf welchen Typ unserer hierarchisch übergeordneten Objekte wir zurückgreifen wollen und mit dem Property AncestorLevel bestimmen wir die “Ebene“ (beginnend bei 1).
Angenommen wir setzen in unserem Beispiel das AncestorLevel auf 1 würden wir den Farbwert für Grün bekommen (#FF00FF00 - die ersten zwei F stehen für den Alpha-Kanal, also die Helligkeit. Diese Art der Farbangabe nennt man ARGB. Aber das ist ein anderes Thema.).

PreviousData

RelativeSourceMode.PreviousData macht nur Sinn, wenn man mit Collections arbeitet. Wie der Name schon sagt bezieht man sich auf die vorhergehenden Daten.
Die Anwendungsfälle für diesen Modus sind meiner Meinung nach sehr begrenzt und in der Praxis so gut wie nie zu finden.
Stellen wir uns vor wir haben eine Listbox mit einem eigenen ItemsTemplate. Vorausgesetzt man möchte bei jedem Eintrag noch irgendwelche Daten aus dem vorhergehenden Listenelement anzeigen, dann ist PreviousData „the way to go“. Man bezieht sich also wie gesagt immer auf das vorhergehende Element in der Auflistung.
Etwas gleichartiges in die gegengesetzte Richtung, also immer ein Element nach vorne, gibt es nicht.

Ich hoffe ich konnte einige Unklarheiten klären und euch das Leben mit RelativeSources einfacher machen.

Kommentare sind geschlossen