Das Model-View-ViewModel-Muster
Hinweis
Dieses eBook wurde im Frühjahr 2017 veröffentlicht und wurde seitdem nicht aktualisiert. Es gibt viel in dem Buch, das wertvoll bleibt, aber einige der Materialien sind veraltet.
Die Xamarin.Forms Entwicklerumgebung umfasst in der Regel das Erstellen einer Benutzeroberfläche in XAML und das anschließende Hinzufügen von CodeBehind, das auf der Benutzeroberfläche ausgeführt wird. Da Apps geändert werden und in 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 (MVVM)-Muster hilft dabei, die Geschäfts- und Präsentationslogik einer Anwendung von der Benutzeroberfläche (UI) sauber zu trennen. Die Aufrechterhaltung einer sauberen Trennung zwischen Anwendungslogik und Benutzeroberfläche trägt dazu bei, zahlreiche Entwicklungsprobleme zu beheben und eine Anwendung einfacher zu testen, zu verwalten und zu entwickeln. Es kann auch die Wiederverwendungsmöglichkeiten von Code erheblich verbessern und Entwicklern und UI-Designern ermöglichen, bei der Entwicklung ihrer jeweiligen Teile einer App einfacher zusammenzuarbeiten.
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.
Abbildung 2-1: Das MVVM-Muster
Neben dem Verständnis der Verantwortlichkeiten jeder Komponente 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, wichtige Ä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-UI kann neu gestaltet werden, ohne den Code zu berühren, vorausgesetzt, die Ansicht wird 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 aufgeteilt und 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 Ansicht 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 Ansichtsanzeige auswirken, z. B. ob ein Befehl verfügbar ist, oder einen 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 Befehlen können Verhaltensweisen an ein Objekt in der Ansicht angefügt werden und können entweder auf einen Befehl lauschen, der aufgerufen oder ereignis ausgelöst wird. Als Reaktion kann das Verhalten dann ein ICommand
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 den UI-Thread entsperren lassen, um die Wahrnehmung der Leistung des Benutzers 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 mit Xamarin.FormsAnsichten 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 Konstruktion an das Xamarin.Forms Navigationssystem, das für das Erstellen von Seiten verantwortlich ist, wenn die Navigation erfolgt, wodurch ein Ansichtsmodell zuerst eine komplexe Komposition erstellt und mit der Plattform falsch 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 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, z Button
. B. und ListView
aus 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 haben, die dazu führt, dass dem Ansichtsmodell seine 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 Dependency Injection.
Erstellen einer als Datenvorlage definierten Ansicht
Eine Ansicht kann als Datenvorlage definiert und einem Ansichtsmodelltyp zugeordnet werden. Datenvorlagen können als Ressourcen definiert werden, oder sie können inline innerhalb des Steuerelements definiert werden, das das Ansichtsmodell anzeigt. Der Inhalt des Steuerelements ist die Ansichtsmodellinstanz, 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 Ansichtsmodell-Locator 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, AutoWireViewModel
die zum Zuordnen von Ansichtsmodellen zu Ansichten verwendet wird. 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 auf "false" initialisiert wird, und wenn der Wert den OnAutoWireViewModelChanged
Ereignishandler ändert, aufgerufen wird. Mit dieser Methode wird das Ansichtsmodell für die Ansicht aufgelöst. 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 davon ausgegangen, dass:
- Ansichtsmodelle befinden sich in derselben Assembly wie Ansichtstypen.
- Ansichten befinden sich in einem . Zeigt den untergeordneten Namespace an.
- Ansichtsmodelle befinden sich in einem . ViewModels untergeordneter Namespace.
- Ansichtsmodellnamen entsprechen Ansichtsnamen und enden mit "ViewModel".
Schließlich legt die OnAutoWireViewModelChanged
Methode den BindingContext
Ansichtstyp 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 Ansichtsmodell-Locator, um die Ersetzung zu erleichtern. Ein Ansichtsmodell-Locator kann auch als Ersatz 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 eine Ansicht Zugriff hat, müssen die Schnittstelle INotifyPropertyChanged
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 korrekte Verwendung von Eigenschaftsänderungsbenachrichtigungen entwickelt werden, indem sie die folgenden Anforderungen erfüllen:
- 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 EreignissesPropertyChanged
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 TypPropertyChanged
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 EreignissesPropertyChanged
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 EigenschaftNumberOfItems
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 EigenschaftNumberOfItems
ausgelöst werden, nachdem alle Arbeiten abgeschlossen sind. Lösen Sie bei asynchronen Methoden das EreignisPropertyChanged
für einen bestimmten Eigenschaftsnamen in jedem synchronen Segment einer asynchronen Fortsetzungskette aus.
Die mobile eShopOnContainers-App verwendet die ExtendedBindableObject
Klasse, um Änderungsbenachrichtigungen bereitzustellen, die 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 OnPropertyChanged
Methode bereit. 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 wird von der ViewModelBase
Klasse abgeleitet, die wiederum von der ExtendedBindableObject
Klasse abgeleitet wird. Daher verwendet jede Ansichtsmodellklasse die Methode RaisePropertyChanged
in der Klasse ExtendedBindableObject
, um Eigenschaftsänderungsbenachrichtigungen bereitzustellen. Das folgende Codebeispiel zeigt, wie die mobile eShopOnContainers-App mithilfe eines Lambda-Ausdrucks Eigenschaftsänderungsbenachrichtigungen aufruft:The following code example shows how the eShopOnContainers mobile app invokes property change notification by using a lambda expression:
public bool IsLogin
{
get
{
return _isLogin;
}
set
{
_isLogin = value;
RaisePropertyChanged(() => IsLogin);
}
}
Beachten Sie, dass die Verwendung eines Lambda-Ausdrucks auf diese Weise eine geringe Leistungskosten erfordert, da der Lambda-Ausdruck für jeden Aufruf ausgewertet werden muss. Obwohl die Leistungskosten klein sind und sich normalerweise nicht auf eine App auswirken würden, können die Kosten anfallen, wenn viele Änderungsbenachrichtigungen vorhanden sind. Der Vorteil dieses Ansatzes besteht jedoch in Typsicherheit und Refactoringunterstützung zur Kompilierzeit, wenn Eigenschaften umbenannt werden.
Ui-Interaktion mit 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 hilft, ihn von seiner 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, bei denen es sich um Objektinstanzen handelt, die die ICommand
Schnittstelle implementieren. Eine Reihe von Xamarin.Forms Steuerelementen stellt eine Command
Eigenschaft bereit, die an ein ICommand
objekt gebunden sein kann, das vom Ansichtsmodell bereitgestellt wird. 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 Command<T>
ihnen bereitgestellten Xamarin.FormsKlassen implementieren die ICommand
Schnittstelle. T
Dabei handelt es sich um den Typ der Argumente für Execute
und CanExecute
.
Innerhalb eines Ansichtsmodells sollte ein Objekt vom Typ Command
oder Command<T>
für jede öffentliche Eigenschaft im Ansichtsmodell des Typs ICommand
vorhanden sein. Command<T>
Der Command
Konstruktor 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 Func
bool
.
Der folgende Code zeigt, wie eine Command
Instanz, die einen Registerbefehl darstellt, durch Angeben eines Delegaten an die Register
Ansichtsmodellmethode erstellt 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 mithilfe der async
await
Schlüsselwörter aufgerufen werden, wenn sie den Delegaten des Execute
Befehls angeben. 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 eine Command
Instanz, die einen Anmeldebefehl darstellt, erstellt wird, indem sie einen Delegaten an die SignInAsync
Ansichtsmodellmethode angibt:
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 eine Command<T>
Instanz verwendet wird, um anzugeben, dass für die NavigateAsync
Methode ein Argument vom Typ string
erforderlich ist:
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 keine Stellvertretung angegeben ist, wird die Command
Rückgabe true
für CanExecute
. 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 in der Benutzeroberfläche, die an den Befehl gebunden sind, aktualisieren dann ihren 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. Wenn 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. Mit Verhalten können Sie Code implementieren, den Sie normalerweise als CodeBehind schreiben müssen, da sie direkt mit der API des Steuerelements interagiert, so dass sie präzise an das Steuerelement angefügt und für die Wiederverwendung in mehreren Ansichten oder Apps verpackt 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 Klasse, bei der LineColorBehavior
es sich um ein angefügtes Verhalten handelt. Weitere Informationen zu diesem Verhalten finden Sie unter Anzeigen von Überprüfungsfehlern.
Ein Xamarin.Forms Verhalten ist eine Klasse, die von der Behavior
Klasse Behavior<T>
abgeleitet ist, wobei T
es sich um den Typ des Steuerelements handelt, 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 wird die BindableBehavior<T>
Klasse von der Behavior<T>
Klasse abgeleitet. 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
EventToCommandBehavior
ist besonders nützlich, um einen Befehl an ein Steuerelement anzufügen, das keine Befehle unterstützt. Beispielsweise wird die ProfileView
EventToCommandBehavior
Ausführung OrderDetailCommand
verwendet, wenn das ItemTapped
Ereignis in der ListView
Liste der Bestellungen des Benutzers ausgelöst wird, wie im folgenden Code dargestellt:
<ListView>
<ListView.Behaviors>
<behaviors:EventToCommandBehavior
EventName="ItemTapped"
Command="{Binding OrderDetailCommand}"
EventArgsConverter="{StaticResource ItemTappedEventArgsConverter}" />
</ListView.Behaviors>
...
</ListView>
Zur Laufzeit reagiert das EventToCommandBehavior
auf die Interaktion mit der ListView
. Wenn ein Element im ListView
Kontrollkästchen ausgewählt ist, wird das ItemTapped
Ereignis ausgelöst, das in OrderDetailCommand
der ProfileViewModel
Datei ausgeführt wird. Die Ereignisargumente für das Ereignis werden standardmäßig an den Befehl übergeben. Diese Daten werden so konvertiert, wie sie vom in der EventArgsConverter
Eigenschaft angegebenen Konverter zwischen Quelle und Ziel übergeben wird, der die Item
Von der ListView
ItemTappedEventArgs
Eigenschaft zurückgibt. Wenn die Ausführung erfolgt, wird die OrderDetailCommand
ausgewählte Order
Datei daher als Parameter an die registrierte Aktion übergeben.
Weitere Informationen zu Verhaltensweisen finden Sie unter "Verhalten".
Zusammenfassung
Das Model-View-ViewModel (MVVM)-Muster hilft dabei, die Geschäfts- und Präsentationslogik einer Anwendung von der Benutzeroberfläche (UI) sauber zu trennen. Die Aufrechterhaltung einer sauberen Trennung zwischen Anwendungslogik und Benutzeroberfläche trägt dazu bei, zahlreiche Entwicklungsprobleme zu beheben und eine Anwendung einfacher zu testen, zu verwalten und zu entwickeln. Es kann auch die Wiederverwendungsmöglichkeiten von Code erheblich verbessern und Entwicklern und UI-Designern ermöglichen, bei der Entwicklung ihrer jeweiligen Teile einer App einfacher zusammenzuarbeiten.
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 Ui- und UI-Logik kapselt; das Ansichtsmodell, das die Darstellungslogik und den Zustand kapselt; und das Modell, das die Geschäftslogik und Die Daten der App kapselt.