28.5 Das MVVM-Pattern
Die Oberfläche einer WPF-Anwendung wird meistens mit XAML-Code erfolgen. Da XAML reindeklarativ ist, müssen komplexere Operationen mit C# in der Code-Behind-Datei implementiert werden. Das wiederum hat zur Folge, dass zwischen der XAML-Datei und der Code-Behind-Datei eine starke Abhängigkeit herrscht.
In der Praxis wird sehr häufig versucht, Abhängigkeiten zu vermeiden. Das ist auch bei WPF-Anwendungen der Fall. Das Aufbrechen der engen Kopplung zwischen XAML- und C#-Code hat mehrere Vorteile:
- Arbeiten Grafiker und Entwickler gemeinsam an einer WPF-Anwendung, sind die Aufgabenstellungen klarer verteilt, und jeder kann mit seinem »Teil« arbeiten, ohne damit Vorgaben der anderen Seite zu verletzen.
- Die grafische Benutzeroberfläche kann ohne Probleme nicht nur geändert, sondern sogar ausgetauscht werden, ohne dass die Programmlogik angepasst werden muss.
- Andererseits kann auch die Programmlogik geändert werden, ohne dass dies eine Änderung der Oberfläche zur Konsequenz hätte.
- Ohne die Benutzeroberfläche anzeigen zu müssen, kann die Logik mit Unit-Test kontrolliert werden.
An dieser Stelle betritt das MVVM-Pattern die Bühne. MVVM ist das Kürzel für Mode View ViewModel. Durch das Pattern werden drei Blöcke beschrieben:
- das »Model«
- der »View«
- das »ViewModel«
Wir sollten uns zunächst einmal ansehen, welche Aufgabe diesen drei Teilen zukommt.
- Das Model: Dieser Block repräsentiert die Daten, mit denen in der Anwendung gearbeitet wird. Üblicherweise ist keine Logik implementiert, die die Daten weiterbehandelt. Damit ist das Model auch beispielsweise nicht dafür verantwortlich, Text so zu formatieren, dass er nett dargestellt wird. Ein Model kann beispielsweise die Daten einer Datenbank repräsentieren oder auch die Daten eines Person-Objekts beschreiben, wobei Person eine Klasse in der Anwendung ist. Es gibt nur eine Ausnahme hinsichtlich der Aussage, dass innerhalb des Models keine Programmlogik enthalten ist. Dabei handelt es sich um die Validierung.
- Der View: Dieser Block des MVVM-Patterns beschreibt das, was der Anwender sieht, also die Darstellung der Daten. Die Programmlogik ist im View nicht enthalten, dazu dient der Block ViewModel. Dennoch muss der View nicht ganz frei von C#-Code sein. Ereignisse, die im Zusammenhang mit der Darstellung stehen, werden innerhalb des Views verarbeitet. Dabei könnte es sich beispielsweise auch um Mausereignisse handeln. Diese werden in der Regel nicht im ViewModel verarbeitet.
- Das ViewModel: Zwischen die Daten, repräsentiert durch das Model, und die Darstellung der Daten im View ist das ViewModel geschaltet. Es ist also das Bindeglied zwischen View und Model und bildet somit den Kern des MVVM-Patterns, gewissermaßen den Controller. Das ViewModel kann Eingaben aus dem View entgegennehmen oder mit einem anderen Dienst interagieren, um ein Model zu erhalten, um dessen Daten an den View weiterzuleiten. Zu den Aufgaben gehört ferner, mit Methoden und auch Commands den Status des Views aktuell zu halten oder, falls sich im View Daten ändern, diese an das Model zu leiten.Das MVVM-Pattern ist kein Pattern, das einen einzigen Weg zur Separierung der Daten und der Datenansicht beschreibt. Es ist kein Gesetz, denn es gibt mehrere Wege, die eingeschlagen werden können und am Ende zum Ziel führen.Nichtsdestotrotz nehmen die Commands eine bedeutende Rolle in diesem Pattern ein. Deshalb wird das MVVM-Pattern auch in diesem Kapitel behandelt. Doch nähern wir uns dem Pattern langsam in einer sehr einfachen Anwendung.
28.5.1 Ein simples Beispielprogramm
Zunächst einmal sollten wir uns ein Model besorgen. In den folgenden Beispielen dient dazu wieder eine Klasse Person mit den beiden Eigenschaften Name und Alter.
public class Person
{
public string Name { get; set; }
public int Alter { get; set; }
public static Person CreatePerson() {
return new Person { Name = "Franz", Alter = 45 };
}
}
Listing 28.20 Das Model
In der Klasse ist auch die Methode CreatePerson definiert. Diese Methode dient uns einfach nur dazu, Daten einer Person bereitzustellen. Die Methode hat im Kontext des MVVM-Patterns keine Bedeutung.
Wie würde der View ohne Berücksichtigung des Patterns aussehen? Auch das sehen wir uns im folgenden Listing an.
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80"/><ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label>Name:</Label>
<Label Grid.Row="1">Alter</Label>
<TextBlock Grid.Column="1" Text="{Binding Name}"/>
<TextBlock Grid.Column="1" Grid.Row="1" Text="{Binding Alter}" />
</Grid>
Listing 28.21 Der View
Die Daten des Person-Objekts würden wir, mit dem bisherigen Kenntnisstand, aus einer DataContext-Bindung direkt aus dem Objekt beziehen. Also beispielsweise mit dem folgenden C#-Code in der Code-Behind-Datei unter Einbeziehung der Hilfsmethode CreatePerson:
this.DataContext = Person.CreatePerson();
Listing 28.22 Bindung an den Datenkontext des Fensters
Es ist nicht schwer zu erkennen, dass die Daten direkt an die Benutzeroberfläche gebunden werden. Zudem kränkelt das System auch daran, das eine Datenänderung des Objekts nicht an den View weitergeleitet wird, da das Interface INotifyPropertyChanged nicht implementiert ist.
Das wollen wir nun besser machen, dazu die wohl einfachste Form des MVVM-Patterns benutzen und zwischen das Model (also die Klasse Person) und den View (die Benutzeroberfläche) eine weitere Klasse schalten, die im Grunde genommen bereits als ViewModel agiert.
public class PersonViewModel : INotifyPropertyChanged
{
private Person _Model;
// Konstruktor
public PersonViewModel(Person model) {
_Model = model;
_Name = _Model.Name;
_Alter = _Model.Alter;
}
// Schnittstellen-Ereignis
public event PropertyChangedEventHandler PropertyChanged;
protected internal void OnPropertyChanged(string propertyname)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
}
// Eigenschaften
private string _Name;
public string Name
{
get { return _Name; }
set
{
if (_Name == value) return;
_Name = value;
OnPropertyChanged("Name");
}
}
private int _Alter;
public int Alter
{
get { return _Alter; }
set
{
if (_Alter == value) return;
_Alter = value;
OnPropertyChanged("Alter");
}
}
}
Listing 28.23 Das ViewModel
Das ViewModel stellt dem View eine Bindungsquelle zur Verfügung. Es kapselt das Model und implementiert die einzelnen Komponenten der Person nochmals. Darüber hinaus stellt das ViewModel auch die Schnittstelle INotifyPropertyChanged zur Verfügung, um Datenänderungen entweder an die GUI oder an die private Person-Variable _Model weiterzuleiten.
Im View wird nun eine kleine, aber entscheidende Änderung durchgeführt. Anstatt an die Daten, also das Model, direkt zu binden, erfolgt die Bindung an eine Instanz der ViewModel-Klasse. Damit das ViewModel auch entsprechende Daten zur Verfügung stellen kann, ist in der Klasse PersonViewModel ein Konstruktor definiert.
Die Anbindung an ein Objekt vom Typ des ViewModels in der Code-Behind-Datei ist sehr einfach, wie das folgende Listing zeigt.
public MainWindow()
{
InitializeComponent();
this.DataContext = new PersonViewModel(Person.CreatePerson());
}
Listing 28.24 Anbindung des Views an das ViewModel
Nun ist der View vollkommen unabhängig von den Daten und nur noch an das ViewModel gebunden. Fügen Sie in den View eine Schaltfläche ein, mit der eine Änderung der Objektdaten angestoßen wird, werden die Anzeigesteuerelemente aktualisiert.
Zugegeben, dieses Beispiel ist ausgesprochen einfach, aber es zeigt das MVVM-Pattern in seinen Grundzügen deutlich.
Sie finden dieses Beispiel auf der Buch-DVD unter ..\Beispiele\Kapitel 28\MVVMPattern\ Sample1.
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.