PPM – Schwerpunktfragen Fragebogendesigner Stefan Wurzinger Datenbindung in WPF/XAML Verwendung im Maturaprojekt: Für die Realisierung des MVVM Entwurfsmusters benötigt WPF erlaubt es vom XAML Code auf Eigenschaften des Programmcodes zu verweisen, diese also zu binden. Dabei steht es einem frei, ob man die Bindung nur einseitig – d.h. z.B. ausschließlich mit den Daten einer Eigenschaft im Code ein Textfeld zu befüllen bzw. mit Daten im Textfeld eine Eigenschaft setzen – oder beidseitig (Änderungen auf beiden Seiten haben Auswirkungen auf das jeweilige gegenüber) durchführen will. Hierbei wird der Datenkontext auf die entsprechende Klasse gesetzt, auf die sich folgend alle Datenbindungen des jeweiligen Elements und dessen untergeordnete Elemente beziehen. Da die klassischen Eigenschaften nicht mehr ausreichten, um z.B. Änderungen festzustellen und zu publizieren, wurden so genannte Abhängigkeitseigenschaften (aus dem englischen: Dependency Property)eingeführt. Dabei werden die Daten nicht mehr herkömmlich in einer privaten Variable der Klasse gespeichert, sondern in einem globalen gemeinsamen Speicherbereich verwaltet. Des Weiteren bieten sie Unterstützung für das Propagieren von Nachrichten über Änderungen der entsprechenden Variablen und können Vererbt werden. Beispiel zweier Datenbindungen: <TextBox Text="{Binding Path=Text, ValidatesOnDataErrors=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" /> Path Im Path wird der Name der Eigenschaft angegeben, die gebunden werden soll. Es kann auch zusätzlich eine oder mehrere Unter-Eigenschaften oder ein Array-Indizes angegeben werden. Mit den Eigenschaften „RelativeSource“ und „ElementName“ kann zusätzlich angegeben werden, auf welches Objekt sich die Datenbindung bezieht (standardmäßig ist dies der dem Element zugewiesene bzw. vererbte Datenkontext). Mode Bestimmt die Art der Datenbindung. Hierbei kann zwischen folgenden Arten unterschieden werden (u.a.): o OneWay Die Daten werden nur bei Änderungen im ViewModel auf der Benutzeroberfläche aktualisiert. o TwoWay Änderungen im ViewModel bewirken Änderungen auf der Benutzeroberfläche, aber auch umgekehrt. o OneWayToSource Änderungen auf der Benutzeroberfläche bewirken Änderungen im ViewModel, aber nicht umgekehrt. o OneTime Die Daten werden nur ein einziges Mal vom ViewModel abgerufen und auf der Benutzeroberfläche aktualisiert. UpdateSourceTrigger Hier kann angegeben werden, wann Änderungen einer Seite der Datenbindung an das jeweilige Gegenüber übermittelt werden sollen. Dies kann z.B. sofort bei jeder Rev. 1.0 Seite 1 von 6 23.06.2011 PPM – Schwerpunktfragen Fragebogendesigner Stefan Wurzinger Änderung, nach Verlust des Eingabefokus oder allein Programmtechnisch durch zusätzlichen Programmcode geschehen. Model-View-ViewModel Verwendung im Maturaprojekt: Grundstruktur der Anwendung MVVM bezeichnet ein für WPF angepasstes Entwurfsmuster, um eine saubere Trennung von Oberfläche und Programmcode zu gewährleisten. Es basiert großteils auf jahrelang verwendeten und gut etablierten Entwurfsmustern, wie etwa das in C++ häufig verwendete MVC Entwurfsmuster. WPF bietet nun einige erweiterte Möglichkeiten im Gegensatz zu C++, wie etwa Datenbindung, durch welche einige Aufgaben dieses Modells überflüssig bzw. umständlich werden ließen. Der Begriff MVVM ist eine Kurzbezeichnung für die einzelnen Kernkomponenten des Entwurfsmusters: Model View ViewModel DataAccess Letztere ist zwar nicht zwingend nötig (und auch nicht in der Bezeichnung enthalten), wird aber in den allermeisten Fällen eingesetzt. Ziele des MVVM Entwurfsmusters: Schließen der zwischen XAML und Programmcode entstandenen Lücke Programmcode in der Code-Behind Datei minimieren Unit-Test-taugliche Programmstruktur gewährleisten Einfach wartbare Programmstruktur gewährleisten Model Die Model Klasse abstrahiert das Datenmodell des darzustellenden Objekts. Wenn bei klassischer Programmierung die Daten jeweils als private Variable einer Klasse implementiert werden, werden diese bei MVVM in eine eigene Model Klasse ausgegliedert. Die Model Schicht stellt die Daten auf unterster Ebene dar. Das bedeutet, dass beim Einlesen eines Objektes aus z.B. einer Datenbank oder einer XML-Datei die Datenstruktur unverändert bleiben sollte. In der Model Klasse kann weiters Serialisierung implementiert werden. Da die Daten bereits in derselben Form vorliegen, wie sie aus der Datenbank bzw. XML-Datei kommen, sollten keine größeren Umwandlungen zum Serialisieren der Daten erforderlich sein. Die Model Klasse sorgt normalerweise weiters für die Überprüfung der Daten mittels Validierung. Hierzu werden die einzelnen Eigenschaften separat auf Gültigkeit überprüft. Als Beispiel sei ein Datenbankfeld erwähnt, das nicht NULL sein darf. Bei der Validierung kann nun genau diese Bedingung überprüfen. Rev. 1.0 Seite 2 von 6 23.06.2011 PPM – Schwerpunktfragen Fragebogendesigner Stefan Wurzinger DataAccess Die DataAccess Schicht des MVVM Entwurfsmusters ist grundsätzlich für die Verwaltung der Datenmodelle zuständig, wenn – wie häufig –mehrere Objekte existieren. Die DataAccess Schicht ist ebenso zum Einlesen von Datensätzen aus der Datenbank, einer XML-Datei oder einem anderen Datenspeicher zuständig, genauso wie zum Speichern der Änderungen bzw. neuen Datensätze im Datenspeicher. Dabei soll die Klasse den Datenzugriff soweit abstrahieren, dass es für die restlichen Schichten unsichtbar bleibt, ob nun eine Datenbank, eine XML-Datei oder eine interne Liste im Arbeitsspeicher als Datenspeicher dient. Die Datenmodelle werden dabei meist in einer privaten Liste zwischengespeichert. Da die Klasse für die Verwaltung der Datenmodelle zuständig ist wird sie meist „Repository“ genannt. Sie stellt Funktionen zum Hinzufügen und Löschen von Datenmodellen aus der Liste bereit. Für die übergeordneten Schichten des MVVM Entwurfsmusters wird eine Liste der Datenmodelle bereitgestellt, die zumeist eine Kopie der Intern verwendeten Liste darstellt. Die DataAccess Schicht ist kein zwingend nötiger Teil des MVVM Entwurfsmusters, wird aber zumeist verwendet. Solang z.B. nur ein Objekt eines bestimmten Datenmodells gleichzeitig bestehen kann, dann ist die DataAccess Schicht um die Verwaltungsfunktion für die Liste von Objekten ärmer, wodurch nur noch die Funktionen zum Speichern und Laden der Daten von der Datenquelle übrig bleibt. ViewModel Die ViewModel Schicht des MVVM Entwurfsmusters stellt die Daten aus der Model Klasse für die Programmoberfläche zur Verfügung. Hierbei werden die einzelnen Werte ein weiteres Mal lokal im ViewModel zwischengespeichert. Im ViewModel befinden sich zusätzlich zu den Eigenschaften, die direkt vom Model stammen, auch zumeist Hilfsvariablen für die Datenbindung an die Programmoberfläche. So kann z.B. im Modell eine Identifikationsnummer gespeichert werden und im ViewModel eine Liste mit der Übersetzung von einer Identifikationsnummer zu einer für den Benutzer verständlichen Bezeichnung. Intern wird im genannten Beispiel folglich die Identifikationsnummer verwendet während der Benutzer nur die Bezeichnungen der einzelnen Optionen zu Gesicht bekommt. Das ViewModel sollte seine Daten unabhängig vom Model zwischenspeichern. Das bedeutet, dass die im ViewModel befindlichen Daten nicht zu jedem Zeitpunkt mit denen im Model übereinstimmen müssen. Wenn die Daten nicht zwischengespeichert werden und Änderungen sofort auf die Daten im Model übernommen werden kann dies zu Problemen bei manchen Anwendungsfällen führen. Das beste Beispiel hierfür ist wahrscheinlich der „Abbrechen“ bzw. „Änderungen Verwerfen“ Schalter in einem Dialog. Dieser wird normalerweise dazu verwendet die vom Benutzer getätigten Änderungen nicht zu speichern. Wenn vom ViewModel direkt die Daten im Model geändert werden ist es nur umständlich bzw. nicht mehr möglich eine solche „Abbrechen“ Funktion zu implementieren. Wenn ein Datenobjekt untergeordnete Elemente besitzt, wird der zugehörige Datenspeicher (DataAccess Schicht; Repository) ebenfalls im ViewModel verwaltet. Dabei wird der Datenspeicher auf Änderungen überwacht und bei entsprechendem einfügen bzw. entfernen von Datenobjekten aus dem Datenspeicher können wiederum ViewModel Objekte für das jeweilige Datenmodell angelegt bzw. gelöscht werden. Rev. 1.0 Seite 3 von 6 23.06.2011 PPM – Schwerpunktfragen Fragebogendesigner Stefan Wurzinger Im ViewModel kann auch für zusätzliche Funktionen verwendet werden, wie z.B. Selektieren von bestimmten Datenobjekten. Oft wird auch ein Anzeigename definiert. Für die Programmoberfläche können auch Commands zur Verfügung gestellt werden, die wiederum mittels Datenbindung mit der Oberfläche verknüpft sind. Gängige Funktionen sind hierbei Befehle zum Speichern oder Verwerfen von Formulardaten. Das ViewModel ist auch für die Erkennung und Behandlung von Eingabefehlern zuständig. Während Formatfehler (z.B. ungültige Zeichen für ein numerisches Eingabefeld) bereits durch eine fehlgeschlagene Datenbindung erkannt werden, können inhaltliche Fehler, die aus dem Zusammenhang mehrerer Felder ausgelöst wurden, nur im ViewModel erkannt werden. Datenbindung wurde in den obigen Absätzen bereits mehrfach genannt und ist bei der Separation von ViewModel und View die grundlegend notwendige Funktion von WPF, mit der einerseits diese zwei Schichten verknüpft werden aber andererseits doch relativ unabhängig voneinander bleiben. View Die View Schicht des MVVM Entwurfsmusters stellt den dem Benutzer sichtbaren Teil des Programms dar. Hier werden die in den Schichten unterhalb aufbereiteten und gespeicherten bzw. abgerufenen Daten dem Benutzer präsentiert. Die View Schicht wird in XAML realisiert und sollte im Idealfall keine einzige Zeile manuell geschriebenen Programmcode enthalten. Oft wird diese Forderung jedoch aufgrund komplexer Situationen und umständlicher Lösungswege als Ausweg schlichtweg ignoriert. Um die Daten vom ViewModel in die View Schicht zu transferieren (und umgekehrt) wird eine mit WPF eingeführte neue Technik mit der Bezeichnung „Datenbindung“ verwendet. Dabei wird im XAML Code der View Schicht auf bestimmte Eigenschaften über dessen Namen verwiesen. Damit dies funktioniert, muss für die View Schicht der Datenkontext entsprechend auf die Instanz eines ViewModel gesetzt werden, wozu oft eine Zeile Programmcode in der View Schicht nötig ist. LINQ Verwendung im Maturaprojekt: u.a. Datenabfrage und -filterung zwischen DataAccess und ViewModel Schicht LINQ stellt SQL-ähnliche Abfragen für u.a. C#-Collections direkt in C# bereit. Neben der Verwendung für C#-Collections gibt es auch dutzende andere Zielsysteme für LINQ. Genannt seien LINQ to SQL, LINQ to XML oder LINQ to DataSets. Man kann auch selbst LINQ um Provider erweitern, wodurch z.B. Projekte wie LINQ to Google entstanden sind. private static List<string> people = new List<string>() { "Granville", "John", "Rachel", "Betty", "Chandler", "Ross", "Monica" }; public static void Example1() { Rev. 1.0 Seite 4 von 6 23.06.2011 PPM – Schwerpunktfragen Fragebogendesigner Stefan Wurzinger IEnumerable<string> query = from p in people select p; foreach (string person in query) { Console.WriteLine(person); } } Die Abfrage gibt an, welche Informationen aus der Datenquelle oder den Datenquellen abgerufen werden sollen. Optional kann eine Abfrage auch angeben, wie diese Informationen vor der Rückgabe sortiert, gruppiert und strukturiert werden sollen. Eine Abfrage wird in einer Abfragevariablen gespeichert und mit einem Abfrageausdruck initialisiert. Um das Schreiben von Abfragen zu erleichtern, hat C# eine neue Abfragesyntax eingeführt. Wie bereits erwähnt, speichert die Abfragevariable selbst nur die Abfragebefehle. Die tatsächliche Ausführung der Abfrage wird so lange verzögert, bis Sie die Abfragevariable in einer foreach-Anweisung durchlaufen. Dieses Konzept wird als verzögerte Ausführung bezeichnet. Da die Abfragevariable selbst nie die Abfrageergebnisse enthält, kann man sie beliebig oft ausführen. Wenn man beispielsweise über eine Datenbank verfügt, die ständig durch eine separate Anwendung aktualisiert wird kann man in der Anwendung eine Abfrage erstellen, die die neuesten Daten abruft, und diese Abfrage in bestimmten Abständen wiederholt ausführen, um bei jeder Ausführung andere Ergebnisse abzurufen. Um die unmittelbare Ausführung einer Abfrage zu erzwingen und ihre Ergebnisse zwischenzuspeichern, kann man die ToList<TSource>-Methode oder die ToArray<TSource>Methode aufrufen. LINQ-Abfragevariablen werden als IEnumerable<T> typisiert oder als abgeleiteter Typ, wie zum Beispiel IQueryable<T>. Oft werden auch generische Typen zur Speicherung der Abfrage verwendet: var query = from p in products where p.Name.StartsWith("A") orderby p.ID select p; foreach ( var p in query ) { Console.WriteLine ( p.Name ); } Das var-Schlüsselwort bietet sich besonders an, wenn der Variablentyp offensichtlich ist oder wenn die ausdrückliche Angabe von geschachtelten, generischen Typen, z. B. solche, die bei Gruppenabfragen erstellt werden, nicht so wichtig ist. Alternativ zur neuen C# Abfragesyntax können auch so genannte Erweiterungsmethoden mit Lambda-Ausdrücken verwendet werden um dieselbe Funktionalität zu erreichen. var query = products .Where(p => p.Name.StartsWith("A")) .OrderBy(p => p.ID); foreach ( var product in query ) { Console.WriteLine ( product.Name ); } Rev. 1.0 Seite 5 von 6 23.06.2011 PPM – Schwerpunktfragen Rev. 1.0 Fragebogendesigner Seite 6 von 6 Stefan Wurzinger 23.06.2011