Das Model-View-ViewModel-Muster

Hinweis

Dieses E-Book wurde im Frühjahr 2017 veröffentlicht und seitdem nicht mehr aktualisiert. Es gibt vieles im Buch, das wertvoll bleibt, aber ein Teil des Materials ist veraltet.

Die Xamarin.Forms Entwicklerumgebung umfasst in der Regel das Erstellen einer Benutzeroberfläche in XAML und das anschließende Hinzufügen von CodeBehind, der auf der Benutzeroberfläche ausgeführt wird. Wenn Apps geändert werden und an Größe und Umfang wachsen, können komplexe Wartungsprobleme auftreten. Diese Probleme schließen die enge Kopplung zwischen den UI-Steuerelementen und der Geschäftslogik ein, die die Kosten für UI-Änderungen erhöht, sowie die Schwierigkeit der Durchführung von Komponententests für solchen Code.

Das Model-View-ViewModel-Muster (MVVM) hilft dabei, die Geschäfts- und Präsentationslogik einer Anwendung von ihrer Benutzeroberfläche (UI) sauber zu trennen. Die Aufrechterhaltung eines sauber Trennung zwischen Anwendungslogik und Benutzeroberfläche trägt dazu bei, zahlreiche Entwicklungsprobleme zu beheben, und kann das Testen, Verwalten und Entwickeln einer Anwendung vereinfachen. Es kann auch die Möglichkeiten zur Wiederverwendung von Code erheblich verbessern und entwicklern und UI-Designern ermöglichen, einfacher zusammenzuarbeiten, wenn sie ihre jeweiligen Teile einer App entwickeln.

Das MVVM-Muster

Das MVVM-Muster umfasst drei Kernkomponenten: das Modell (Model), die Ansicht (View) und das Ansichtsmodell (ViewModel). Diese dienen jeweils einem bestimmten Zweck. Abbildung 2-1 zeigt die Beziehungen zwischen den drei Komponenten.

Das MVVM-Muster

Abbildung 2-1: Das MVVM-Muster

Neben dem Verständnis der Zuständigkeiten der einzelnen Komponenten ist es auch wichtig zu verstehen, wie sie miteinander interagieren. Ganz allgemein ist die Ansicht über das Ansichtsmodell informiert, und das Ansichtsmodell ist über das Modell informiert, das Modell ist allerdings nicht über das Ansichtsmodell und das Ansichtsmodell nicht über die Ansicht informiert. Das Ansichtsmodell isoliert also die Ansicht vom Modell und ermöglicht die Entwicklung des Modells unabhängig von der Ansicht.

Die Verwendung des MVVM-Musters bietet folgende Vorteile:

  • Wenn eine vorhandene Modellimplementierung vorhanden ist, die vorhandene Geschäftslogik kapselt, kann es schwierig oder riskant sein, sie zu ändern. In diesem Szenario fungiert das Ansichtsmodell als Adapter für die Modellklassen und ermöglicht es Ihnen, größere Änderungen am Modellcode zu vermeiden.
  • Entwickler können Komponententests für das Ansichtsmodell und das Modell erstellen, ohne die Ansicht zu verwenden. Durch die Komponententests für das Ansichtsmodell können genau die gleichen Funktionen ausgeführt werden, die auch von der Ansicht verwendet werden.
  • Die App-Benutzeroberfläche kann neu gestaltet werden, ohne den Code zu berühren, vorausgesetzt, die Ansicht ist vollständig in XAML implementiert. Daher sollte eine neue Version der Ansicht mit dem vorhandenen Ansichtsmodell funktionieren.
  • Designer und Entwickler können während des Entwicklungsprozesses unabhängig und gleichzeitig an ihren Komponenten arbeiten. Designer können sich auf die Ansicht konzentrieren, während Entwickler am Ansichtsmodell und an Modellkomponenten arbeiten können.

Der Schlüssel zur effektiven Verwendung von MVVM liegt darin, zu verstehen, wie App-Code in die richtigen Klassen integriert werden kann, und darin, wie die Klassen interagieren. In den folgenden Abschnitten werden die Aufgaben der einzelnen Klassen im MVVM-Muster erläutert:

Sicht

Die Ansicht dient zum Definieren der Struktur, des Layouts und der Darstellung dessen, was dem Benutzer auf dem Bildschirm angezeigt wird. Im Idealfall wird jede Ansicht in XAML definiert und verfügt über ein eingeschränktes CodeBehind, das keine Geschäftslogik enthält. Manchmal enthält das CodeBehind jedoch Benutzeroberflächenlogik, um visuelles Verhalten zu implementieren, das in XAML nur schwer auszudrücken ist (beispielsweise Animationen).

In einer Xamarin.Forms Anwendung ist eine Sicht in der Regel eine Page-abgeleitete oder ContentView-abgeleitete Klasse. Ansichten können jedoch auch durch eine Datenvorlage dargestellt werden, die die UI-Elemente angibt, mit denen ein Objekt visuell dargestellt werden soll, wenn es angezeigt wird. Eine Datenvorlage als Ansicht verfügt über kein CodeBehind und ist für die Bindung an einen bestimmten Ansichtsmodelltyp konzipiert.

Tipp

Vermeiden Sie das Aktivieren und Deaktivieren von UI-Elementen im CodeBehind. Stellen Sie sicher, dass Ansichtsmodelle für die Definition logischer Zustandsänderungen verantwortlich sind, die sich auf einige Aspekte der Anzeige der Ansicht auswirken, z. B. ob ein Befehl verfügbar ist oder ein Hinweis darauf, dass ein Vorgang aussteht. Verwenden Sie daher zum Aktivieren und Deaktivieren von UI-Elementen eine Bindung an Anzeigemodelleigenschaften, anstatt sie im CodeBehind zu aktivieren und zu deaktivieren.

Es gibt mehrere Möglichkeiten, Code für das Ansichtsmodell als Reaktion auf Interaktionen in der Ansicht (beispielsweise Klicken auf eine Schaltfläche oder Auswählen eines Elements) auszuführen. Wenn ein Steuerelement Befehle unterstützt, kann die Eigenschaft des Steuerelements Command an eine ICommand Eigenschaft im Ansichtsmodell gebunden werden. Wenn der Befehl des Steuerelements aufgerufen wird, wird der Code im Ansichtsmodell ausgeführt. Zusätzlich zu Den Befehlen können Verhaltensweisen an ein Objekt in der Ansicht angefügt werden und entweder darauf lauschen, dass ein Befehl aufgerufen oder ein Ereignis ausgelöst wird. Als Reaktion darauf kann das Verhalten dann eine ICommand für das Ansichtsmodell oder eine -Methode für das Ansichtsmodell aufrufen.

ViewModel

Das Ansichtsmodell implementiert Eigenschaften und Befehle, an die die Ansicht Daten binden kann, und informiert die Ansicht mithilfe von Änderungsbenachrichtigungsereignissen über alle Zustandsänderungen. Die durch das Ansichtsmodell bereitgestellten Eigenschaften und Befehle definieren zwar die auf der Benutzeroberfläche angebotenen Funktionen, aber die Ansicht steuert, wie diese Funktionen angezeigt werden.

Tipp

Verwenden Sie asynchrone Vorgänge, um die Reaktionsfähigkeit der Benutzeroberfläche nicht zu beeinträchtigen. Mobile Apps sollten die Blockierung des UI-Threads aufheben, um die Wahrnehmung der Leistung durch den Benutzer zu verbessern. Nutzen Sie daher im Ansichtsmodell asynchrone Methoden für E/A-Vorgänge, und lösen Sie Ereignisse aus, um Ansichten asynchron über Eigenschaftsänderungen zu informieren.

Das Ansichtsmodell ist auch für die Koordination der Interaktionen der Ansicht mit allen erforderlichen Modellklassen zuständig. Zwischen dem Ansichtsmodell und den Modellklassen besteht in der Regel eine 1:n-Beziehung. Das Ansichtsmodell kann Modellklassen direkt für die Ansicht verfügbar machen, sodass Steuerelemente in der Ansicht Daten direkt an sie binden können. In diesem Fall müssen die Modellklassen so konzipiert sein, dass sie Datenbindung und Änderungsbenachrichtigungsereignisse unterstützen.

Jedes Ansichtsmodell stellt Daten aus einem Modell in einem Format bereit, das von der Ansicht problemlos genutzt werden kann. Hierzu führt das Ansichtsmodell manchmal Datenkonvertierungen durch. Es empfiehlt sich, diese Datenkonvertierung im Ansichtsmodell zu platzieren, da sie Eigenschaften bereitstellt, die von der Ansicht zu Bindungszwecken genutzt werden können. Beispielsweise kann das Ansichtsmodell die Werte von zwei Eigenschaften kombinieren, um die Anzeige durch die Ansicht zu vereinfachen.

Tipp

Zentralisieren Sie Datenkonvertierungen auf einer Konvertierungsebene. Es ist auch möglich, Konverter als separate Datenkonvertierungsebene zwischen Ansichtsmodell und Ansicht zu verwenden. Dies kann beispielsweise erforderlich sein, wenn Daten eine spezielle Formatierung erfordern, die durch das Ansichtsmodell nicht bereitgestellt wird.

Damit das Ansichtsmodell an der bidirektionalen Datenbindung mit der Ansicht teilnehmen kann, müssen seine Eigenschaften das Ereignis PropertyChanged auslösen. Ansichtsmodelle erfüllen diese Anforderung durch Implementieren der Schnittstelle INotifyPropertyChanged sowie durch Auslösen des Ereignisses PropertyChanged, wenn eine Eigenschaft geändert wird.

Für Sammlungen wird die ansichtsfreundliche Sammlung ObservableCollection<T> bereitgestellt. Diese Sammlung implementiert Benachrichtigungen zu geänderten Sammlungen, sodass die Schnittstelle INotifyCollectionChanged bei Sammlungen nicht implementiert werden muss.

Modell

Modellklassen sind nicht visuelle Klassen, die die Daten der App kapseln. Das Modell kann somit als Darstellung des Domänenmodells der App betrachtet werden, das in der Regel ein Datenmodell zusammen mit Geschäfts- und Validierungslogik enthält. Beispiele für Modellobjekte sind Datenübertragungsobjekte (Data Transfer Objects, DTOs) und POCOs (Plain Old CLR Objects) sowie generierte Entitäts- und Proxyobjekte.

Modellklassen werden in der Regel zusammen mit Diensten oder Repositorys verwendet, die den Datenzugriff und die Zwischenspeicherung kapseln.

Verbinden von Ansichtsmodellen mit Ansichten

Ansichtsmodelle können mithilfe der Datenbindungsfunktionen von Xamarin.Formsmit Ansichten verbunden werden. Es gibt viele Ansätze, die verwendet werden können, um Ansichten und Ansichtsmodelle zu erstellen und zur Laufzeit zuzuordnen. Diese Ansätze lassen sich in zwei Kategorien unterteilen: Erstellung mit Schwerpunkt auf der Ansicht und Erstellung mit Schwerpunkt auf dem Ansichtsmodell. Die Entscheidung für die Erstellung mit Schwerpunkt auf der Ansicht oder für die Erstellung mit Schwerpunkt auf dem Ansichtsmodell hängt vom bevorzugten Ansatz und von der Komplexität ab. Alle Ansätze haben jedoch zum Ziel, der BindingContext-Eigenschaft der Ansicht ein Ansichtsmodell zuzuweisen.

Bei der Erstellung mit Schwerpunkt auf der Ansicht besteht die App konzeptionell aus Ansichten, die eine Verbindung mit den Ansichtsmodellen herstellen, von denen sie abhängen. Der Hauptvorteil dieses Ansatzes besteht in der mühelosen Erstellung lose gekoppelter, für Komponententests geeignete Apps, da die Ansichtsmodelle nicht von den Ansichten selbst abhängig sind. Außerdem lässt sich die Struktur der App ganz einfach anhand der visuellen Struktur nachvollziehen, anstatt die Codeausführung nachverfolgen zu müssen, um zu verstehen, wie Klassen erstellt und zugeordnet werden. Darüber hinaus richtet sich die erste Ansichtskonstruktion an dem Navigationssystem aus, das für das Xamarin.Forms Erstellen von Seiten zuständig ist, wenn die Navigation erfolgt, wodurch ein Ansichtsmodell zuerst komplex und falsch mit der Plattform ausgerichtet wird.

Bei der ersten Komposition des Ansichtsmodells besteht die App konzeptionell aus Ansichtsmodellen, wobei ein Dienst für die Suche nach der Ansicht für ein Ansichtsmodell verantwortlich ist. Die Erstellung mit Schwerpunkt auf dem Ansichtsmodell wird von einigen Entwicklern als natürlicher empfunden, da die Erstellung der Ansicht abstrahiert werden kann, sodass sich die Entwickler auf die logische UI-fremde Struktur der App konzentrieren können. Darüber hinaus ermöglicht sie die Erstellung von Ansichtsmodellen durch andere Ansichtsmodelle. Dieser Ansatz ist jedoch häufig komplex, und es kann schwierig werden, zu verstehen, wie die verschiedenen Teile der App erstellt und zugeordnet werden.

Tipp

Achten Sie darauf, dass Ansichtsmodelle und Ansichten unabhängig bleiben. Die Bindung von Ansichten an eine Eigenschaft in einer Datenquelle sollte die Hauptabhängigkeit der Ansicht vom entsprechenden Ansichtsmodell sein. Verweisen Sie insbesondere nicht auf Ansichtstypen wie Button und ListViewaus Ansichtsmodellen. Wenn Sie den hier beschriebenen Prinzipien folgen, können Ansichtsmodelle isoliert getestet werden, was den Umfang beschränkt und so die Wahrscheinlichkeit von Softwarefehlern verringert.

In den folgenden Abschnitten werden die wichtigsten Ansätze zum Verbinden von Ansichtsmodellen mit Ansichten erläutert.

Deklaratives Erstellen eines Ansichtsmodells

Der einfachste Ansatz besteht darin, die Ansicht ihr entsprechendes Ansichtsmodell deklarativ in XAML instanziieren zu lassen. Bei der Erstellung der Ansicht wird auch das entsprechende Ansichtsmodellobjekt erstellt. Dieser Ansatz wird im folgenden Codebeispiel veranschaulicht:

<ContentPage ... xmlns:local="clr-namespace:eShop">  
    <ContentPage.BindingContext>  
        <local:LoginViewModel />  
    </ContentPage.BindingContext>  
    ...  
</ContentPage>

Wenn ContentPage erstellt wird, wird automatisch eine Instanz von LoginViewModel erstellt und als BindingContext der Ansicht festgelegt.

Der Vorteil dieser deklarativen Erstellung und Zuweisung des Ansichtsmodells durch die Ansicht ist ihre Unkompliziertheit. Der Nachteil ist, dass im Ansichtsmodell ein Standardkonstruktor (ohne Parameter) benötigt wird.

Programmgesteuertes Erstellen eines Ansichtsmodells

Eine Ansicht kann Code in der CodeBehind-Datei enthalten, der dazu führt, dass das Ansichtsmodell seiner BindingContext Eigenschaft zugewiesen wird. Dies wird häufig im Konstruktor der Ansicht erreicht, wie im folgenden Codebeispiel zu sehen:

public LoginView()  
{  
    InitializeComponent();  
    BindingContext = new LoginViewModel(navigationService);  
}

Die programmgesteuerte Erstellung und Zuweisung des Ansichtsmodells im CodeBehind der Ansicht hat den Vorteil, dass sie unkompliziert ist. Der Hauptnachteil dieses Ansatzes besteht darin, dass die Ansicht dem Ansichtsmodell gegenüber alle erforderlichen Abhängigkeiten angeben muss. Mithilfe eines Containers für die Abhängigkeitsinjektion kann eine lose Kopplung zwischen der Ansicht und dem Ansichtsmodell aufrechterhalten werden. Weitere Informationen finden Sie unter Abhängigkeitsinjektion.

Erstellen einer als Datenvorlage definierten Ansicht

Eine Ansicht kann als Datenvorlage definiert und einem Ansichtsmodelltyp zugeordnet werden. Datenvorlagen können als Ressourcen definiert oder inline innerhalb des Steuerelements definiert werden, das das Ansichtsmodell anzeigt. Der Inhalt des Steuerelements ist das Ansichtsmodell instance, und die Datenvorlage wird verwendet, um es visuell darzustellen. Diese Technik ist ein Beispiel für eine Situation, in der das Ansichtsmodell zuerst instanziiert wird, gefolgt von der Erstellung der Ansicht.

Automatisches Erstellen eines Ansichtsmodells mit einem Ansichtsmodell-Locator

Ein Ansichtsmodelllocator ist eine benutzerdefinierte Klasse, die die Instanziierung von Ansichtsmodellen und deren Zuordnung zu Ansichten verwaltet. In der mobilen eShopOnContainers-App verfügt die ViewModelLocator Klasse über eine angefügte Eigenschaft, , die verwendet wird, AutoWireViewModelum Ansichtsmodelle Ansichten zuzuordnen. Im XAML-Code der Ansicht wird diese angefügte Eigenschaft auf true festgelegt, um anzugeben, dass das Ansichtsmodell automatisch mit der Ansicht verbunden werden soll, wie im folgenden Codebeispiel gezeigt:

viewModelBase:ViewModelLocator.AutoWireViewModel="true"

Die AutoWireViewModel Eigenschaft ist eine bindungsfähige Eigenschaft, die in false initialisiert wird, und wenn sich ihr Wert ändert, wird der OnAutoWireViewModelChanged Ereignishandler aufgerufen. Diese Methode löst das Ansichtsmodell für die Ansicht auf. Das folgende Codebeispiel zeigt, wie dies erreicht wird:

private static void OnAutoWireViewModelChanged(BindableObject bindable, object oldValue, object newValue)  
{  
    var view = bindable as Element;  
    if (view == null)  
    {  
        return;  
    }  

    var viewType = view.GetType();  
    var viewName = viewType.FullName.Replace(".Views.", ".ViewModels.");  
    var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;  
    var viewModelName = string.Format(  
        CultureInfo.InvariantCulture, "{0}Model, {1}", viewName, viewAssemblyName);  

    var viewModelType = Type.GetType(viewModelName);  
    if (viewModelType == null)  
    {  
        return;  
    }  
    var viewModel = _container.Resolve(viewModelType);  
    view.BindingContext = viewModel;  
}

Die OnAutoWireViewModelChanged -Methode versucht, das Ansichtsmodell mithilfe eines konventionsbasierten Ansatzes aufzulösen. Bei dieser Konvention wird folgendes vorausgesetzt:

  • Ansichtsmodelle befinden sich in derselben Assembly wie Ansichtstypen.
  • Ansichten befinden sich in einem . Der untergeordnete Namespace wird angezeigt.
  • Ansichtsmodelle befinden sich in einem . Untergeordneter ViewModels-Namespace.
  • Ansichtsmodellnamen entsprechen Ansichtsnamen und enden mit "ViewModel".

Schließlich legt die OnAutoWireViewModelChanged -Methode den BindingContext des Ansichtstyps auf den aufgelösten Ansichtsmodelltyp fest. Weitere Informationen zum Auflösen des Ansichtsmodelltyps finden Sie unter Auflösung.

Dieser Ansatz hat den Vorteil, dass eine App über eine einzelne Klasse verfügt, die für die Instanziierung von Ansichtsmodellen und deren Verbindung mit Ansichten verantwortlich ist.

Tipp

Verwenden Sie einen Ansichtsmodelllocator, um die Ersetzung zu erleichtern. Ein Ansichtsmodelllocator kann auch als Ersetzungspunkt für alternative Implementierungen von Abhängigkeiten verwendet werden, z. B. für Komponententests oder Entwurfszeitdaten.

Aktualisieren von Ansichten als Reaktion auf Änderungen im zugrunde liegenden Ansichtsmodell oder -modell

Alle Ansichtsmodell- und Modellklassen, auf die für eine Ansicht zugegriffen werden kann, sollten die INotifyPropertyChanged Schnittstelle implementieren. Durch die Implementierung dieser Schnittstelle in einer Ansichtsmodell- oder Modellklasse kann die Klasse Änderungsbenachrichtigungen für alle datengebundenen Steuerelemente in der Ansicht bereitstellen, wenn sich der zugrunde liegende Eigenschaftswert ändert.

Apps sollten für die richtige Verwendung von Eigenschaftsänderungsbenachrichtigungen entworfen werden, indem die folgenden Anforderungen erfüllt werden:

  • Es muss immer ein Ereignis vom Typ PropertyChanged ausgelöst werden, wenn sich der Wert einer öffentlichen Eigenschaft ändert. Gehen Sie nicht davon aus, dass das Auslösen des Ereignisses PropertyChanged ignoriert werden kann, weil bekannt ist, wie die XAML-Bindung erfolgt.
  • Es muss immer ein Ereignis vom Typ PropertyChanged für alle berechneten Eigenschaften ausgelöst werden, deren Werte von anderen Eigenschaften im (Ansichts-)Modell verwendet werden.
  • Das Ereignis PropertyChanged muss immer am Ende der Methode ausgelöst werden, die eine Eigenschaft ändert, oder wenn bekannt ist, dass sich das Objekt in einem sicheren Zustand befindet. Das Auslösen des Ereignisses unterbricht den Vorgang durch synchrones Aufrufen der Handler des Ereignisses. Passiert dies während eines laufenden Vorgangs, wird das Objekt möglicherweise für Rückruffunktionen verfügbar, wenn es sich in einem unsicheren, teilweise aktualisierten Zustand befindet. Darüber hinaus können durch Ereignisse vom Typ PropertyChanged auch kaskadierende Änderungen ausgelöst werden. Um kaskadierende Änderung sicher ausführen zu können, müssen Aktualisierungen in der Regel abgeschlossen sein.
  • Es wird niemals ein Ereignis vom Typ PropertyChanged ausgelöst, wenn sich die Eigenschaft nicht ändert. Das bedeutet, dass vor dem Auslösen des Ereignisses PropertyChanged die alten und neuen Werte verglichen werden müssen.
  • Wenn Sie eine Eigenschaft initialisieren, darf das Ereignis PropertyChanged niemals ausgelöst werden, während der Konstruktor eines Ansichtsmodells aktiv ist. Datengebundene Steuerelemente in der Ansicht haben an diesem Punkt noch keine Änderungsbenachrichtigungen abonniert.
  • Innerhalb eines einzelnen synchronen Aufrufs einer öffentlichen Methode einer Klasse werden niemals mehrere Ereignisse vom Typ PropertyChanged mit dem gleichen Eigenschaftsnamenargument ausgelöst. Ein Beispiel: Angenommen, der Sicherungsspeicher der Eigenschaft NumberOfItems ist das Feld _numberOfItems. Wenn eine Methode nun während der Ausführung einer Schleife _numberOfItems fünfzigmal erhöht, darf nur eine einzelne Eigenschaftsänderungsbenachrichtigung für die Eigenschaft NumberOfItems ausgelöst werden, nachdem alle Arbeiten abgeschlossen sind. Lösen Sie bei asynchronen Methoden das Ereignis PropertyChanged für einen bestimmten Eigenschaftsnamen in jedem synchronen Segment einer asynchronen Fortsetzungskette aus.

Die mobile eShopOnContainers-App verwendet die ExtendedBindableObject -Klasse, um Änderungsbenachrichtigungen bereitzustellen, was im folgenden Codebeispiel gezeigt wird:

public abstract class ExtendedBindableObject : BindableObject  
{  
    public void RaisePropertyChanged<T>(Expression<Func<T>> property)  
    {  
        var name = GetMemberInfo(property).Name;  
        OnPropertyChanged(name);  
    }  

    private MemberInfo GetMemberInfo(Expression expression)  
    {  
        ...  
    }  
}

Die Xamarin.Form-Klasse BindableObject implementiert die INotifyPropertyChanged -Schnittstelle und stellt eine -Methode bereit OnPropertyChanged . Die Klasse ExtendedBindableObject stellt die Methode RaisePropertyChanged zum Aufrufen von Eigenschaftsänderungsbenachrichtigungen bereit und verwendet dabei die von der Klasse BindableObject bereitgestellten Funktionen.

Jede Ansichtsmodellklasse in der mobilen eShopOnContainers-App leitet sich von der ViewModelBase -Klasse ab, die wiederum von der ExtendedBindableObject -Klasse abgeleitet wird. Daher verwendet jede Ansichtsmodellklasse die Methode RaisePropertyChanged in der Klasse ExtendedBindableObject, um Eigenschaftsänderungsbenachrichtigungen bereitzustellen. Im folgenden Codebeispiel wird veranschaulicht, wie die mobile eShopOnContainers-App eine Eigenschaftsänderungsbenachrichtigung mithilfe eines Lambdaausdrucks aufruft:

public bool IsLogin  
{  
    get  
    {  
        return _isLogin;  
    }  
    set  
    {  
        _isLogin = value;  
        RaisePropertyChanged(() => IsLogin);  
    }  
}

Beachten Sie, dass die Verwendung eines Lambdaausdrucks auf diese Weise mit geringen Leistungskosten verbunden ist, da der Lambdaausdruck für jeden Aufruf ausgewertet werden muss. Obwohl die Leistungskosten gering sind und sich normalerweise nicht auf eine App auswirken, können die Kosten anfallen, wenn viele Änderungsbenachrichtigungen vorliegen. Der Vorteil dieses Ansatzes besteht jedoch in Typsicherheit und Refactoringunterstützung zur Kompilierzeit, wenn Eigenschaften umbenannt werden.

Benutzeroberflächeninteraktion mithilfe von Befehlen und Verhaltensweisen

In mobilen Apps werden Aktionen in der Regel als Reaktion auf eine Benutzeraktion aufgerufen, z. B. ein Schaltflächenklick, die durch Erstellen eines Ereignishandlers in der CodeBehind-Datei implementiert werden können. Im MVVM-Muster ist dagegen das Ansichtsmodell für die Implementierung der Aktion zuständig, und es sollte kein Code im CodeBehind sollte platziert werden.

Befehle sind praktisch, um Aktionen darzustellen, die an Steuerelemente auf der Benutzeroberfläche gebunden werden können. Sie kapseln den Code, der die Aktion implementiert, und helfen, sie von ihrer visuellen Darstellung in der Ansicht zu entkoppeln. Xamarin.Forms enthält Steuerelemente, die deklarativ mit einem Befehl verbunden werden können, und diese Steuerelemente rufen den Befehl auf, wenn der Benutzer mit dem Steuerelement interagiert.

Auch Verhalten ermöglicht es, Steuerelemente deklarativ mit einem Befehl zu verbinden. Verhalten kann jedoch verwendet werden, um eine Aktion aufzurufen, die einem Bereich von Ereignissen zugeordnet ist, die von einem Steuerelement ausgelöst werden. Daher behandelt Verhalten großteils die gleichen Szenarien wie befehlsbasierte Steuerelemente, bietet aber ein höheres Maß an Flexibilität und Kontrolle. Darüber hinaus kann Verhalten auch verwendet werden, um Befehlsobjekte oder Methoden Steuerelementen zuzuordnen, die nicht speziell für die Interaktion mit Befehlen entwickelt wurden.

Implementieren von Befehlen

Ansichtsmodelle machen in der Regel Befehlseigenschaften für die Bindung aus der Ansicht verfügbar, d. h. Objektinstanzen, die die ICommand Schnittstelle implementieren. Eine Reihe von Steuerelementen Xamarin.Forms stellt eine Command Eigenschaft bereit, bei der es sich um Daten handeln kann, die an ein vom Ansichtsmodell bereitgestelltes ICommand Objekt gebunden sind. Die Schnittstelle ICommand definiert eine Methode vom Typ Execute, die den eigentlichen Vorgang kapselt, eine Methode vom Typ CanExecute, die angibt, ob der Befehl aufgerufen werden kann, und ein Ereignis vom Typ CanExecuteChanged, das auftritt, wenn Änderungen vorgenommen werden, die beeinflussen, ob der Befehl ausgeführt werden soll. Die Command von bereitgestellten Xamarin.FormsKlassen und Command<T> implementieren die ICommand -Schnittstelle, wobei T der Typ der Argumente für Execute und CanExecuteist.

Innerhalb eines Ansichtsmodells sollte ein Objekt vom Typ Command oder Command<T> für jede öffentliche Eigenschaft im Ansichtsmodell des Typs vorhanden ICommandsein. Der Command Konstruktor oder Command<T> erfordert ein Action Rückrufobjekt, das aufgerufen wird, wenn die ICommand.Execute -Methode aufgerufen wird. Die CanExecute -Methode ist ein optionaler Konstruktorparameter und gibt einen Funcboolzurück.

Der folgende Code zeigt, wie ein Command instance, der einen Registerbefehl darstellt, erstellt wird, indem ein Delegat für die Register Ansichtsmodellmethode angegeben wird:

public ICommand RegisterCommand => new Command(Register);

Der Befehl wird für die Ansicht über eine Eigenschaft verfügbar gemacht, die einen Verweis auf einen Befehl (ICommand) zurückgibt. Wenn die Methode Execute für das Objekt Command aufgerufen wird, leitet sie den Aufruf einfach über den im Konstruktor Command angegebenen Delegaten an die Methode im Ansichtsmodell weiter.

Eine asynchrone Methode kann von einem Befehl aufgerufen werden, indem die async Schlüsselwörter und await beim Angeben des Delegaten des Execute Befehls verwendet werden. Dadurch wird angegeben, dass es sich bei dem Rückruf um eine Aufgabe (Task) handelt und dass auf den Rückruf gewartet werden muss. Der folgende Code zeigt beispielsweise, wie ein Command instance, der einen Anmeldebefehl darstellt, erstellt wird, indem ein Delegat für die SignInAsync Ansichtsmodellmethode angegeben wird:

public ICommand SignInCommand => new Command(async () => await SignInAsync());

Parameter können an die Aktionen Execute und CanExecute übergeben werden, indem die Klasse Command<T> zum Instanziieren des Befehls verwendet wird. Der folgende Code zeigt beispielsweise, wie ein Command<T> instance verwendet wird, um anzugeben, dass die NavigateAsync Methode ein Argument vom Typ stringerfordert:

public ICommand NavigateCommand => new Command<string>(NavigateAsync);

Sowohl in der Klasse Command als auch in der Klasse Command<T> ist der Delegat für die Methode CanExecute in den einzelnen Konstruktoren optional. Wenn kein Delegat angegeben wird, wird für CommandCanExecutezurückgegebentrue. Das Ansichtsmodell kann jedoch eine Statusänderung für den Befehl CanExecute angeben, indem die Methode ChangeCanExecute für das Objekt Command aufgerufen wird. Dadurch wird das Ereignis CanExecuteChanged ausgelöst. Alle Steuerelemente auf der Benutzeroberfläche, die an den Befehl gebunden sind, aktualisieren dann ihre aktivierten status, um die Verfügbarkeit des datengebundenen Befehls widerzuspiegeln.

Aufrufen von Befehlen aus einer Ansicht

Das folgende Codebeispiel zeigt, wie ein Raster (Grid) in der Anmeldeansicht (LoginView) mithilfe einer TapGestureRecognizer-Instanz an den Registrierungsbefehl (RegisterCommand) in der Klasse LoginViewModel gebunden wird:

<Grid Grid.Column="1" HorizontalOptions="Center">  
    <Label Text="REGISTER" TextColor="Gray"/>  
    <Grid.GestureRecognizers>  
        <TapGestureRecognizer Command="{Binding RegisterCommand}" NumberOfTapsRequired="1" />  
    </Grid.GestureRecognizers>  
</Grid>

Ein Befehlsparameter kann auch optional mithilfe der Eigenschaft CommandParameter definiert werden. Die Art des erwarteten Arguments wird in den Zielmethoden Execute und CanExecute angegeben. TapGestureRecognizer ruft automatisch den Zielbefehl auf, wenn der Benutzer mit dem angefügten Steuerelement interagiert. Falls angegeben, wird der Befehlsparameter als Argument an den Delegaten des Execute Befehls übergeben.

Implementieren von Verhaltensweisen

Verhalten ermöglicht das Hinzufügen von Funktionen zu Steuerelementen der Benutzeroberfläche, ohne Unterklassen verwenden zu müssen. Stattdessen wird die Funktion in einer Verhaltensklasse implementiert und an das Steuerelement angefügt, als wäre sie ein Teil des Steuerelements selbst. Verhaltensweisen ermöglichen es Ihnen, Code zu implementieren, den Sie normalerweise als CodeBehind schreiben müssten, da es direkt mit der API des Steuerelements interagiert, sodass es präzise an das Steuerelement angefügt und für die Wiederverwendung in mehreren Ansichten oder Apps gepackt werden kann. Im Kontext von MVVM ist Verhalten nützlich, um Steuerelemente mit Befehlen zu verbinden.

Ein Verhalten, das über angefügte Eigenschaften an ein Steuerelement angefügt wird, wird als angefügtes Verhalten bezeichnet. Das Verhalten kann dann die verfügbar gemachte API des Elements verwenden, an das es angefügt ist, um diesem Steuerelement (oder anderen Steuerelementen) in der visuellen Struktur der Ansicht Funktionen hinzuzufügen. Die mobile eShopOnContainers-App enthält die LineColorBehavior -Klasse, bei der es sich um ein angefügtes Verhalten handelt. Weitere Informationen zu diesem Verhalten finden Sie unter Anzeigen von Validierungsfehlern.

Ein Xamarin.Forms Verhalten ist eine Klasse, die von der Behavior - oder Behavior<T> -Klasse abgeleitet wird. Dabei T handelt es sich um den Typ des Steuerelements, auf das das Verhalten angewendet werden soll. Diese Klassen stellen Methoden vom Typ OnAttachedTo und OnDetachingFrom bereit, die überschrieben werden müssen, um Logik bereitzustellen, die ausgeführt wird, wenn das Verhalten an Steuerelemente angefügt bzw. von ihnen getrennt wird.

In der mobilen eShopOnContainers-App leitet sich die BindableBehavior<T> -Klasse von der Behavior<T> -Klasse ab. Der Zweck der BindableBehavior<T> -Klasse besteht darin, eine Basisklasse für Xamarin.Forms Verhaltensweisen bereitzustellen, für die das BindingContext verhalten auf das angefügte Steuerelement festgelegt werden muss.

Die Klasse BindableBehavior<T> stellt eine überschreibbare Methode vom Typ OnAttachedTo bereit, die den Bindungskontext (BindingContext) des Verhaltens festlegt, sowie eine überschreibbare Methode vom Typ OnDetachingFrom, die den Bindungskontext (BindingContext) bereinigt. Darüber hinaus speichert die Klasse einen Verweis auf das angefügte Steuerelement in der AssociatedObject-Eigenschaft.

Die mobile eShopOnContainers-App enthält eine EventToCommandBehavior Klasse, die einen Befehl als Reaktion auf ein Ereignis ausführt. Diese Klasse wird von der Klasse BindableBehavior<T> abgeleitet, sodass das Verhalten an eine durch eine Command-Eigenschaft angegebene ICommand-Instanz gebunden werden und diese ausführen kann, wenn das Verhalten genutzt wird. Das folgende Codebeispiel zeigt die EventToCommandBehavior-Klasse:

public class EventToCommandBehavior : BindableBehavior<View>  
{  
    ...  
    protected override void OnAttachedTo(View visualElement)  
    {  
        base.OnAttachedTo(visualElement);  

        var events = AssociatedObject.GetType().GetRuntimeEvents().ToArray();  
        if (events.Any())  
        {  
            _eventInfo = events.FirstOrDefault(e => e.Name == EventName);  
            if (_eventInfo == null)  
                throw new ArgumentException(string.Format(  
                        "EventToCommand: Can't find any event named '{0}' on attached type",   
                        EventName));  

            AddEventHandler(_eventInfo, AssociatedObject, OnFired);  
        }  
    }  

    protected override void OnDetachingFrom(View view)  
    {  
        if (_handler != null)  
            _eventInfo.RemoveEventHandler(AssociatedObject, _handler);  

        base.OnDetachingFrom(view);  
    }  

    private void AddEventHandler(  
            EventInfo eventInfo, object item, Action<object, EventArgs> action)  
    {  
        ...  
    }  

    private void OnFired(object sender, EventArgs eventArgs)  
    {  
        ...  
    }  
}

Die Methoden OnAttachedTo und OnDetachingFrom werden verwendet, um einen Ereignishandler für das in der Eigenschaft EventName definierte Ereignis zu registrieren bzw. seine Registrierung aufzuheben. Wenn das Ereignis ausgelöst wird, wird die Methode OnFired aufgerufen, wodurch wiederum der Befehl ausgeführt wird.

Die Verwendung von EventToCommandBehavior zum Ausführen eines Befehls, wenn ein Ereignis ausgelöst wird, hat den Vorteil, dass Befehle mit Steuerelementen verknüpft werden können, die nicht für die Interaktion mit Befehlen konzipiert wurden. Außerdem wird der Code für die Ereignisbehandlung dadurch in Ansichtsmodelle verlagert, wo Komponententests für ihn ausgeführt werden können.

Aufrufen von Verhaltensweisen aus einer Ansicht

Der EventToCommandBehavior ist besonders nützlich, um einen Befehl an ein Steuerelement anzufügen, das keine Befehle unterstützt. Der verwendet beispielsweise denEventToCommandBehavior, um den OrderDetailCommand auszuführen, ProfileView wenn das Ereignis auf dem ItemTappedListView ausgelöst wird, das die Bestellungen des Benutzers auflistet, wie im folgenden Code gezeigt:

<ListView>  
    <ListView.Behaviors>  
        <behaviors:EventToCommandBehavior             
            EventName="ItemTapped"  
            Command="{Binding OrderDetailCommand}"  
            EventArgsConverter="{StaticResource ItemTappedEventArgsConverter}" />  
    </ListView.Behaviors>  
    ...  
</ListView>

Zur Laufzeit reagiert EventToCommandBehavior auf die Interaktion mit der Eingabe (ListView). Wenn ein Element in ListViewausgewählt ist, wird das ItemTapped -Ereignis ausgelöst, das in OrderDetailCommandProfileViewModelausgeführt wird. Standardmäßig werden die Ereignisargumente für das Ereignis an den Befehl übergeben. Diese Daten werden konvertiert, während sie von dem in der EventArgsConverter -Eigenschaft angegebenen Konverter zwischen Quelle und Ziel übergeben werden.ItemListViewItemTappedEventArgs Daher wird beim Ausführen von das OrderDetailCommand ausgewählte Order als Parameter an die registrierte Aktion übergeben.

Weitere Informationen zu Verhaltensweisen finden Sie unter Verhalten.

Zusammenfassung

Das Model-ViewModel-Muster (MVVM) hilft dabei, die Geschäfts- und Präsentationslogik einer Anwendung von ihrer Benutzeroberfläche (UI) sauber zu trennen. Das Beibehalten einer sauber Trennung zwischen Anwendungslogik und Benutzeroberfläche trägt dazu bei, zahlreiche Entwicklungsprobleme zu beheben, und kann das Testen, Verwalten und Entwickeln einer Anwendung erleichtern. Es kann auch die Möglichkeiten zur Wiederverwendung von Code erheblich verbessern und ermöglicht Es Entwicklern und UI-Designern, einfacher zusammenzuarbeiten, wenn sie ihre jeweiligen Teile einer App entwickeln.

Mithilfe des MVVM-Musters wird die Benutzeroberfläche der App und die zugrunde liegende Präsentations- und Geschäftslogik in drei separate Klassen unterteilt: die Ansicht, die die Benutzeroberfläche und die Ui-Logik kapselt; das Ansichtsmodell, das die Darstellungslogik und den Zustand kapselt; und das Modell, das die Geschäftslogik und -daten der App kapselt.