Silverlight 5 in der Praxis

Seite 2: MVVM-Pattern & andere Tricks

Inhaltsverzeichnis

Die Gestaltung der Oberfläche einer Silverlight- oder WPF-Anwendung erfolgt in der Regel mithilfe von XAML-Code (eXtensible Application Markup Language). Dieser XML-Code ist rein deklarativ und lässt keine Programmierung von Programmabläufen zu. Die Implementierung der Logik erfolgt in einer Programmiersprache wie C# oder VB.NET. Gelingt es, Abhängigkeiten zwischen XAML- und C#-Code zu vermeiden oder zu minimieren, dann ergeben sich daraus einige Vorteile:

  • Austauschbarkeit und nachträgliche Änderungsmöglichkeit der Oberfläche, ohne dass die Programmlogik angepasst werden muss
  • Änderungsmöglichkeit der Programmlogik ohne Einfluss auf die Oberflächengestaltung
  • Klare Aufgabenteilung zwischen Designer (Grafiker) und Programmierer
  • Kontrolle der Bedienlogik durch Unit-Tests, ohne dass die Oberfläche angezeigt werden muss

Allerdings bekommt man die strikte Aufteilung nicht geschenkt, sondern muss bei der Programmierung einiges berücksichtigen. Als Bindeglied zwischen einer Ansicht (View) und der Geschäftslogik (Model) ist eine zusätzliche Klasse, das ViewModel, erforderlich. Es bedient auf der einen Seite die Geschäftslogik, ruft also Methoden oder Dienste auf, um Informationen zu erhalten oder weiterzureichen, und stellt auf der anderen Seite öffentliche Eigenschaften bereit, die leicht im XAML-Code mittels Datenbindungen genutzt werden können. Das ViewModel selbst darf keinerlei Kenntnis von spezifischen Controls der Oberfläche haben, um universell einsetzbar zu bleiben. Wenn der Designer sich später dazu entschließt, das Datagrid der Firma X durch das der Firma Y zu ersetzen, ist das für das ViewModel dann ohne Belang.

Benutzeraktionen, die von der View ausgehen, können nicht direkt über Eventhandler abgewickelt werden, da diese im Code-Behind der Fensterklasse programmiert werden müssten und so keine Trennung möglich wäre. Es stehen jedoch verschiedene Workarounds zur Verfügung, um beliebige Ereignisse der Oberfläche an das ViewModel weiterzuleiten. Oft reicht eine bidirektionale Datenbindung aus, um auf Eingaben in Textboxen oder Auswahländerungen in Listboxen oder Datagrids zu reagieren. Steuerelemente wie Buttons verfügen zudem über eine ebenfalls bindbare Command-Eigenschaft, bei der die Abarbeitung eines Command.Execute()-Befehls (die Schaltfläche wurde gedrückt, die Key-Gesture eingegeben und so weiter …) im ViewModel erfolgen kann. In Abbildung 5 sind die Zusammenhänge verdeutlicht.

Das MVVM-Pattern definiert die klare Trennung zwischen der Darstellung und der Logik. (Abb. 5)

Im Internet finden sich einige Frameworks, die den Umgang mit MVVM unterstützen. Meist lassen sich die benötigten Funktionen jedoch schon mit wenigen Hilfsmitteln realisieren. Hier einige der typischen Kandidaten:

NotifyPropertyChanged: Datenbindungen in Silverlight und WPF berücksichtigen die Implementierung der Schnittstelle INotifyPropertyChanged. Zur Vereinfachung der Auslösung des PropertyChanged-Events wird man sich meist eine Basisklasse erstellen:

public class NotifyPropertyChanged : INotifyPropertyChanged
{
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

public event PropertyChangedEventHandler PropertyChanged;
}

Es gibt verschiedene Implementierungsmöglichkeiten für diese Klasse. Beispielsweise kann es zur Fehlererkennung sinnvoll sein, zu prüfen, ob der übergebene Eigenschaftsname tatsächlich existiert. Auch eine Umsetzung, bei der statt eines Strings ein Lambda-Ausdruck die Eigenschaft beschreibt, ist möglich. Das vermeidet Fehler, wenn die betreffende Eigenschaft später umbenannt werden sollte.

Item: Diese Klasse stellt einfache Daten, Tooltip-Informationen sowie Verfügbarkeit bereit:

public class Item : NotifyPropertyChanged
{
private object value;

public object Value
{
get { return this.value; }
set { this.value = value; OnPropertyChanged("Value"); }
}

public object Tooltip {…}
public bool IsEnabled {…}
protected virtual void OnIsEnabledChanged() {}
}

ActionCommand: In der Literatur auch DelegateCommand oder RelayCommand genannt. Diese Klasse kapselt die für ein Command-Objekt notwendigen Informationen und stellt die Verbindung zwischen der Command-Eigenschaft eines Steuerelements und einer auszuführenden Methode im ViewModel her:

public class ActionCommand : Item, ICommand
{
private Action action;

// action:
// Delegate, das bei Command.Execute() ausgeführt werden soll
public ActionCommand(Action action)
{
this.action = action;
}

protected override void OnIsEnabledChanged()
{
if (CanExecuteChanged != null)
CanExecuteChanged(this, EventArgs.Empty);
}

public bool CanExecute(object parameter)
{
return this.IsEnabled;
}

public event EventHandler CanExecuteChanged;

public void Execute(object parameter)
{
if (action != null)
action();
}
}

Die Verfügbarkeit einer Anweisung lässt sich einfach über die boolsche Eigenschaft IsEnabled steuern. Gebundene Controls werden hierbei automatisch ihren Zustand anpassen.

SelectableItem: Eine weitere Hilfsklasse, abgeleitet von Item, die zusätzlich über die Eigenschaft IsSelected verfügt. Sie ist beim Einsatz in Auflistungen oder Baumstrukturen hilfreich, eine Benutzerauswahl im ViewModel sichtbar zu machen.

ViewModelBase: In dieser Klasse lassen sich die Gemeinsamkeiten der verschiedenen ViewModels zusammenfassen. Typischerweise ist das der Zugriff auf das Model oder bei Bedarf auch auf ein übergeordnetes ViewModel. Im Beispiel wird ferner über IsValid bekannt gemacht, ob die Benutzereingaben vollständig sind und somit zur nächsten Ansicht gewechselt werden kann:

public class ViewModelBase : NotifyPropertyChanged
{
// Webservice der Geschäftslogik
protected WWWingsBuchungsserviceClient wwwingsBuchungsserviceProxy = …

public bool IsValid …

public virtual void Reset()
{
IsValid = false;
}
}