Verwenden eines ViewModels

Abgeschlossen

Nachdem Sie mehr über die Komponenten erfahren haben, aus denen das MVVM-Muster besteht, haben Sie wahrscheinlich festgestellt, dass das Modell und die Ansicht leicht zu definieren waren. Sehen wir uns an, wie Sie die ViewModel-Komponente verwenden, um ihre Rolle im Muster besser zu definieren.

Verfügbarmachen von Eigenschaften für die Benutzeroberfläche

Wie im vorherigen Beispiel gezeigt, stützen sich ViewModels in Bezug auf die meisten Daten und die Geschäftslogik in der Regel auf das Model. Das ViewModel übernimmt dagegen die Formatierung, Konvertierung und Anreicherung von Daten in der für die aktuelle View erforderlichen Weise.

Formatieren mithilfe eines ViewModels

Wir haben uns mit der Formatierung des Urlaubsanspruchs bereits ein Formatierungsbeispiel angesehen. Datumsformatierung, Zeichencodierungen und Serialisierung sind Beispiele dafür, wie das ViewModel möglicherweise Daten aus dem Model formatiert.

Konvertieren mithilfe eines ViewModels

Häufig stellt das Model Informationen nur indirekt zur Verfügung. Aber ein ViewModel kann dies korrigieren. Angenommen, es soll auf dem Bildschirm angezeigt werden, ob es sich bei einem Mitarbeiter um einen Vorgesetzten handelt. Aus unserem Employee-Modell geht dies jedoch nicht direkt hervor. Stattdessen kann diese Information aus der Tatsache abgeleitet werden, ob andere Mitarbeiter an die Person berichten. Angenommen, das Model umfasst diese Eigenschaft:

public IList<Employee> DirectReports
{
    get
    {
        ...
    }
}

Wenn die Liste leer ist, können Sie daraus schließen, dass dieser Employee kein Vorgesetzter ist. In diesem Fall enthält EmployeeViewModel die Eigenschaft IsSupervisor, die diese Logik bereitstellt:

public bool IsSupervisor => _model.DirectReports.Any();

Anreichern mithilfe eines ViewModels

Gelegentlich stellt ein Model möglicherweise nur eine ID für zugehörige Daten bereit. Oder es werden verschiedene Modellklassen benötigt, um die erforderlichen Daten für einen einzelnen Bildschirm zu korrelieren. Das ViewModel bietet eine hervorragende Möglichkeit zum Ausführen dieser Aufgaben. Angenommen, Sie möchten alle Projekte abrufen, die ein Mitarbeiter zurzeit verwaltet. Diese Daten sind nicht Teil der Employee-Modellklasse. Der Zugriff darauf erfolgt über die CompanyProjects-Modellklasse. Das EmployeeViewModel-Objekt stellt – wie immer – seine Daten als öffentliche Eigenschaft zur Verfügung:

public IEnumerable<string> ActiveProjects => CompanyProjects.All
    .Where(p => p.Owner == _model.Id && p.IsActive)
    .Select(p => p.Name);

Verwenden von Eigenschaften zur Datenübergabe (Pass-Through) mit einem ViewModel

Häufig benötigt ein ViewModel genau die Eigenschaft, die das Model bereitstellt. Für diese Eigenschaften leitet die ViewModel-Komponente lediglich die Daten weiter:

public string Name
{
    get => _model.Name;
    set => _model.Name = value;
}

Festlegen des Bereichs für das ViewModel

Ein ViewModel kann auf jeder Ebene eingesetzt werden, die eine View enthält. Eine Seite umfasst üblicherweise ein ViewModel, aber dies trifft möglicherweise auch auf Unteransichten der Seite zu. Ein häufiger Grund für geschachtelte ViewModel-Komponenten ist die Anzeige eines ListView-Objekts auf der Seite. Die Liste verfügt über eine ViewModel-Komponente, die die Auflistung darstellt, z. B. EmployeeListViewModel. Jedes Element in der Liste ist ein EmployeeViewModel-Element.

Diagramm: EmployeeListViewModel-Element mit mehreren untergeordneten EmployeeViewModel-Objekten

Es kommt außerdem häufig vor, dass ein ViewModel auf der obersten Ebene die Daten und den Status für die gesamte Anwendung enthält, aber keiner bestimmten Seite zugeordnet ist. Ein solches ViewModel wird beispielsweise häufig zum Verwalten des „aktiven“ Elements verwendet. Betrachten Sie das soeben beschriebene ListView-Beispiel. Wenn der Benutzer eine Mitarbeiterzeile auswählt, stellt dieser Mitarbeiter das aktuelle Element dar. Wenn der Benutzer zu einer Detailseite navigiert oder eine Symbolleistenschaltfläche auswählt, während diese Zeile ausgewählt ist, sollte sich die Aktion oder Anzeige auf diesen Mitarbeiter beziehen. Eine elegante Form der Umsetzung dieses Szenarios besteht darin, für eine ListView.SelectItem-Datenbindung an eine Eigenschaft zu sorgen, auf die die Symbolleiste oder Detailseite ebenfalls zugreifen kann. Es funktioniert gut, diese Eigenschaft in einem zentralen ViewModel zu platzieren.

Wann sollten ViewModel-Komponente mit View-Komponenten wiederverwendet werden?

Wie Sie die Beziehung zwischen ViewModel und Model und zwischen ViewModel und View definieren, wird stärker durch App-Anforderungen als durch Regeln bestimmt. Der Zweck des ViewModels ist es, der Ansicht die Struktur und die Daten bereitzustellen, die sie benötigt. Dies sollte die Entscheidung darüber beeinflussen, „wie groß“ ein ViewModel sein sollte.

ViewModels spiegeln oft die Struktur einer Modellklasse wider und stehen in einer 1:1-Beziehung zu dieser Klasse. Im vorherigen Beispiel mit EmployeeViewModel wurde eine einzelne Employee-Instanz eingeschlossen und erweitert. Es liegt aber nicht immer eine 1:1-Beziehung vor. Wenn die ViewModel-Komponente die für die Ansicht erforderlichen Daten bereitstellen soll, könnten Sie stattdessen beispielsweise HRDashboardViewModel erhalten, um eine Übersicht über die Personalabteilung bereitzustellen. Diese ViewModel-Komponente weist keine explizite Beziehung zu einem Model auf, kann aber Daten aus einer beliebigen Modellklasse verwenden.

Ebenso stellen Sie möglicherweise fest, dass ViewModels und Views häufig eine 1:1-Beziehung aufweisen. Aber auch dies ist nicht notwendigerweise so. Betrachten wir erneut eine ListView, die eine Zeile für jeden Mitarbeiter anzeigt. Wenn Sie eine der Zeilen auswählen, gelangen Sie zu einer Seite mit Mitarbeiterdetails.

Die Listenseite enthält das zugehörige ViewModel mit einer Sammlung. Wie zuvor vorgeschlagen, könnte es sich bei dieser Sammlung um eine Sammlung aus EmployeeViewModel-Objekten handeln. Wenn der Benutzer eine Zeile auswählt, könnte die EmployeeViewModel-Instanz an EmployeeDetailPage übergeben werden. Und die Detailseite könnte dieses EmployeeViewModel als BindingContext verwenden.

Dieses Szenario kann eine hervorragende Möglichkeit zur ViewModel-Wiederverwendung sein. Beachten Sie aber, dass ViewModels dafür vorgesehen sind, die von der View benötigten Daten bereitzustellen. In einigen Fällen sollten selbst dann getrennte ViewModels verwendet werden, wenn sie alle auf derselben Modellklasse basieren. In diesem Beispiel werden für die ListView-Zeilen wahrscheinlich deutlich weniger Informationen als die vollständige Detailseite benötigt. Wenn das Abrufen der für die Detailseite benötigten Daten mit viel Aufwand verbunden ist, können Sie ggf. die Modelle EmployeeListRowViewModel und ein EmployeeDetailViewModel verwenden, um die jeweiligen Ansichten zu bedienen.

ViewModel-Objektmodell

Die Verwendung einer Basisklasse, die INotifyPropertyChanged implementiert, bedeutet, dass Sie die Schnittstelle nicht für jede ViewModel-Komponente neu implementieren müssen. Sehen Sie sich die HR-Anwendung an, wie sie im vorherigen Teil dieses Schulungsmoduls beschrieben wurde. Die Klasse EmployeeViewModel hat die INotifyPropertyChanged-Schnittstelle implementiert und eine Hilfsmethode mit dem Namen OnPropertyChanged zum Auslösen des PropertyChanged-Ereignisses bereitgestellt. Andere ViewModel-Komponenten im Projekt, etwa solche, die die einem Mitarbeiter zugewiesenen Ressourcen beschreiben, erfordern ebenfalls INotifyPropertyChanged, um vollständig in eine Ansicht integriert zu werden.

Die MVVM Toolkit-Bibliothek, Teil des .NET-Community-Toolkits, ist eine Sammlung von standardmäßigen, eigenständigen, einfachen Typen, die eine Startimplementierung für die Erstellung moderner Apps mithilfe des MVVM-Musters bereitstellen.

Anstatt Ihre eigene ViewModel-Basisklasse zu schreiben, erben Sie von der ObservableObject-Klasse des Toolkits, die alles bereitstellt, was Sie für eine ViewModel-Basisklasse benötigen. EmployeeViewModel kann vereinfacht werden:

using System.ComponentModel;

public class EmployeeViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;
    private Employee _model;

    public string Name
    {
        get {...}
        set
        {
            _model.Name = value;
            OnPropertyChanged(nameof(Name))
        }
    }

    protected void OnPropertyChanged(string propertyName) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

Geänderter Code:

using Microsoft.Toolkit.Mvvm.ComponentModel;

public class EmployeeViewModel : ObservableObject
{
    private string _name;

    public string Name
    {
        get => _name;
        set => SetProperty(ref _name, value);
    }
}

Das MVVM Toolkit wird über das NuGet-Paket CommunityToolkit.Mvvm verteilt.

Überprüfen Sie Ihr Wissen

1.

Bei Verwendung des MVVM-Musters mit .NET MAUI werden Ihr Model, die Ansicht und die ViewModel-Komponente nicht vollständig voneinander entkoppelt. Welche Auswahl beschreibt eine häufige Abhängigkeit zwischen den MVVM-Elementen?

2.

Welches Element ist wahrscheinlich eng an die Plattform gekoppelt und erschwert das Erstellen von Unittests? Model, View oder ViewModel?