26.3 Definition einer Dependency Property
Dependency Properties können nur in Klassen definiert werden, die von der Klasse DependencyObject abgeleitet sind. Diese Bedingung wird von allen wesentlichen Klassen der WPF erfüllt.
Sehen wir uns jetzt exemplarisch an, wie eine Abhängigkeitseigenschaft mit dem Namen Radius in der Klasse Circle bereitgestellt wird. Grundsätzlich werden alle Abhängigkeitseigenschaften durch ein Objekt vom Typ DependencyProperty beschrieben. In unserem Beispiel lautet die Definition folgendermaßen:
public class Circle : DependencyObject
{
public static readonly DependencyProperty RadiusProperty;
}
Listing 26.1 Grundgerüst einer Abhängigkeitseigenschaft
Wichtig ist, die abhängige Eigenschaft static readonly zu kennzeichnen. Damit werden zwei Verhaltensmerkmale erzwungen:
- Da eine Abhängigkeitseigenschaft static definiert ist, wird sie nur einmal bereitgestellt – nicht nur für alle Circle-Objekte, sondern auch für alle Objekte, die auf Klassen basieren, die von Circle abgeleitet sind. Alle auf Circle zurückzuführenden Objekte nutzen die Dependency Property gemeinsam.
- Durch den Modifikator readonly im Zusammenhang mit static wird eine Konstante definiert, deren Wert spätestens im statischen Konstruktor festgeschrieben werden muss. Wie Sie in diesem Kapitel noch lernen werden, zeichnen sich Abhängigkeitseigenschaften durch spezifische, unveränderliche Merkmale aus, so dass die Vorstellung von einer Konstanten auch im Zusammenhang mit den Eigenschaften durchaus gerechtfertigt ist.
Per Konvention muss dem beabsichtigten Eigenschaftsbezeichner (in unserem Beispiel Radius) das Suffix Property angehängt werden. In unserem Beispiel heißt deshalb das Feld der Abhängigkeitseigenschaft RadiusProperty.
26.3.1 Registrieren einer Abhängigkeitseigenschaft

Mit der Felddefinition alleine ist eine abhängige Eigenschaft natürlich noch nicht vollständig beschrieben. Es fehlen zu diesem Zeitpunkt noch viele Detailinformationen, beispielsweise der von der Eigenschaft beschriebene Datentyp, gegebenenfalls der Standardwert sowie viele andere Merkmale im Umfeld des Einsatzes innerhalb der WPF. Zudem muss eine Abhängigkeitseigenschaft dem WPF-Subsystem bekannt gegeben werden.
Um alle Bedingungen zu erfüllen, muss eine Abhängigkeitseigenschaft mit der statischen Methode Register der Klasse DependencyProperty registriert werden. Die Methode ist vielfach überladen. Sehen wir uns zuerst die einfachste Definition an:
public static DependencyProperty Register(String, Type, Type)
Die drei Parameter lassen sich wie folgt beschreiben:
- Aus dem ersten Parameter geht der Bezeichner der Eigenschaft hervor. Dieser muss für den an den dritten Parameter übergebenen Besitzertyp eindeutig sein.
- Der zweite Parameter erwartet die Angabe des Datentyps, den die Eigenschaft beschreibt.
- Dem dritten Parameter wird mitgeteilt, für welchen Typ die abhängige Eigenschaft registriert werden soll.
Für das Beispiel unserer Abhängigkeitseigenschaft Radius könnte das wie folgt aussehen:
static Circle() {
RadiusProperty = DependencyProperty.Register("Radius", typeof(int),
typeof(Circle));
}
Listing 26.2 Registrierung der Eigenschaft »Radius« als Abhängigkeitseigenschaft
26.3.2 Der Eigenschaftswrapper

Damit würde unsere Abhängigkeitseigenschaft bereits beim WPF-Subsystem registriert und wäre prinzipiell bereits fertig. Mit den folgenden Anweisungen kann bereits zu diesem Zeitpunkt der Radius eines Circle-Objekts wie nachfolgend gezeigt festgelegt werden:
Circle kreis = new Circle();
kreis.SetValue(Circle.RadiusProperty, 118);
Listing 26.3 Festlegen eines Wertes einer Abhängigkeitseigenschaft
Bei SetValue handelt es sich um eine Methode, die von der Basisklasse DependencyObject geerbt wird. Zwei Argumente werden von der Methode erwartet: Im ersten Argument wird die Abhängigkeitseigenschaft angeführt, deren Wert lokal gesetzt werden soll, das zweite Argument beschreibt den Wert selbst.
Sehr ähnlich erfolgt auch die Auswertung. Hierzu dient die Methode GetValue, der die auszuwertende Abhängigkeitseigenschaft als Argument übergeben wird:
Listing 26.4 Auswerten einer Abhängigkeitseigenschaft
Es fällt in den beiden Codefragmenten auf, dass die traditionelle Zuweisung mit dem Zuweisungsoperator nicht möglich ist und sogar zu einem Fehler führt:
kreis.Radius = 118;
Selbstverständlich möchte man auf die von den CLR-Eigenschaften her gewohnte Variante der Eigenschaftswertfestlegung nicht verzichten. Zudem wäre es nach dem momentanen Stand auch nicht möglich, die Eigenschaft Radius im XAML-Code festzulegen, der immer die Existenz eines get- und set-Zweigs voraussetzt. Deshalb gehört zu jeder Registrierung einer Abhängigkeitseigenschaft auch die Bereitstellung eines Eigenschaftswrappers. Am Beispiel der Eigenschaft Radius würde man den Wrapper wie folgt definieren:
public int Radius
{
get { return (int)GetValue(RadiusProperty); }
set { SetValue(RadiusProperty, value); }
}
Listing 26.5 Eigenschaftswrapper der Eigenschaft »Radius«
Dabei kommen erneut die beiden bereits oben erwähnten Methoden SetValue und GetValue ins Spiel. Das Festlegen und die Auswertung eines Radius kann nun in bekannter Weise mit
Circle kreis = new Circle();
kreis.Radius = 120;
int radius = kreis.Radius;
erfolgen.
In einem Eigenschaftswrapper sollten Sie niemals Code schreiben, der zur Validierung dient oder ein Ereignis auslöst. Der Grund dafür ist, dass viele WPF-spezifische Features keine Notiz vom Eigenschaftswrapper nehmen und mit der Eigenschaft nur unter Aufruf von SetValue und GetValue operieren. Code, der im Eigenschaftswrapper steht, würde also niemals ausgeführt. Zur Validierung eines Eigenschaftswertes bzw. zur Auslösung von Ereignissen bei einer Eigenschaftsänderung stellt die WPF alternativ andere Möglichkeiten bereit, die wir in den nächsten Abschnitten noch kennenlernen werden.
Tatsächlich ist es so, dass der Eigenschaftswrapper eine Voraussetzung dafür ist, dass einer Eigenschaft im XAML-Code ein Wert zugewiesen wird, in unserem Fall beispielsweise mit
<Window.Resources>
<local:Circle x:Key="kreis" Radius="120" />
</Window.Resources>
Natürlich muss dabei gewährleistet sein, dass Radius einen Wert beschreibt, der größer oder gleich null ist.
Erfolgt die Validierung innerhalb des set-Zweigs, z. B.
set {
if (value > 0)
SetValue(RadiusProperty, value);
else
throw new Exception();
}
werden Sie feststellen, dass die Ausnahme nicht ausgelöst wird und das Circle-Objekt tatsächlich einen negativen Radius hat. Das lässt sich sehr einfach beweisen, indem man den folgenden XAML-Code benutzt:
<Window xmlns:local="clr-namespace:WpfApplication1" ...>
<Window.Resources>
<local:Circle x:Key="k" Radius="-120" />
</Window.Resources>
<Grid>
<TextBlock Text="{Binding Source={StaticResource k}, Path=Radius}" />
</Grid>
</Window>
Im TextBlock des Fensters wird der negative Wert angezeigt.
26.3.3 Die Eigenschaftsmetadaten

CLR-Eigenschaften sind recht einfach gestrickt. Sie repräsentieren einen gültigen Wert des Objekts, der über einen set-Zweig gesetzt und über einen get-Zweig ausgewertet wird. Ganz anders sind die an eine Abhängigkeitseigenschaft gestellten Anforderungen. Einige Abhängigkeitseigenschaften üben bei ihrer Änderung einen Einfluss auf die im Elementbaum über- oder untergeordnete Komponente aus, andere verändern das Layout oder werden ihrerseits selbst durch Animationen, Styles oder Templates beeinflusst. WPF-Datenbindungen wären ohne Abhängigkeitseigenschaften in ihren Fähigkeiten nicht denkbar.
Die oben vorgestellte einfache Registrierung einer Dependency Property birgt in sich noch nicht die Möglichkeiten, die aufgezählten Merkmale umzusetzen. Zudem kann mit diesem Ansatz keine Datenvalidierung umgesetzt werden. Um alle Features auszuschöpfen, bietet sich die folgende Überladung der Register-Methode an:
public static DependencyProperty Register(String,
Type,
Type,
PropertyMetadata,
ValidateValueCallback)
Listing 26.6 Überladung der »Register«-Methode
Die drei ersten Parameter entsprechen exakt denen, die bereits beschrieben worden sind. Der vierte Parameter vom Typ PropertyMetadata legt die WPF-spezifischen Merkmale der Abhängigkeitseigenschaft fest, die sogenannten Eigenschaftsmetadaten. Der fünfte und letzte Parameter beschreibt schließlich einen Delegaten, der für die Validierung des Eigenschaftswertes sorgt.
Sehen wir uns zuerst den vierten Parameter etwas genauer an. Per Definition handelt es sich dabei um ein Objekt vom Typ PropertyMetadata. Im Wesentlichen benutzt man exakt diesen Metadatentyp, wenn einer Abhängigkeitseigenschaft nur ein Standardwert mit auf den Lebensweg gegeben werden soll. Meistens wird das aber nicht ausreichend sein, so dass entweder die von PropertyMetadata abgeleiteten Typen UIPropertyMetadata oder FrameworkPropertyMetadata eingesetzt werden. Dabei erweitert UIPropertyMetadata die Klasse PropertyMetadata nur um Merkmale im Zusammenspiel mit Animationen. In der Regel wird man deshalb auf ein Objekt vom Typ der Ableitung FrameworkPropertyMetadata zurückgreifen.
Die meisten Eigenschaften der Klasse FrameworkPropertyMetadata beschreiben boolesche Werte, die zunächst auf false eingestellt sind. Eine Eigenschaft des Typs ermöglicht zudem die Angabe einer Rückrufmethode (Callback), in der Aufgaben im Zusammenhang mit der Festlegung des Eigenschaftswertes ausgeführt werden können (PropertyChangedCallback). Einer weiteren Eigenschaft kann über einen Delegaten eine Methode zur internen Validierung (CoerceValueCallback) mitgeteilt werden.
Um Ihnen einen Überblick zu verschaffen, sind in Tabelle 26.1 einige Eigenschaften der Klasse FrameworkPropertyMetadata aufgeführt.
Eigenschaft | Beschreibung |
AffectsMeasure |
Gibt an, dass nach einer Änderung der Abhängigkeitseigenschaft die Abmessungen neu ermittelt werden. |
AffectsArrange |
Gibt an, dass nach einer Änderung der Abhängigkeitseigenschaft die Anordnung der enthaltenen Steuerelemente neu ermittelt wird. |
AffectsParentMeasure |
Gibt an, dass nach einer Änderung der Abhängigkeitseigenschaft die Abmessungen des übergeordneten Steuerelements neu ermittelt werden. |
AffectsParentArrange |
Gibt an, dass nach einer Änderung der Abhängigkeitseigenschaft die Anordnung der Steuerelemente in der übergeordneten Komponente neu ermittelt wird. |
AffectsRender |
Gibt an, ob eine Abhängigkeitseigenschaft Einfluss auf das allgemeine Layout hat und möglicherweise das Element zwingt, sich neu zu zeichnen. |
BindsTwoWayByDefault |
Legt fest, ob die Abhängigkeitseigenschaft das »Two-Way-Binding« unterstützt. Der Standard ist »One-Way-Binding«. |
CoerceValueCallback |
Beschreibt einen Delegaten auf eine Methode, die den Wert der abhängigen Eigenschaft »korrigiert«. |
DefaultValue |
Ruft den Standardwert der Eigenschaft ab oder legt ihn fest. |
Inherits |
Gibt an, ob der Wert der Abhängigkeitseigenschaft vererbbar ist, oder legt den Wert fest. |
IsAnimationProhibited |
Ist diese Eigenschaft true, kann die Abhängigkeitseigenschaft nicht in einer Animation verwendet werden. |
IsNotDataBindable |
Diese Eigenschaft wird auf true gesetzt, wenn sie nicht als Ziel einer Datenbindung verwendet werden darf. |
Journal |
In einer navigierbaren Anwendung soll der Wert der Abhängigkeitseigenschaft im Journal gespeichert werden. Damit bleibt dieser erhalten, wenn zurücknavigiert wird. |
PropertyChangedCallback |
Eignet sich zum Beispiel auch dazu, hier ein Ereignis auszulösen, das auf Clientseite behandelt werden kann. |
Mit diesen Charakteristiken könnte man sich zum Beispiel vorstellen, die Eigenschaft RadiusProperty wie folgt zu initialisieren:
static Circle() {
FrameworkPropertyMetadata meta = new FrameworkPropertyMetadata();
meta.DefaultValue = 0;
meta.AffectsRender = true;
meta.BindsTwoWayByDefault = true;
meta.PropertyChangedCallback = new PropertyChangedCallback(OnRadiusChanged);
RadiusProperty = DependencyProperty.Register("Radius",
typeof(int),
typeof(Circle),
meta);
}
Listing 26.7 Initialisieren und Registrieren von »Radius«
Die Abhängigkeitseigenschaft hat den Standardwert 0. Ferner wird im Bedarfsfall ein Neuzeichnen erzwungen und die Unterstützung des Two-Way-Bindings festgeschrieben. Ändert sich der Wert des Radius, wird die Methode OnRadiusChanged ausgeführt.
Um zumindest einen funktionsfähigen Code zu haben, sollten wir auch die Rückrufmethode OnRadiusChanged bereitstellen. Der Delegate PropertyChangedCallback schreibt vor, dass die Methode zwei Parameter haben muss: Der erste ist vom Typ DependencyObject und liefert die Referenz auf das auslösende Objekt. Der zweite Parameter ist vom Typ DependencyPropertyChangedEventArgs. Die Eigenschaften des EventArgs-Objekts liefern neben dem Bezeichner der auslösenden Eigenschaft auch deren alten und neuen Wert ab. Im nachfolgenden Code benutzen wir die Callback-Methode dazu, ein Ereignis auszulösen.
public class Circle : DependencyObject {
public event EventHandler RadiusChanged;
public static readonly DependencyProperty RadiusProperty;
static Circle() {
[...]
}
public static void OnRadiusChanged(DependencyObject sender,
DependencyPropertyChangedEventArgs e) {
Circle kreis = (Circle)sender;
if( kreis.RadiusChanged != null)
kreis.RadiusChanged(kreis, null);
}
public int Radius {
get { return (int)GetValue(RadiusProperty); }
set { SetValue(RadiusProperty, value); }
}
}
Listing 26.8 Klasse »Circle« mit Callback-Methode
Die Enumeration »FrameworkMetadataOptions«
Der Konstruktor der Klasse FrameworkPropertyMetadata ist überladen. Darunter sind auch mehrere Konstruktoren zu finden, die einen Parameter vom Typ FrameworkPropertyMetadataOptions beschreiben. Bei diesem Typ handelt es sich um eine Enumeration, deren Member sich bitweise verknüpfen lassen. Die einzelnen Mitglieder beschreiben die Eigenschaften der Klasse FrameworkPropertyMetadata, die vom Typ Boolean sind. Wird ein Member dieser Enumeration angegeben, wird das vom Compiler entsprechend als true bewertet.
Somit ließe sich die Initialisierung des Objekts RadiusProperty alternativ auch folgendermaßen umsetzen:
static Circle() {
FrameworkPropertyMetadata meta = new FrameworkPropertyMetadata(0,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault);
meta.PropertyChangedCallback = new PropertyChangedCallback(OnRadiusChanged);
RadiusProperty = DependencyProperty.Register("Radius", typeof(int),
typeof(Circle), meta);
}
Listing 26.9 Alternative Angabe der Metadaten einer Dependency Property
Visual Studio 2012 bietet ein Code-Snippet an, mit dem man sehr einfach das Grundgerüst
einer Dependency Property erstellen kann. Geben Sie dazu einfach in der Klasse propdp ein, und drücken Sie anschließend die -Taste.
26.3.4 Freigabe des spezifischen Eigenschaftswertes

In manchen Situationen kann es sinnvoll sein, den individuellen lokalen Eigenschaftswert einer Komponente zu löschen, um wieder auf den Standardwert zurückzugreifen. Die einfache Übergabe des Standardwertes an die Eigenschaft reicht dazu nicht aus. Stattdessen ruft man die Methode ClearValue auf und übergibt ihr als Argument die Abhängigkeitseigenschaft, z. B.:
Listing 26.10 Zurücksetzen der Eigenschaft auf die Standardeinstellung
26.3.5 Vererbung von Abhängigkeitseigenschaften
Auf alle Eigenschaften der Klasse FrameworkPropertyMetadata (siehe Tabelle 26.1) einzugehen, würde in diesem Buch zu weit führen. Aber lassen Sie uns einen Blick auf eine besondere Eigenschaft werfen. Eine Option ist die Einstellung Inherits. Auch wenn der Bezeichner im ersten Moment etwas anderes suggeriert, mit der aus der OOP bekannten Vererbung hat diese Einstellung nichts zu tun. Stattdessen gibt die Option Inherits an, ob der Wert der Abhängigkeitseigenschaft an die im Elementbaum untergeordneten Elemente weitergereicht wird. Ein einfaches Beispiel soll das Verhalten verdeutlichen:
<Window ...
Title="MainWindow" Height="350" Width="525" FontSize="26">
<StackPanel>
<Button>
<Label>Hallo</Label>
</Button>
</StackPanel>
</Window>
Listing 26.11 Weitervererbte Eigenschaft »FontSize«
Die Eigenschaft FontSize wird für das Window-Element festgelegt. FontSize ist eine Eigenschaft, die »weitervererbt« wird. Das hat zur Konsequenz, dass das weiter unten im Elementbaum positionierte Label den Text Hallo in der Schriftgröße 26 anzeigt.
Einige Controls werden ein davon abweichendes Verhalten zeigen. Dazu gehören unter anderem Menu, ToolTip und auch StatusBar. Der Grund ist, dass diese Komponenten die Schriftgröße intern selbst festlegen. Genau genommen beziehen diese Steuerelemente ihre Informationen aus den aktuellen Systemeinstellungen.
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.