29.3 Die Programmlogik des Steuerelements
29.3.1 Die Eigenschaften
Das Steuerelement soll insgesamt vier Eigenschaften veröffentlichen, die als Abhängigkeitseigenschaften implementiert werden sollen. In Kapitel 26 wurde beschrieben, dass dazu static readonly-Eigenschaften bereitgestellt werden müssen, die vom Typ DependencyProperty sind.
Im ersten Schritt wollen wir diese Felder in der Klasse ColorMixer codieren.
// Felder
public static readonly DependencyProperty ColorProperty;
public static readonly DependencyProperty RedProperty;
public static readonly DependencyProperty GreenProperty;
public static readonly DependencyProperty BlueProperty;
Listing 29.3 Die Abhängigkeitseigenschaften
Wir sollten auch sofort für alle vier Eigenschaften einen Wrapper bereitstellen, damit die Eigenschaften nach außen hin wie jede übliche CLR-Property auftreten.
public Color Color {
get { return (Color)GetValue(ColorProperty); }
set { SetValue(ColorProperty, value); }
}
public byte Red {
get { return (byte)GetValue(RedProperty); }
set { SetValue(RedProperty, value); }
}
public byte Green {
get { return (byte)GetValue(GreenProperty); }
set { SetValue(GreenProperty, value); }
}
public byte Blue {
get { return (byte)GetValue(BlueProperty); }
set { SetValue(BlueProperty, value); }
}
Listing 29.4 Die Eigenschaftswrapper der Klasse »ColorMixer«
Initialisiert werden die in Listung 29.3 erstellten Felder im statischen Konstruktor mit der statischen Methode Register der Klasse DependencyProperty. Dabei werden nicht nur die tatsächlich von den Eigenschaften beschriebenen Datentypen festgelegt, sondern auch die spezifischen Charakteristiken der Abhängigkeitseigenschaften. Dazu wird bei der Initialisierung ein Objekt des Typs FrameworkPropertyMetadata übergeben, in dem alle Charakteristiken beschrieben werden.
Einen Umstand können wir bei der Eigenschaftsinitialisierung sofort berücksichtigen. Ändert sich eine der drei Eigenschaften Red, Green oder Blue, ist auch die Eigenschaft Color davon betroffen und muss neu festgelegt werden. Wird andererseits die Eigenschaft Color verändert, hat das Einfluss auf die durch Red, Green und Blue beschriebenen jeweiligen Anteile.
Um die beschriebene Synchronisation der Farben zu gewährleisten, stellen wir Methoden bereit, die an den Delegaten vom Typ PropertyChangedCallback im FrameworkPropertyMetadata-Objekt gebunden werden. Die Methodenbezeichner sollen ColorPropertyChanged und RGBPropertyChanged lauten.
Damit sieht der statische Konstruktor wie folgt aus:
static ColorMixer()
{
ColorProperty = DependencyProperty.Register("Color",
typeof(Color), typeof(ColorMixer),
new FrameworkPropertyMetadata(Colors.Black,
new PropertyChangedCallback(ColorPropertyChanged)));
RedProperty = DependencyProperty.Register("Red",
typeof(byte), typeof(ColorMixer),
new FrameworkPropertyMetadata(new
PropertyChangedCallback(RGBPropertyChanged)));
GreenProperty = DependencyProperty.Register("Green",
typeof(byte), typeof(ColorMixer),
new FrameworkPropertyMetadata(new
PropertyChangedCallback(RGBPropertyChanged)));
BlueProperty = DependencyProperty.Register("Blue",
typeof(byte), typeof(ColorMixer),
new FrameworkPropertyMetadata(new
PropertyChangedCallback(RGBPropertyChanged)));
}
Listing 29.5 Der statische Konstruktor der Klasse »ColorMixer«
Jetzt müssen wir die beiden Methoden ColorPropertyChanged und RGBPropertyChanged implementieren. Widmen wir uns zuerst der Methode RGBPropertyChanged, die immer dann ausgeführt wird, wenn sich eine der Eigenschaften Red, Green oder Blue verändert. Dem EventArgs-Objekt können wir aus dessen Eigenschaft Property die Eigenschaft entnehmen, die verändert worden ist. Das ist wichtig, denn die Gesamteigenschaft Color muss dann in ihrem entsprechenden Farbanteil angepasst werden. Den neuen Wert kann man der NewValue-Eigenschaft des EventArgs-Objekts entnehmen.
private static void RGBPropertyChanged(DependencyObject sender,
DependencyPropertyChangedEventArgs e){
ColorMixer colorPicker = sender as ColorMixer;
Color color = colorPicker.Color;
if (e.Property == RedProperty)
color.R = (byte)e.NewValue;
else if (e.Property == GreenProperty)
color.G = (byte)e.NewValue;
else if (e.Property == BlueProperty)
color.B = (byte)e.NewValue;
colorPicker.Color = color;
}
Listing 29.6 Die Methode »RGBPropertyChanged«
Die vorgesehene Methode ColorPropertyChanged ist sehr ähnlich zu implementieren.
private static void ColorPropertyChanged(DependencyObject sender,
DependencyPropertyChangedEventArgs e) {
ColorMixer colorPicker = (ColorMixer)sender;
Color newColor = (Color)e.NewValue;
colorPicker.Red = newColor.R;
colorPicker.Green = newColor.G;
colorPicker.Blue = newColor.B;
}
Listing 29.7 Die Methode »ColorPropertyChanged«
29.3.2 Ein Ereignis bereitstellen
Nun möchten wir sicherlich auch die Möglichkeit eröffnen, dem Anwender durch eine Benachrichtigung mitzuteilen, dass sich der Farbwert des Steuerelements verändert hat. Dazu müssen wir einen Event in der Methode ColorPropertyChanged auslösen. Im Grunde genommen würde es ausreichen, ein herkömmliches Ereignis zu programmieren. Damit würden wir uns aber der Möglichkeit berauben, das Ereignis im Elementbaum nach oben blubbern zu lassen, um den Event an einer im Elementbaum höheren Stelle zu behandeln. Somit bleibt die Idee der Implementierung eines Routed Events, den wir ColorChanged nennen wollen.
Im ersten Schritt legen wir dazu eine statische, schreibgeschützte Variable an, die mit der Methode RegisterRoutedEvent der Klasse EventManager beim System registriert wird. Natürlich dürfen wir nicht vergessen, mit einem standardmäßigen Ereigniswrapper das Ereignis zu veröffentlichen, um das Registrieren und Deregistrieren mehrerer Ereignishandler zu ermöglichen.
public static readonly RoutedEvent ColorChangedEvent =
EventManager.RegisterRoutedEvent("ColorChanged",
RoutingStrategy.Bubble,
typeof(RoutedPropertyChangedEventHandler<Color>),
typeof(ColorMixer));
public event RoutedPropertyChangedEventHandler<Color> ColorChanged {
add { AddHandler(ColorChangedEvent, value); }
remove { RemoveHandler(ColorChangedEvent, value); }
}
Listing 29.8 Bereitstellung des Ereignisses »ColorChanged«
Der Typ des EventArgs-Parameters ist RoutePropertyChangedEventArgs<Color>, in dem sowohl der alte als auch der neue Farbwert bereitgestellt werden. Da wir darüber hinaus die Ereignisauslösung in einer separaten Methode kapseln wollen, die wir OnColorChanged nennen, bietet es sich an, den alten und neuen Wert an die kapselnde Methode zu übergeben.
public partial class ColorPicker : UserControl
{
[...]
private static void ColorPropertyChanged( ... )
{
ColorMixer colorPicker = (ColorMixer)sender;
Color newColor = (Color)e.NewValue;
colorPicker.Red = newColor.R;
colorPicker.Green = newColor.G;
colorPicker.Blue = newColor.B;
// zusätzliche Anweisungen
Color oldColor = (Color)e.OldValue;
colorPicker.OnColorChanged(oldColor, newColor);
}
private void OnColorChanged(Color oldValue, Color newValue) {
RoutedPropertyChangedEventArgs<Color> args =
new RoutedPropertyChangedEventArgs<Color>(oldValue, newValue);
args.RoutedEvent = ColorMixer.ColorChangedEvent;
RaiseEvent(args);
}
}
Listing 29.9 Ereignisauslösung in der Klasse »ColorMixer«
29.3.3 Das Steuerelement um einen »Command« ergänzen
Kommen wir nun zum letzten Schritt. Wir wollen das Steuerelement um ein Kommando ergänzen, das es uns ermöglicht, den letzten Farbwechsel wieder rückgängig zu machen. In der Klasse ApplicationCommands finden wir das dazu passend bereitgestellte Kommando Undo.
Ehe wir uns an die Programmierung machen, müssen wir erst sicherstellen, dass der alte Wert auch gespeichert wird. Dazu eignet sich ein Feld in der Klasse. Wir wollen es oldColor nennen. Es ist empfehlenswert, das Feld als Nullable-Typ zu deklarieren, damit beim Start der Anwendung von dem Feld kein Farbwert beschrieben wird.
public partial class ColorPicker : UserControl {
private Color? previousColor;
[...]
}
Listing 29.10 Ergänzung der Klasse »ColorMixer« um das Feld »oldColor«
Für die Bereitstellung des Kommandos eignet sich die Klasse CommandManager mit ihrer statischen Methode RegisterClassCommandBinding. Die Methode wird ebenfalls im statischen Konstruktor aufgerufen:
CommandManager.RegisterClassCommandBinding(typeof(ColorMixer),
new CommandBinding(ApplicationCommands.Undo,
UndoCommand_Executed,
UndoCommand_CanExecute));
Listing 29.11 Registrieren des »Command«-Objekts beim System
Zur Fertigstellung unseres Controls bleibt noch, die beiden Ereignisse CanExcecute und Execute zu implementieren.
private static void UndoCommand_CanExecute(object sender,
CanExecuteRoutedEventArgs e){
ColorMixer colorPicker = (ColorMixer)sender;
e.CanExecute = colorPicker.oldColor.HasValue;
}
private static void UndoCommand_Executed(object sender,
ExecutedRoutedEventArgs e) {
ColorMixer colorPicker = (ColorMixer)sender;
colorPicker.Color = (Color)colorPicker.oldColor;
}
Listing 29.12 Die Ereignisse »Execute« und »CanExecute«
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.