Model-View-ViewModel (MVVM)

Meine neue WebSite finden Du jetzt unter https://AttilaKrick.com.

MVVM

Siehe auch: MSDN – Implementing the MVVM Pattern

MVVM (Model View ViewModel) ist ein Programm-Entwurfsmuster zur Entkoppelung von Markup und Logik der UI. Es sieht die Rollentrennung von UI-Designern (VIEW) und Entwicklern (MODEL und VIEWMODEL) vor.

Vorteile: Durch diese Trennung können die Anwendungsschichten von verschiedenen Arbeitsgruppen entwickelt werden. Designer können einen Fokus auf das Benutzererlebnis und die UI legen (VIEW) und Entwickler unabhängig davon die UI- und Geschäftslogik schreibe (MODEL und VIEWMODEL). In der Regel sind keine aufwendigen UI-Tests nötig. Stattdessen genügen codebasierte Modul-Tests des ViewModel. Auch ist eine leichtere Austauschbarkeit der View z.B. nach WPF oder Windows Phone möglich. Unterstützung in WPF, Silverlight, Windows Phone und Windows Store Apps.

Nachteil: MVVM stellt einen Mehraufwand dar und ist für Anwendungen mit einfacher UI ein Overkill. Für größere Anwendungen kann das Design eines ausreichend allgemeinen ViewModel’s im Voraus schwierig sein. Außerdem führe eine schlecht verwaltete Datenbindung zu einem erheblichen Speicherbedarf der Anwendung.

Das MVVM-Entwurfsmuster besteht aus den folgenden Schichten und Kern-Technologien:

View : XAML, UX-Logik, Bindung zum ViewModel/Model und der Datenkonvertierung über IValueConverter

ViewModel : Stellt Bindbare Propertys und Commands bereit und enthält UI-Logik. Hierfür stehen folgende Komponenten zur Verfügung:

ICommand, INotifyPropertyChanged, NotifyCollectionChanged, INotifyCollectionChanged und INotifyDataErrorInfo.

Model : Entität, Geschäftsregeln und Validierungs-Logik. Hierfür stehen folgende Komponenten zur Verfügung:

ObservableCollection<T>, INotifyPropertyChanged, INotifyCollectionChanged, und INotifyDataErrorInfo.

[Data|View|Etc.]Services : Diverse Dienste die MVVM zuarbeiten und auf verschiedenen Plattformen unterschiedlich implementiert werden. Zum Beispiel der Zugriff auf unterschiedliche Datenbank-Hersteller oder die Implementierung der MessageBox in Apps, WPF oder auf einem Windows Phone.

DataService

Die DataService-Schicht übernimmt die Aufgabe der Datenbeschaffung (GetAllKunden()) bzw. der Datenaktualisierung (UpdateKunde(kunde)).

Die DataAccess-Schicht wird Ausgeführt im Model oder im ViewModel.

In einfachen Anwendungen schließt das Model diesen Code zur Unterstützung von Datenzugriff und Caching mit ein.

Stehen unterschiedliche Daten-Anbieter (DB, XML, WCF, etc.) zur Verfügung kann die Aufgaben der Datenbereitstellung aus dieser Schicht entkoppelt und in eine eigene ProviderService-Klasse implementiert werden. Diese ProviderService’s sollten ein zuvor erstelltes Interface (z.B. IProviderService) implementieren um Provider-Unabhängig im DataService Daten beschaffen zu können:

/* ZUM BEISPIEL
DataService ds;
ds = new DataService(new WCFProviderService()); // If Online
ds = new DataService(new DBProviderService()); // else Offline
*/

public interface IProviderService {
    object GetKunden();
}

public class DBProviderService : IProviderService {
    public object GetKunden() {
        return "Geladene Kunden von der DB";
    }
}

public class WCFProviderService : IProviderService {
    public object GetKunden() {
        return "Geladene Kunden vom HTTPS-WCF-Dienst";
    }
}

public class DataService {
    public DataService(IProviderService providerService) {
        ProviderService = providerService;
    }

    IProviderService ProviderService;

    public object GetKunden() {
        var kunden = ProviderService.GetKunden();
        // Aufbereiten der Kunden
        return kunden;
    }
}

Model

Das Model ist für die Verwaltung, Konsistenz und Gültigkeit der enthaltenen Entitäten verantwortlich.

Das Model besteht aus:

Gekapselter Entität und deren Daten

Geschäftsregeln

Validierungs-Logik

Eine Anwendungs-Domäne besteht aus mehreren Model’s der Geschäftsentität, aus Auflistungen von Geschäftsentitäten oder aus Kombinationen beider.

Bei einfacher DataService-Schicht kann diese Aufgabe direkt im Model implementiert werden.

Das Model ist eine nicht-visuelle Klasse die weder von einer visuellen Klasse abgeleitet wird noch visuelle Objekte implementiert.

Das Model besitzt keine Referenz oder Abhängigkeit zum ViewModel oder zur View.

Wenn Ihre Anwendungs-Domäne aus mehreren Model’s besteht empfiehlt es sich die benötigten Interfaces in einer abstrakten Klasse ModelBase zu implementieren und von dieser zu erben.

Das Model könnte auch in einem WCF-Dienst implementiert sein was das ViewModel anschließend konsumiert.

Für Benachrichtigung über Eigenschaftsänderungen implementiert das Model INotifyPropertyChanged bzw. INotifyCollectionChanged. Diese Benachrichtigungen benötigt ViewModel/View um ihre Daten/Anzeige zu aktualisieren. Diese Benachrichtigungsdienst kann auch vom ViewModel übernommen werden.

Auflistungen von Objekten die später an die View gebunden werden sollen, werden i.d.R. von ObservableCollection<T> erstellt da diese Klasse u.a. INotifyCollectionChanged implementiert.

Validierung : Für die Datenüberprüfung und Fehlerberichterstattung wird INotifyDataErrorInfo im Model implementiert oder diese Aufgabe übernimmt auch das ViewModel.

ViewModel

Durch die Implementierung der Präsentations-Logik im ViewModel koordiniert diese die Interaktion zwischen den Model‘s und der View.

Das ViewModel besteht aus:

Durch-gereichte / Gekapselte Model‘s

Bindbare Eigenschaften für die View

Bindbare Kommandos für die View

Benachrichtigungen für die View

Gekapselte Präsentations-Logik

Ein ViewModel implementiert Model’s und wird für die Aufgabenstellung einer View entwickelt.

Während das Model System-unabhängig ist, tendiert das ViewModel zu einem konkreten System wie WPF, Silverlight, Windows Store App, etc.

Das ViewModel ist eine nicht-visuelle Klasse das weder von einer visuellen Klasse abgeleitet wird noch visuelle Objekte implementiert.

Das ViewModel besitzt keine Kenntnisse oder Referenz über die View, d.h. eine Abhängigkeit von der View darf nicht entstehen. Soweit nicht gekapselt könnte das ViewModel jedoch über das Model Kenntnisse über den DataService haben.

Das ViewModel arbeitet mit bereits initiierten Model’s oder stößt das Befüllen der Model’s mittels der DataAccess-Schicht an.

Wenn Ihre Anwendungs-Domäne aus mehreren ViewModel’s besteht empfiehlt es sich die benötigten Interfaces in einer abstrakten Klasse ViewModelBase zu implementieren und von diesen abzuleiten.

Test : Das ViewModel ist unabhängig von der View und dem Model testbar.

Die Daten die in der View präsentiert werden, können im ViewModel manipulieren und aufbereitet werden. Separate Konverter-Klassen (IValueConverter) werden unabhängig dem Binding-Objekt in der View zugewiesen und übernehmen die Aufgabe der Konvertierung in Richtung View-ViewModel und umgekehrt.

Das ViewModel implementiert Befehle die als Eigenschaft vom Typ ICommand bereitgestellt werden. Diese wiederum können später in der View gebunden werden:

<Button Content="{Binding Email}"
        Command="{Binding SendenCommand}" />

Bindung : Das ViewModel kann die Eigenschaften des Model‘s direkt der View zugänglich machen. Alternativ kapselt das ViewModel die Eigenschaften des Models, bereitet die Daten auf und stellte diese der View dann zur Verfügung. Entscheidend ist die nicht redundante Änderungsbenachrichtigung der View die entweder vom Model oder vom ViewModel kommen. Hierzu implementieren Sie im ViewModel bzw. Model INotifyPropertyChanged, INotifyCollectionChanged oder ObservableCollection<T>.

Das ViewModel definiert logische Zustände und informiert über INotifyPropertyChanged und INotifyCollectionChanged die View über Änderungen die dem Benutzer visuell dargestellt werden können.

Validierung : Für die Datenüberprüfung und Fehlerberichterstattung wird INotifyDataErrorInfo im ViewModel implementiert oder diese Aufgabe übernimmt auch das Model.

View

Die Datenbindungs-Infrastruktur ermöglicht eine lose Kopplung, bei der die View und die verknüpften Daten synchronisiert bleiben bzw. alle Benutzereingaben zu den entsprechenden Befehlen geleitet werden.

Datenbindung - Schema

Die View besteht aus:

UI-Elemente (XAML, DataTemplate’s, UserControl’s, Style‘s, Resource’s, etc.

Property-Bindungen auf Eigenschaften der ViewModel

Command-Bindungen auf Eigenschaften der ViewModel

UX (User Experience)-Logik

Die View geht eine 1:1 Beziehung mit dem entsprechenden ViewModel ein. Eine View kann sich aus mehreren Views (z.B. UserControl’s) zusammensetzen, die wiederum ihr eigenes ViewModel besitzen.

Die UI-Elemente binden ihre Eigenschaften mit einem Binding-Objekt an das dazugehörige ViewModel:

<... Text="{Binding Email}"
     Command="{Binding SpeichernCommand}" ... />

UX : In der dazugehörigen Code-Behind-Klasse liegt kein bis wenig Code der sich gekapselt nur auf die UX (User Experience)-Logik auswirkt.

Separate Konverter-Klassen die IValueConverter implementieren werden unabhängig dem Binding-Objekt in der View zugewiesen und übernehmen die Aufgabe der Konvertierung in Richtung View-ViewModel und umgekehrt.

Bindung : Für umgehende Aktualisierung der UI-Elemente ist es wichtig dass die gebunden Quelleigenschaft des Model’s bzw. des ViewModel‘s die View korrekt benachrichtigt. Dies tun sie, wenn Sie die folgenden Interfaces implementiert haben: INotifyPropertyChanged, INotifyCollectionChanged, ObservableCollection<T>, oder INotifyDataErrorInfo.

Die konkrete Umsetzung der Bindung können Sie im Kapitel Datenbindung nachvollziehen.

Benachrichtigung über Toast (Windows Store App)

Meine neue WebSite finden Du jetzt unter https://AttilaKrick.com.

Interessant: „Popupbenachrichtigungen“ auf http://goo.gl/enhq4

Ein Toast oder auch Popup ist eine Benachrichtigungs-Streifen in der rechten oberen Ecke. Toast bietet die Möglichkeit zeitkritische Informationen anzuzeigen und mit einem Tap bzw. Klick in die Anwendung zu kommen. Ein Toast ist die einzige Art mit Benutzer zu interagieren, wenn die App nicht Vordergrund ist. Diese Form eignet sich besonders gut für lokal-, zeit- und push-gesteuerte Benachrichtigung.

Beispiel-Szenarios für Tost’s könnten sein:

Eine Musik App zeigt beim Liedwechsel den neuen Titel an.

Ein Kalender zeigt eine Erinnerung für einen bevorstehenden Termin.

Die Shopping App informiert über Preisänderung eines beobachteten Artikels.

Die Messaging App zeigt eine neue eingehende Nachricht für IM, Email, SMS, VoIP, News oder der gleichen an.

Wichtig: Ein Update sollte nicht nur dann erfolgen wenn der User gerade Ihrer App nutzt sonder auch außerhalb dieser Zeit. Wenn Ihre App jedoch beendet oder suspended ist, können Sie Update nur über Hintergrundaufgaben ausführen. Werfen Sei dazu einen Blick in das Kapitel Hintergrundaufgaben

Toast sind als Einladung zusehen, um zu einem bestimmten Ziel / Zeitpunkt in der App geführt zu werden.

Vorlagen: Toast können Text, Bild und Audio-Botschaften enthalten die jedoch reglementiert sind. Eine Vorlage kann über die Methode ToastNotificationManager.GetTemplateContent bezogen werden. Diese erwartet eine Enumeration von ToastTemplateType.
Eine Vorschau aller möglichen Vorlagen finden Sie unter http://goo.gl/d5nvc sowie die Vorgaben von Maßen im XML-Schema unter http://goo.gl/6ePnJ

App-Manifest Um Toast auslösen zu können müssen Sie diese im package.appxmanifest deklariert:

<VisualElements ... ToastCapable="true">

Die Anzeigedauer eines Toast wird vom System vorgegeben und besteht aus short für eine Standard-Dauer oder long mit 25 Sekunden:

var vorlage = ToastTemplateType.ToastImageAndText02;
var toast = ToastNotificationManager.GetTemplateContent(vorlage);
var root = toast.DocumentElement;
root.SetAttribute("duration", "long");

Denken Sie daran optionale Startparameter festzulegen. Diese werden beim Klick auf die Toast-Benachrichtigung, an Ihre App übergeben.

var vorlage = ToastTemplateType.ToastImageAndText02;
var toast = ToastNotificationManager.GetTemplateContent(vorlage);
var root = toast.DocumentElement;
string launch = "{\"type\":\"sofortToast\",\"param1\":\"12345\",\"param2\":\"67890\"}";
root.SetAttribute("launch", launch);

Das optionale Bild, das Sie in einem Toast verwenden, muss 150x150px abmessen und darf nicht größer als 200 KB sein:

var vorlage = ToastTemplateType.ToastImageAndText02;
var toast = ToastNotificationManager.GetTemplateContent(vorlage);
var root = toast.DocumentElement;
var image = root.GetElementsByTagName("image");
((XmlElement)image[0]).SetAttribute("src", "ms-appx:///Buch/Beispiele/_Tile.jpg");
((XmlElement)image[0]).SetAttribute("alt", "Wasser fließt durch einen Bambus auf einen Stein");

Wann: Wenn Sie Toast’s an das System senden, so können Sie dies sofort, geplant oder geplant mit Intervall tun.

? Fangen Sie Push-Toast’s mit dem PushNotificationReceived-Ereignis ab, wenn die App ausgeführt wird.

? Eine Toast-Benachrichtigung kann auch von einer klassischen Desktop-Anwendung gesendet. Mehr dazu unter http://goo.gl/qhaHy.

? Berücksichtigen Sie das andere aktive Apps Toast-Benachrichtigungen ausblenden können.

? Dieses Thema ist stark verflochten mit Hintergrundaufgaben (s. Seite 200 ff.) da in der Regel Aktualisierungen im Hintergrund ausgeführt werden.

? Um über WNS Push-Benachrichtigungen zu senden lesen Sie bitte auch folgende Richtlinien unter http://goo.gl/6fV8Z.

Tips

Fassen Sie mehrere zusammenhängende Updates, die innerhalb kurzer Zeit anfallen, zu einem einzigen Toast zusammen.

Präsentieren Sie Informationen so einfach wie möglich.

Fügen Sie Bilder ein, wenn diese den Inhalt der Nachricht verdeutlichen, sonst besser nicht.

No Go

Verwenden Sie Toast’s nicht für wichtige Infos die nicht übersehen werden dürfen. Besser hierfür wären Flyout’s, Dialogfelder, die AppBar oder ein anderes Inline-Element.

Verzichten Sie auf Formulierungen wie „Klicken Sie hier, …“.

Verwenden Sie Toast’s nicht für sehr häufig (z.B. Aktien) anfallende Benachrichtigungen.

Verwenden Sie keine Toast’s, wenn die App im Vordergrund ist. Besser hierfür wären Flyout’s, Dialogfelder, die AppBar oder ein anderes Inline-Element.

Fügen Sie im Bildfeld einer Toast keine generischen Bilder wie Symbole oder das App-Logo ein.

Troubleshooting

Sollte Ihre Toast-Benachrichtigung nicht angezeigt werden überprüfen Sie folgende Punkte:

Überprüfen der Benutzereinstellungen

Überprüfen der Einträge im App-Manifest

Überprüfen der Bildgrößen

Untersuchen der Bildformate

Überprüfen der URLs

Überprüfen der XML-Syntax

Überprüfen der Ablaufzeit der Benachrichtigung

Lösungsansätze finden Sie auch in dem Artikel „Richtlinien und Prüfliste für Popup-Benachrichtigungen“, im Bereich „Problembehandlung“ unter http://goo.gl/NDdht

Eine einfache Toast-Nachricht anzeigen

var vorlage = ToastTemplateType.ToastText01;
var toast = ToastNotificationManager.GetTemplateContent(vorlage);

var text = toast.GetElementsByTagName("text");
text[0].AppendChild(toast.CreateTextNode("Geröstetes Brot (Toast) "
    + "wurde früher in gefüllte Trinkgläser getaucht."));

var notification = new ToastNotification(toast);
var notifier = ToastNotificationManager.CreateToastNotifier();
notifier.Show(notification);

Eine komplexe Toast-Nachricht anzeigen

var vorlage = ToastTemplateType.ToastImageAndText02;
var toast = ToastNotificationManager.GetTemplateContent(vorlage);

var root = toast.DocumentElement;
root.SetAttribute("duration", "long"); // 25 Se. Dauer

// + Parameter für den Wiedereinstieg in die App nützlich
string launch = "{\"type\":\"sofortToast\",\"param1\":\"12345\",\"param2\":\"67890\"}";
root.SetAttribute("launch", launch);

// + Text
var text = root.GetElementsByTagName("text");
text[0].AppendChild(toast.CreateTextNode("Guten Morgen"));
text[1].AppendChild(toast.CreateTextNode("Ich bin ein Toast mit einer Nachricht."));

// + Bild
var image = root.GetElementsByTagName("image");
((XmlElement)image[0]).SetAttribute("src", "ms-appx:///Buch/Beispiele/_Tile.jpg");
((XmlElement)image[0]).SetAttribute("alt", "Wasser fließt durch einen Bambus auf einen Stein");

// + Audio
var audio = toast.CreateElement("audio");
root.AppendChild(audio);
audio.SetAttribute("loop", "true");
audio.SetAttribute("src", "ms-winsoundevent:Notification.Looping.Alarm2");

var notifier = ToastNotificationManager.CreateToastNotifier();

// Sofortige Anzeige
var sofortToast = new ToastNotification(toast);
notifier.Show(sofortToast);

// ODER als geplant Anzeige
var startzeitA = DateTime.Now.AddSeconds(5);
var geplanterToast = new ScheduledToastNotification(toast, startzeitA);
notifier.AddToSchedule(geplanterToast);

// ODER als intervall Anzeigen
var startzeitB = DateTime.Now.AddSeconds(10);
var intervallToastB = new ScheduledToastNotification(
    content: toast,
    deliveryTime: startzeitB,
    snoozeInterval: new TimeSpan(0, 0, 60),
    maximumSnoozeCount: 3);
notifier.AddToSchedule(intervallToastB);

Windows Store App – Seiten-Gestaltung

Meine neue WebSite finden Du jetzt unter https://AttilaKrick.com.

Windows Store App Page Template

Windows Store App Page Template

Die Benutzeroberfläche einer Windows 8-App soll in allen Anwendungen eine
einheitliche Silhouette haben. Merkmale dieser Silhouette sind breite Ränder oben, unten und links. Durch die breiten Ränder am richtigen Rand wird dem Benutzer die horizontale bzw. vertikale Verschiebungsrichtung für den Inhalt intuitiv vorgelegt.

Durch feste Muster wird die Einheitlichkeit in allen Anwendungen gewahrt und den Benutzern das Verständnis der systemweiten Interaktionen erleichtert.

Windows 8 und sein Apps zelebrieren den Slogan clean and fluid. Zu mindestens bedeutet dass für clean, das von einem klassischen Windows-Fenster alles verband wurde was kein Inhalt darstellt und der Inhalt gleichzeitige die Interaktionsfläche für den Benutzer wird. Unterschätzen Sie jedoch nicht den Aufwand für eine funktionelle minimalistische Benutzeroberfläche.

Seitenlayout

Unter einem Seitenlayout bezeichnet man das Anpassen und Positionieren von Objekten auf der Benutzeroberfläche. Sie müssen Ihre visuellen Objekte in Container-Objekt platzieren, um diese auf der Seite zu positionieren. Zu den Container gehören u.a. Canvas, StackPanel, Grid, GridView und ListView.

Das Seitenlayout wird für Ihre Apps in verschiedenen Ansichten benötigt. Eine Ansicht ist die Art, in der sich der Inhalt im UI einpasst und wie der Benutzer auf die App zugreift z. B. im Portrait-Format oder Ihre App ist an eine Seite angedockt. Eine App die mehrere Ansichten unterstützt, kann auf Geräten verschiedener Größen und Ausrichtungen verwendet werden, und der Benutzer kann den Inhalt entsprechend seinen Anforderungen und Vorlieben bearbeiten.

Windows 8-App’s müssen als Vollbild-Anwendungen konzeptioniert werden.

Ihr Inhalt ist das primäre Element auf der Oberfläche, sekundäre Inhalt sollte nur dann auftreten wenn sie benötigt werden, daher verlagern Sie diesen z.B. in Flyouts oder in die AppBar.

Eingabefehler zeigen Sie Inline an, d.h. dort wo sie entstanden sind.

Geben Sie Ihrem Benutzer Feedback für jede Handlung die er tut, gerne auch als Animation, z.B. beim Berühren von Elemente.

Machen Sie sich Gedanken über die Fläche die der Benutzer oft berührt bzw. nicht oft berührt und legen dort berührungswürdige bzw. berührungsunwürdige Elemente ab.

Visual Studio bietet für diese Designsprache zwei Vorlagen. _Rasteranwendung zeigt Gruppen von Daten in einem Rasterformat an,
z.B. Shopping-, Nachrichten-, Foto-, Video-Apps oder sowie RSS-Reader und _Geteilte Anwendung zeigt eine Master-Detail-Liste an.

Blend for Visual Studio 2012 ist ein ideales Werkzeug zum Bearbeiten von Layout, Bild, Grafik und Animation. Natürlich können auch alle Aufgaben mit Visual Studio realisiert werden, nur müssen Sie hierfür vieles manuell in XAML erstellen.

Für Adobe Photoshop können Sie PDS-Dateien unter http://goo.gl/jVTHl herunterladen.

Rastersystem

Das hier gezeigte Rastersystem ist ein Design-Mittel, das dazu beiträgt, visuelle Einheitlichkeit für verschiedene Anwendungen und Features zu erzielen.

Visual Studio Projekt-Vorlagen aber auch die Hilfe in der MSDN beziehen sich auf dieses Rastersystem.

Anzeigen eines Ausrichtungsgitters und weitere Rastersystem-Funktionen können in der Status-Leiste des Visual Studio-Designer aktiviert werden.

Sie finden im Download-Bereich dieser App eine Handout mit den wichtigsten Maßen.

Für Adobe Photoshop können unter http://goo.gl/jVTHl können Sie Vorlagen als PSD-Dateien downloaden.

Bei einer Auflösung von 1366x768px ergibt sich folgende Rasteraufteilung:

Einheiten 20 × 20 Pixel
Untereinheiten 5 × 5 Pixel
Quadrateinheit 16 Untereinheiten

Abstände des Seitenkopfes

Vom oberen Rand 5 Einheiten
Vom linken Rand 6 Einheiten

Abstände zum Inhaltsbereich

Vom oberen Rand 7 Einheiten
Vom linken Rand 6 Einheiten
Vom unteren Rand 2,5 – 6,5 Einheiten je nachdem ob der Inhalte Horizontal
oder Vertikal verschoben wird

Vertikale Aufteilung von Elementen

Stark konturierte Elemente 2 Untereinheiten z.B. zw. Kachel
und dem zugehörigen Text
Listen Spalten-Abstand 2 Einheiten
Themen-Gruppen-Abstand 4 Einheiten

Horizontale Aufteilung von Elementen

Kachel-Textlisteneinträgen zum Nächsten 1 Einheit
Stark konturierte Objekte 2 Untereinheiten