Model-View-ViewModel (MVVM)

Tip

Deze inhoud is een fragment uit het eBook, Enterprise Application Patterns Using .NET MAUI, beschikbaar op .NET Docs of als een gratis downloadbare PDF die offline kan worden gelezen.

Enterprise Application Patterns Using .NET MAUI eBook cover thumbnail.

De .NET-ontwikkelaarservaring MAUI omvat meestal het maken van een gebruikersinterface in XAML en het toevoegen van code-achter die werkt op de gebruikersinterface. Complexe onderhoudsproblemen kunnen zich voordoen wanneer apps worden gewijzigd en groter worden en groter worden. Deze problemen omvatten de nauwe koppeling tussen de besturingselementen voor de gebruikersinterface en de bedrijfslogica, waardoor de kosten voor het aanbrengen van ui-wijzigingen worden verhoogd en de moeilijkheidsgraad van het testen van een dergelijke code.

Het MVVM-patroon helpt de bedrijfs- en presentatielogica van een toepassing schoon te scheiden van de gebruikersinterface (UI). Het onderhouden van een schone scheiding tussen toepassingslogica en de gebruikersinterface helpt bij het oplossen van talloze ontwikkelproblemen en maakt een toepassing gemakkelijker te testen, te onderhouden en te ontwikkelen. Het kan ook de mogelijkheden voor opnieuw gebruik van code aanzienlijk verbeteren en stelt ontwikkelaars en UI-ontwerpers in staat om gemakkelijker samen te werken bij het ontwikkelen van hun respectieve onderdelen van een app.

Het MVVM-patroon

Er zijn drie kernonderdelen in het MVVM-patroon: het model, de weergave en het weergavemodel. Elk heeft een duidelijk doel. In het onderstaande diagram ziet u de relaties tussen de drie onderdelen.

Het MVVM-patroon

Naast het begrijpen van de verantwoordelijkheden van elk onderdeel, is het ook belangrijk om te begrijpen hoe ze communiceren. Op hoog niveau weet de weergave 'over' het weergavemodel en het weergavemodel 'weet over' het model, maar het model is niet op de hoogte van het weergavemodel en het weergavemodel is zich niet bewust van de weergave. Daarom isoleert het weergavemodel de weergave van het model en kan het model onafhankelijk van de weergave worden ontwikkeld.

De voordelen van het gebruik van het MVVM-patroon zijn als volgt:

  • Als een bestaande modelimplementatie bestaande bedrijfslogica inkapselt, kan het lastig of riskant zijn om deze te wijzigen. In dit scenario fungeert het weergavemodel als een adapter voor de modelklassen en voorkomt u dat u belangrijke wijzigingen aanbrengt in de modelcode.
  • Ontwikkelaars kunnen eenheidstests maken voor het weergavemodel en het model, zonder de weergave te gebruiken. De eenheidstests voor het weergavemodel kunnen exact dezelfde functionaliteit uitoefenen als die door de weergave wordt gebruikt.
  • De gebruikersinterface van de app kan opnieuw worden ontworpen zonder het weergavemodel en de modelcode aan te raken, mits de weergave volledig is geïmplementeerd in XAML of C#. Daarom moet een nieuwe versie van de weergave werken met het bestaande weergavemodel.
  • Ontwerpers en ontwikkelaars kunnen tijdens de ontwikkeling onafhankelijk en gelijktijdig aan hun onderdelen werken. Ontwerpers kunnen zich richten op de weergave, terwijl ontwikkelaars aan het weergavemodel en modelonderdelen kunnen werken.

De sleutel tot het effectief gebruik van MVVM ligt in het begrijpen hoe app-code in de juiste klassen en hoe de klassen communiceren. In de volgende secties worden de verantwoordelijkheden van elk van de klassen in het MVVM-patroon besproken.

Weergave

De weergave is verantwoordelijk voor het definiëren van de structuur, indeling en weergave van wat de gebruiker op het scherm ziet. In het ideale geval wordt elke weergave gedefinieerd in XAML, met een beperkte codeachterloop die geen bedrijfslogica bevat. In sommige gevallen kan de code-behind echter UI-logica bevatten die visueel gedrag implementeert dat moeilijk te uiten is in XAML, zoals animaties.

In een .NET-toepassing MAUI is een weergave doorgaans een ContentPage-afgeleide of ContentView-afgeleide klasse. Weergaven kunnen echter ook worden vertegenwoordigd door een gegevenssjabloon, waarmee de UI-elementen worden opgegeven die moeten worden gebruikt om een object visueel weer te geven wanneer het wordt weergegeven. Een gegevenssjabloon als weergave heeft geen codeachter en is ontworpen om te binden aan een specifiek type weergavemodel.

Tip

Vermijd het in- en uitschakelen van UI-elementen in de code-behind.

Zorg ervoor dat de weergavemodellen verantwoordelijk zijn voor het definiëren van logische statuswijzigingen die van invloed zijn op bepaalde aspecten van de weergaveweergave, zoals of een opdracht beschikbaar is of een indicatie dat een bewerking in behandeling is. Schakel daarom UI-elementen in en uit door binding om modeleigenschappen weer te geven in plaats van ze in code-behind in en uit te schakelen.

Er zijn verschillende opties voor het uitvoeren van code op het weergavemodel als reactie op interacties in de weergave, zoals een knopklik of itemselectie. Als een besturingselement opdrachten ondersteunt, kan de eigenschap Command van het besturingselement gegevensgebonden zijn aan een eigenschap ICommand in het weergavemodel. Wanneer de opdracht van het besturingselement wordt aangeroepen, wordt de code in het weergavemodel uitgevoerd. Naast opdrachten kunnen gedrag worden gekoppeld aan een object in de weergave en kan worden geluisterd naar een opdracht die moet worden aangeroepen of de gebeurtenis die moet worden gegenereerd. Als reactie kan het gedrag vervolgens een ICommand aanroepen op het weergavemodel of een methode in het weergavemodel.

ViewModel

Het weergavemodel implementeert eigenschappen en opdrachten waarmee de weergave gegevens kan binden en meldt de weergave van statuswijzigingen via wijzigingsmeldingen. De eigenschappen en opdrachten die het weergavemodel biedt, definiëren de functionaliteit die door de gebruikersinterface moet worden aangeboden, maar de weergave bepaalt hoe die functionaliteit moet worden weergegeven.

Tip

Houd de gebruikersinterface responsief met asynchrone bewerkingen.

Apps met meerdere platforms moeten de UI-thread gedeblokkeerd houden om de perceptie van prestaties van de gebruiker te verbeteren. Gebruik daarom in het weergavemodel asynchrone methoden voor I/O-bewerkingen en verhef gebeurtenissen om asynchroon weergaven van eigenschapswijzigingen te melden.

Het weergavemodel is ook verantwoordelijk voor het coördineren van de interacties van de weergave met alle modelklassen die vereist zijn. Er is doorgaans een een-op-veel-relatie tussen het weergavemodel en de modelklassen. Het weergavemodel kan ervoor kiezen om modelklassen rechtstreeks beschikbaar te maken voor de weergave, zodat besturingselementen in de weergave gegevens rechtstreeks aan deze klassen kunnen binden. In dit geval moeten de modelklassen worden ontworpen ter ondersteuning van gegevensbinding en wijzigingsmeldingen.

Elk weergavemodel bevat gegevens van een model in een formulier dat de weergave eenvoudig kan gebruiken. Hiervoor voert het weergavemodel soms gegevensconversie uit. Het plaatsen van deze gegevensconversie in het weergavemodel is een goed idee omdat deze eigenschappen biedt waarmee de weergave kan worden verbonden. Het weergavemodel kan bijvoorbeeld de waarden van twee eigenschappen combineren om het gemakkelijker te maken om door de weergave weer te geven.

Tip

Centraliseer gegevensconversies in een conversielaag.

Het is ook mogelijk om conversieprogramma's te gebruiken als een afzonderlijke laag voor gegevensconversie tussen het weergavemodel en de weergave. Dit kan bijvoorbeeld nodig zijn wanneer gegevens speciale opmaak vereisen die het weergavemodel niet biedt.

Als u wilt dat het weergavemodel deelneemt aan een gegevensbinding in twee richtingen met de weergave, moeten de eigenschappen van het weergavemodel de PropertyChanged gebeurtenis genereren. Bekijk modellen die aan deze vereiste voldoen door de INotifyPropertyChanged interface te implementeren en de gebeurtenis op te PropertyChanged halen wanneer een eigenschap wordt gewijzigd.

Voor verzamelingen is de weergavevriendelijk ObservableCollection<T> beschikbaar. Deze verzameling implementeert een melding dat de verzameling is gewijzigd, zodat de ontwikkelaar de INotifyCollectionChanged interface voor verzamelingen niet hoeft te implementeren.

Modelleren

Modelklassen zijn niet-visuele klassen die de gegevens van de app inkapselen. Daarom kan het model worden beschouwd als een representatie van het domeinmodel van de app, dat meestal een gegevensmodel bevat, samen met bedrijfs- en validatielogica. Voorbeelden van modelobjecten zijn DTU's (Data Transfer Objects), Plain Old CLR Objects (POCOs) en gegenereerde entiteits- en proxyobjecten.

Modelklassen worden doorgaans gebruikt in combinatie met services of opslagplaatsen die gegevenstoegang en caching inkapselen.

Weergavemodellen verbinden met weergaven

Weergavemodellen kunnen worden verbonden met weergaven met behulp van de mogelijkheden voor gegevensbinding van .NET MAUI. Er zijn veel benaderingen die kunnen worden gebruikt om weergaven te maken en modellen weer te geven en deze tijdens runtime te koppelen. Deze benaderingen vallen in twee categorieën, bekend als eerste weergavesamenstelling en model eerste samenstelling bekijken. Het kiezen tussen de eerste weergavesamenstelling en de eerste samenstelling van het weergavemodel is een probleem van voorkeur en complexiteit. Alle benaderingen hebben echter hetzelfde doel, namelijk dat voor de weergave een weergavemodel moet worden toegewezen aan de eigenschap BindingContext.

Met de eerste samenstelling van de weergave bestaat de app conceptueel uit weergaven die verbinding maken met de weergavemodellen waarvan ze afhankelijk zijn. Het belangrijkste voordeel van deze aanpak is dat het eenvoudig is om losjes gekoppelde, testbare apps voor eenheden te maken, omdat de weergavemodellen geen afhankelijkheid hebben van de weergaven zelf. Het is ook eenvoudig om de structuur van de app te begrijpen door de visuele structuur te volgen in plaats van code-uitvoering bij te houden om te begrijpen hoe klassen worden gemaakt en gekoppeld. Bovendien is de eerste constructie van de weergave afgestemd op het Microsoft-navigatiesysteem Mauidat verantwoordelijk is voor het samenstellen van pagina's wanneer de navigatie plaatsvindt, waardoor een weergavemodel de eerste samenstelling complex en verkeerd is uitgelijnd met het platform.

Met de eerste samenstelling van het weergavemodel bestaat de app conceptueel uit weergavemodellen, met een service die verantwoordelijk is voor het vinden van de weergave voor een weergavemodel. De eerste samenstelling van het model weergeven voelt natuurlijker voor sommige ontwikkelaars, omdat het maken van de weergave kan worden verwijderd, zodat ze zich kunnen concentreren op de logische niet-UI-structuur van de app. Daarnaast kunnen weergavemodellen worden gemaakt door andere weergavemodellen. Deze benadering is echter vaak complex en het kan lastig worden om te begrijpen hoe de verschillende onderdelen van de app worden gemaakt en gekoppeld.

Tip

Houd modellen en weergaven onafhankelijk.

De binding van weergaven aan een eigenschap in een gegevensbron moet de hoofdafhankelijkheid van de weergave zijn op het bijbehorende weergavemodel. Verwijs met name niet naar weergavetypen, zoals Knop en ListView, vanuit weergavemodellen. Door de hier beschreven principes te volgen, kunnen modellen geïsoleerd worden getest, waardoor de kans op softwarefouten wordt verminderd door het bereik te beperken.

In de volgende secties worden de belangrijkste benaderingen besproken voor het verbinden van weergavemodellen met weergaven.

Een weergavemodel declaratief maken

De eenvoudigste methode is dat de weergave het bijbehorende weergavemodel in XAML declaratief instantiëren. Wanneer de weergave is gemaakt, wordt het bijbehorende weergavemodelobject ook samengesteld. Deze benadering wordt gedemonstreerd in het volgende codevoorbeeld:

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

Wanneer de ContentPage database wordt gemaakt, wordt automatisch een exemplaar van de LoginViewModel weergave gemaakt en ingesteld als de weergave BindingContext.

Deze declaratieve constructie en toewijzing van het weergavemodel door de weergave heeft het voordeel dat het eenvoudig is, maar heeft het nadeel dat het een standaardconstructor (parameter-less) in het weergavemodel vereist.

Programmatisch een weergavemodel maken

Een weergave kan code bevatten in het code-behind-bestand, wat resulteert in het weergavemodel dat wordt toegewezen aan BindingContext de eigenschap. Dit wordt vaak bereikt in de constructor van de weergave, zoals wordt weergegeven in het volgende codevoorbeeld:

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

De programmatische constructie en toewijzing van het weergavemodel binnen de code-behind van de weergave heeft het voordeel dat het eenvoudig is. Het belangrijkste nadeel van deze benadering is echter dat de weergave het weergavemodel moet voorzien van alle vereiste afhankelijkheden. Het gebruik van een afhankelijkheidsinjectiecontainer kan helpen bij het behouden van losse koppeling tussen het weergave- en weergavemodel. Zie Afhankelijkheidsinjectie voor meer informatie.

Weergaven bijwerken als reactie op wijzigingen in het onderliggende weergavemodel of -model

Alle weergavemodel- en modelklassen die toegankelijk zijn voor een weergave, moeten de INotifyPropertyChanged interface implementeren. Door deze interface in een weergavemodel of modelklasse te implementeren, kan de klasse wijzigingsmeldingen verstrekken aan gegevensgebonden besturingselementen in de weergave wanneer de onderliggende eigenschapswaarde wordt gewijzigd.

App's moeten worden ontworpen voor het juiste gebruik van melding over het wijzigen van eigenschappen door te voldoen aan de volgende vereisten:

  • Altijd een PropertyChanged gebeurtenis genereren als de waarde van een openbare eigenschap verandert. Neem niet aan dat het genereren van de PropertyChanged gebeurtenis kan worden genegeerd vanwege kennis van hoe XAML-binding plaatsvindt.
  • Altijd een PropertyChanged gebeurtenis genereren voor berekende eigenschappen waarvan de waarden worden gebruikt door andere eigenschappen in het weergavemodel of -model.
  • Altijd de PropertyChanged gebeurtenis aan het einde van de methode die een eigenschap wijzigt, of wanneer het object een veilige status heeft. Als de gebeurtenis wordt gegenereerd, wordt de bewerking onderbroken door de handlers van de gebeurtenis synchroon aan te roepen. Als dit zich midden in een bewerking voordoet, kan het object worden blootgesteld aan callback-functies wanneer het een onveilige, gedeeltelijk bijgewerkte status heeft. Daarnaast is het mogelijk dat trapsgewijze wijzigingen worden geactiveerd door PropertyChanged gebeurtenissen. Trapsgewijze wijzigingen vereisen over het algemeen dat updates worden voltooid voordat de trapsgewijze wijziging veilig kan worden uitgevoerd.
  • Nooit een PropertyChanged gebeurtenis genereren als de eigenschap niet verandert. Dit betekent dat u de oude en nieuwe waarden moet vergelijken voordat u de PropertyChanged gebeurtenis verhoogt.
  • De gebeurtenis nooit opheffen tijdens de PropertyChanged constructor van een weergavemodel als u een eigenschap initialiseert. Gegevensgebonden besturingselementen in de weergave zijn op dit moment niet geabonneerd om wijzigingsmeldingen te ontvangen.
  • Nooit meer dan één PropertyChanged gebeurtenis genereren met hetzelfde eigenschapsnaamargument binnen één synchrone aanroep van een openbare methode van een klasse. Bijvoorbeeld, als een NumberOfItems eigenschap waarvan het back-uparchief het _numberOfItems veld is, als een methode vijftig keer wordt verhoogd _numberOfItems tijdens de uitvoering van een lus, moet deze slechts één keer een melding voor het wijzigen van eigenschappen op de NumberOfItems eigenschap genereren, nadat al het werk is voltooid. Voor asynchrone methoden genereert u de PropertyChanged gebeurtenis voor een bepaalde eigenschapsnaam in elk synchroon segment van een asynchrone vervolgketen.

Een eenvoudige manier om deze functionaliteit te bieden, is door een uitbreiding van de BindableObject klasse te maken. In dit voorbeeld biedt de ExtendedBindableObject klasse wijzigingsmeldingen, die wordt weergegeven in het volgende codevoorbeeld:

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)
    {
        // Omitted for brevity ...
    }
}

De klasse van .NET MAUIimplementeert de INotifyPropertyChanged interface en biedt een OnPropertyChanged BindableObject methode. De ExtendedBindableObject klasse biedt de methode voor het RaisePropertyChanged aanroepen van wijzigingsmeldingen voor eigenschappen en maakt hierbij gebruik van de functionaliteit van de BindableObject klasse.

Modelklassen weergeven kunnen vervolgens worden afgeleid van de ExtendedBindableObject klasse. Daarom gebruikt elke weergavemodelklasse de RaisePropertyChanged methode in de ExtendedBindableObject klasse om melding over eigenschapswijziging op te geven. In het volgende codevoorbeeld ziet u hoe de eShop-app voor meerdere platforms een melding voor eigenschapswijzigingen aanroept met behulp van een lambda-expressie:

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

Het gebruik van een lambda-expressie op deze manier omvat een kleine prestatiekosten omdat de lambda-expressie moet worden geëvalueerd voor elke aanroep. Hoewel de prestatiekosten klein zijn en doorgaans geen invloed hebben op een app, kunnen de kosten toenemen wanneer er veel wijzigingsmeldingen zijn. Het voordeel van deze aanpak is echter dat het compileertijdse veiligheids- en herstructureringsondersteuning biedt bij het wijzigen van de naam van eigenschappen.

MVVM Frameworks

Het MVVM-patroon is goed tot stand gebracht in .NET en de community heeft veel frameworks gemaakt die helpen deze ontwikkeling te vereenvoudigen. Elk framework biedt een andere set functies, maar het is standaard voor hen om een gemeenschappelijk weergavemodel te bieden met een implementatie van de INotifyPropertyChanged interface. Aanvullende functies van MVVM-frameworks zijn aangepaste opdrachten, navigatiehulpstoffen, onderdelen van de afhankelijkheidsinjectie/servicezoeker en integratie van het UI-platform. Hoewel het niet nodig is om deze frameworks te gebruiken, kunnen ze uw ontwikkeling versnellen en standaardiseren. De eShop-app voor meerdere platforms maakt gebruik van de .NET Community MVVM Toolkit. Wanneer u een framework kiest, moet u rekening houden met de behoeften van uw toepassing en de sterke punten van uw team. De onderstaande lijst bevat enkele van de meest voorkomende MVVM-frameworks voor .NET MAUI.

Interactie met de gebruikersinterface met behulp van opdrachten en gedrag

In apps met meerdere platforms worden acties meestal aangeroepen als reactie op een gebruikersactie, zoals een knopklik, die kan worden geïmplementeerd door een gebeurtenis-handler te maken in het code-behind-bestand. In het MVVM-patroon ligt de verantwoordelijkheid voor de uitvoering van de actie echter bij het weergavemodel en het plaatsen van code in de code-behind moet worden vermeden.

Opdrachten bieden een handige manier om acties weer te geven die kunnen worden gebonden aan besturingselementen in de gebruikersinterface. Ze bevatten de code waarmee de actie wordt geïmplementeerd en helpen om deze los te koppelen van de visuele weergave in de weergave. Op deze manier worden uw weergavemodellen overdraagbaarder naar nieuwe platforms, omdat ze geen directe afhankelijkheid hebben van gebeurtenissen die worden geleverd door het UI-framework van het platform. .NET MAUI bevat besturingselementen die declaratief kunnen worden verbonden met een opdracht. Met deze besturingselementen wordt de opdracht aangeroepen wanneer de gebruiker met het besturingselement communiceert.

Met gedrag kunnen besturingselementen ook declaratief worden verbonden met een opdracht. Gedrag kan echter worden gebruikt om een actie aan te roepen die is gekoppeld aan een reeks gebeurtenissen die door een besturingselement worden gegenereerd. Daarom is het gedrag van toepassing op veel van dezelfde scenario's als besturingselementen waarvoor opdrachten zijn ingeschakeld, terwijl het een grotere mate van flexibiliteit en beheer biedt. Daarnaast kan gedrag ook worden gebruikt om opdrachtobjecten of -methoden te koppelen aan besturingselementen die niet specifiek zijn ontworpen voor interactie met opdrachten.

Opdrachten implementeren

Modellen weergeven geven doorgaans openbare eigenschappen weer, voor binding vanuit de weergave, waarmee de ICommand interface wordt geïmplementeerd. Veel .NET-besturingselementen MAUI en gebaren bieden een Command eigenschap, die gegevens kan zijn die zijn gebonden aan een ICommand object dat door het weergavemodel wordt geleverd. Het knopbesturingselement is een van de meestgebruikte besturingselementen en biedt een opdrachteigenschap die wordt uitgevoerd wanneer op de knop wordt geklikt.

Notitie

Hoewel het mogelijk is om de daadwerkelijke implementatie beschikbaar te maken van de ICommand interface die uw weergavemodel gebruikt (bijvoorbeeld Command<T> of RelayCommand), is het raadzaam om uw opdrachten openbaar beschikbaar te maken als ICommand. Op deze manier kan de implementatie eenvoudig worden verwisseld als u de implementatie ooit op een later tijdstip moet wijzigen.

De ICommand interface definieert een Execute methode, die de bewerking zelf inkapselt, een CanExecute methode die aangeeft of de opdracht kan worden aangeroepen en een CanExecuteChanged gebeurtenis die optreedt wanneer er wijzigingen optreden die van invloed zijn op of de opdracht moet worden uitgevoerd. In de meeste gevallen leveren we alleen de Execute methode voor onze opdrachten. Raadpleeg de opdrachtdocumentatie voor .NET MAUIvoor een gedetailleerder overzichtICommand.

Geleverd met .NET MAUI zijn de Command en Command<T> klassen die de ICommand interface implementeren, waarbij T het type van de argumenten aan Execute en CanExecute. Command en Command<T> zijn eenvoudige implementaties die de minimale set functionaliteit bieden die nodig is voor de ICommand interface.

Notitie

Veel MVVM-frameworks bieden uitgebreidere implementaties van de ICommand interface.

Voor Command de of Command<T> constructor is een callback-object voor actie vereist dat wordt aangeroepen wanneer de ICommand.Execute methode wordt aangeroepen. De CanExecute methode is een optionele constructorparameter en is een Func die een bool retourneert.

De eShop-app voor meerdere platforms maakt gebruik van de RelayCommand en AsyncRelayCommand. Het belangrijkste voordeel voor moderne toepassingen is dat de AsyncRelayCommand functionaliteit voor asynchrone bewerkingen beter is.

De volgende code laat zien hoe een Command exemplaar, dat een registeropdracht vertegenwoordigt, wordt samengesteld door een gemachtigde op te geven voor de methode Register view model:

public ICommand RegisterCommand { get; }

De opdracht wordt beschikbaar gesteld aan de weergave via een eigenschap die een verwijzing naar een ICommand. Wanneer de Execute methode wordt aangeroepen op het Command object, wordt de aanroep naar de methode in het weergavemodel doorgestuurd via de gemachtigde die is opgegeven in de Command constructor. Een asynchrone methode kan worden aangeroepen door een opdracht met behulp van de asynchrone en wachten op trefwoorden bij het opgeven van de gemachtigde van Execute de opdracht. Dit geeft aan dat de callback een Task en moet worden verwacht. De volgende code laat bijvoorbeeld zien hoe een ICommand exemplaar, dat een aanmeldingsopdracht vertegenwoordigt, wordt samengesteld door een gemachtigde op te geven voor de SignInAsync weergavemodelmethode:

public ICommand SignInCommand { get; }
...
SignInCommand = new AsyncRelayCommand(async () => await SignInAsync());

Parameters kunnen worden doorgegeven aan de Execute en CanExecute acties met behulp van de AsyncRelayCommand<T> klasse om de opdracht te instantiëren. De volgende code laat bijvoorbeeld zien hoe een AsyncRelayCommand<T> exemplaar wordt gebruikt om aan te geven dat voor de NavigateAsync methode een argument van het type tekenreeks is vereist:

public ICommand NavigateCommand { get; }

...
NavigateCommand = new AsyncRelayCommand<string>(NavigateAsync);

In zowel de als RelayCommand<T> de RelayCommand klassen is de gedelegeerde aan de CanExecute methode in elke constructor optioneel. Als er geen gemachtigde is opgegeven, retourneert de Command waarde waar voor CanExecute. Het weergavemodel kan echter duiden op een wijziging in de status van CanExecute de opdracht door de ChangeCanExecute methode voor het Command object aan te roepen. Dit zorgt ervoor dat de CanExecuteChanged gebeurtenis wordt gegenereerd. Alle besturingselementen van de gebruikersinterface die aan de opdracht zijn gebonden, werken vervolgens hun ingeschakelde status bij om de beschikbaarheid van de gegevensgebonden opdracht weer te geven.

Opdrachten aanroepen vanuit een weergave

In het volgende codevoorbeeld ziet u hoe een Grid in de LoginView klasse wordt verbonden met de LoginViewModel RegisterCommand klasse met behulp van een TapGestureRecognizer exemplaar:

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

Een opdrachtparameter kan ook optioneel worden gedefinieerd met behulp van de CommandParameter eigenschap. Het type van het verwachte argument wordt opgegeven in de Execute en CanExecute doelmethoden. De TapGestureRecognizer doelopdracht wordt automatisch aangeroepen wanneer de gebruiker communiceert met het gekoppelde besturingselement. De CommandParameter, indien opgegeven, wordt doorgegeven als het argument aan de gemachtigde Uitvoeren van de opdracht.

Gedrag implementeren

Met gedrag kan functionaliteit worden toegevoegd aan besturingselementen van de gebruikersinterface zonder dat u ze hoeft te subklassen. In plaats daarvan wordt de functionaliteit geïmplementeerd in een gedragsklasse en gekoppeld aan het besturingselement alsof deze deel uitmaakte van het besturingselement zelf. Met gedrag kunt u code implementeren die u doorgaans moet schrijven als code-achter, omdat deze rechtstreeks communiceert met de API van het besturingselement, zodat deze beknopt kan worden gekoppeld aan het besturingselement en verpakt voor hergebruik in meer dan één weergave of app. In de context van MVVM zijn gedragingen een handige benadering voor het verbinden van besturingselementen met opdrachten.

Een gedrag dat is gekoppeld aan een besturingselement via gekoppelde eigenschappen, wordt een gekoppeld gedrag genoemd. Het gedrag kan vervolgens gebruikmaken van de weergegeven API van het element waaraan het is gekoppeld om functionaliteit toe te voegen aan dat besturingselement, of andere besturingselementen, in de visuele structuur van de weergave.

Een .NET-gedrag MAUI is een klasse die is afgeleid van de Behavior of Behavior<T> klasse, waarbij T het type besturingselement is waarop het gedrag moet worden toegepast. Deze klassen bieden OnAttachedTo en OnDetachingFrom methoden, die moeten worden overschreven om logica te bieden die wordt uitgevoerd wanneer het gedrag wordt gekoppeld aan en losgekoppeld van besturingselementen.

In de eShop-app met meerdere platforms is de BindableBehavior<T> klasse afgeleid van de Behavior<T> klasse. Het doel van de BindableBehavior<T> klasse is om een basisklasse te bieden voor .NET-gedrag MAUI waarvoor het BindingContext gedrag moet worden ingesteld op het gekoppelde besturingselement.

De BindableBehavior<T> klasse biedt een overschrijfbare OnAttachedTo methode waarmee het BindingContext gedrag wordt ingesteld en een overschrijfbare OnDetachingFrom methode waarmee de BindingContext.

De eShop-app voor meerdere platforms bevat een EventToCommandBehavior-klasse die wordt geleverd door de MAUI Community-toolkit. EventToCommandBehavior voert een opdracht uit als reactie op een gebeurtenis die optreedt. Deze klasse is afgeleid van de BaseBehavior<View> klasse, zodat het gedrag kan worden gebonden aan en een opgegeven door een Command eigenschap kan worden uitgevoerd ICommand wanneer het gedrag wordt verbruikt. In het volgende codevoorbeeld ziet u de EventToCommandBehavior klasse:

/// <summary>
/// The <see cref="EventToCommandBehavior"/> is a behavior that allows the user to invoke a <see cref="ICommand"/> through an event. It is designed to associate Commands to events exposed by controls that were not designed to support Commands. It allows you to map any arbitrary event on a control to a Command.
/// </summary>
public class EventToCommandBehavior : BaseBehavior<VisualElement>
{
    // Omitted for brevity...

    /// <inheritdoc/>
    protected override void OnAttachedTo(VisualElement bindable)
    {
        base.OnAttachedTo(bindable);
        RegisterEvent();
    }

    /// <inheritdoc/>
    protected override void OnDetachingFrom(VisualElement bindable)
    {
        UnregisterEvent();
        base.OnDetachingFrom(bindable);
    }

    static void OnEventNamePropertyChanged(BindableObject bindable, object oldValue, object newValue)
        => ((EventToCommandBehavior)bindable).RegisterEvent();

    void RegisterEvent()
    {
        UnregisterEvent();

        var eventName = EventName;
        if (View is null || string.IsNullOrWhiteSpace(eventName))
        {
            return;
        }

        eventInfo = View.GetType()?.GetRuntimeEvent(eventName) ??
            throw new ArgumentException($"{nameof(EventToCommandBehavior)}: Couldn't resolve the event.", nameof(EventName));

        ArgumentNullException.ThrowIfNull(eventInfo.EventHandlerType);
        ArgumentNullException.ThrowIfNull(eventHandlerMethodInfo);

        eventHandler = eventHandlerMethodInfo.CreateDelegate(eventInfo.EventHandlerType, this) ??
            throw new ArgumentException($"{nameof(EventToCommandBehavior)}: Couldn't create event handler.", nameof(EventName));

        eventInfo.AddEventHandler(View, eventHandler);
    }

    void UnregisterEvent()
    {
        if (eventInfo is not null && eventHandler is not null)
        {
            eventInfo.RemoveEventHandler(View, eventHandler);
        }

        eventInfo = null;
        eventHandler = null;
    }

    /// <summary>
    /// Virtual method that executes when a Command is invoked
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="eventArgs"></param>
    [Microsoft.Maui.Controls.Internals.Preserve(Conditional = true)]
    protected virtual void OnTriggerHandled(object? sender = null, object? eventArgs = null)
    {
        var parameter = CommandParameter
            ?? EventArgsConverter?.Convert(eventArgs, typeof(object), null, null);

        var command = Command;
        if (command?.CanExecute(parameter) ?? false)
        {
            command.Execute(parameter);
        }
    }
}

De OnAttachedTo en OnDetachingFrom methoden worden gebruikt voor het registreren en de registratie van een gebeurtenis-handler voor de gebeurtenis die in de EventName eigenschap is gedefinieerd. Wanneer de gebeurtenis wordt geactiveerd, wordt de OnTriggerHandled methode aangeroepen, waarmee de opdracht wordt uitgevoerd.

Het voordeel van het gebruik van de EventToCommandBehavior opdracht uitvoeren wanneer een gebeurtenis wordt geactiveerd, is dat opdrachten kunnen worden gekoppeld aan besturingselementen die niet zijn ontworpen voor interactie met opdrachten. Bovendien verplaatst dit gebeurtenisafhandelingscode om modellen weer te geven, waar deze kunnen worden getest.

Gedrag aanroepen vanuit een weergave

Dit EventToCommandBehavior is met name handig voor het koppelen van een opdracht aan een besturingselement dat geen ondersteuning biedt voor opdrachten. De LoginView gebruikt bijvoorbeeld de EventToCommandBehavior opdracht om de ValidateCommand waarde van hun wachtwoord uit te voeren wanneer de gebruiker de waarde van het wachtwoord wijzigt, zoals wordt weergegeven in de volgende code:

<Entry
    IsPassword="True"
    Text="{Binding Password.Value, Mode=TwoWay}">
    <!-- Omitted for brevity... -->
    <Entry.Behaviors>
        <mct:EventToCommandBehavior
            EventName="TextChanged"
            Command="{Binding ValidateCommand}" />
    </Entry.Behaviors>
    <!-- Omitted for brevity... -->
</Entry>

Tijdens runtime reageert de EventToCommandBehavior reactie op interactie met de Entry. Wanneer een gebruiker in het Entry veld typt, wordt de TextChanged gebeurtenis geactiveerd, die de ValidateCommand gebeurtenis uitvoert in de LoginViewModel. Standaard worden de gebeurtenisargumenten voor de gebeurtenis doorgegeven aan de opdracht. Indien nodig kan de EventArgsConverter eigenschap worden gebruikt om de EventArgs opgegeven door de gebeurtenis te converteren naar een waarde die de opdracht als invoer verwacht.

Zie Gedrag in het .NET MAUI Developer Center voor meer informatie over gedrag.

Samenvatting

Het MVVM-patroon (Model-View-ViewModel) helpt de bedrijfs- en presentatielogica van een toepassing schoon te scheiden van de gebruikersinterface (UI). Het onderhouden van een schone scheiding tussen toepassingslogica en de gebruikersinterface helpt bij het oplossen van talloze ontwikkelproblemen en maakt een toepassing gemakkelijker te testen, te onderhouden en te ontwikkelen. Het kan ook de mogelijkheden voor opnieuw gebruik van code aanzienlijk verbeteren en stelt ontwikkelaars en UI-ontwerpers in staat om gemakkelijker samen te werken bij het ontwikkelen van hun respectieve onderdelen van een app.

Met behulp van het MVVM-patroon worden de gebruikersinterface van de app en de onderliggende presentatie en bedrijfslogica onderverdeeld in drie afzonderlijke klassen: de weergave, die de gebruikersinterface en ui-logica inkapselt; het weergavemodel, dat presentatielogica en -status inkapselt; en het model, dat de bedrijfslogica en gegevens van de app inkapselt.