Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Dricks
Det här innehållet är ett utdrag från eBook, Enterprise Application Patterns Using .NET MAUI, tillgängligt på .NET Docs eller som en kostnadsfri nedladdningsbar PDF som kan läsas offline.
.NET-utvecklarupplevelsen MAUI innebär vanligtvis att skapa ett användargränssnitt i XAML och sedan lägga till kod bakom som fungerar i användargränssnittet. Komplexa underhållsproblem kan uppstå när appar ändras och växer i storlek och omfattning. Dessa problem omfattar den snäva kopplingen mellan användargränssnittskontrollerna och affärslogik, vilket ökar kostnaden för att göra ändringar i användargränssnittet och svårigheten med enhetstestning av sådan kod.
MVVM-mönstret hjälper till att separera ett programs affärs- och presentationslogik från användargränssnittet (UI). Genom att upprätthålla en ren separation mellan programlogik och användargränssnittet kan du lösa många utvecklingsproblem och göra ett program enklare att testa, underhålla och utveckla. Det kan också avsevärt förbättra möjligheterna till återanvändning av kod och gör det möjligt för utvecklare och användargränssnittsdesigners att samarbeta enklare när de utvecklar sina respektive delar av en app.
MVVM-mönstret
Det finns tre kärnkomponenter i MVVM-mönstret: modellen, vyn och vymodellen. Var och en har ett distinkt syfte. Diagrammet nedan visar relationerna mellan de tre komponenterna.
Förutom att förstå ansvarsområden för varje komponent är det också viktigt att förstå hur de interagerar. På hög nivå känner vyn "till" vymodellen och vymodellen "känner till" modellen, men modellen är omedveten om vymodellen och vymodellen känner inte till vyn. Därför isolerar vymodellen vyn från modellen och gör att modellen kan utvecklas oberoende av vyn.
Fördelarna med att använda MVVM-mönstret är följande:
- Om en befintlig modellimplementering kapslar in befintlig affärslogik kan det vara svårt eller riskabelt att ändra den. I det här scenariot fungerar vymodellen som ett kort för modellklasserna och hindrar dig från att göra större ändringar i modellkoden.
- Utvecklare kan skapa enhetstester för vymodellen och modellen, utan att använda vyn. Enhetstesterna för vymodellen kan använda exakt samma funktioner som används av vyn.
- Appgränssnittet kan göras om utan att röra vymodellen och modellkoden, förutsatt att vyn implementeras helt i XAML eller C#. Därför bör en ny version av vyn fungera med den befintliga vymodellen.
- Designers och utvecklare kan arbeta oberoende och samtidigt med sina komponenter under utvecklingen. Designers kan fokusera på vyn, medan utvecklare kan arbeta med visningsmodellen och modellkomponenterna.
Nyckeln till att använda MVVM ligger i att förstå hur appkod ska räknas in i rätt klasser och hur klasserna interagerar. I följande avsnitt beskrivs ansvarsområden för var och en av klasserna i MVVM-mönstret.
Visa
Vyn ansvarar för att definiera strukturen, layouten och utseendet på det som användaren ser på skärmen. Helst definieras varje vy i XAML, med en begränsad kod bakom som inte innehåller affärslogik. Men i vissa fall kan koden bakom innehålla UI-logik som implementerar visuellt beteende som är svårt att uttrycka i XAML, till exempel animeringar.
I ett .NET-program MAUI är en vy vanligtvis en ContentPage
-härledd eller ContentView
-härledd klass. Vyer kan dock också representeras av en datamall, som anger vilka gränssnittselement som ska användas för att visuellt representera ett objekt när det visas. En datamall som en vy har ingen bakomliggande kod och är utformad för att binda till en specifik vymodelltyp.
Dricks
Undvik att aktivera och inaktivera gränssnittselement i koden bakom.
Kontrollera att vymodellerna ansvarar för att definiera ändringar i logiskt tillstånd som påverkar vissa aspekter av vyns visning, till exempel om ett kommando är tillgängligt eller en indikation på att en åtgärd väntar. Aktivera och inaktivera därför gränssnittselement genom att binda för att visa modellegenskaper i stället för att aktivera och inaktivera dem i kod bakom.
Det finns flera alternativ för att köra kod på vymodellen som svar på interaktioner i vyn, till exempel ett knappklick eller val av objekt. Om en kontroll stöder kommandon kan kontrollens kommandoegenskap vara databunden till en ICommand-egenskap i vymodellen. När kontrollens kommando anropas körs koden i vymodellen. Förutom kommandon kan beteenden kopplas till ett objekt i vyn och kan lyssna efter antingen ett kommando som ska anropas eller den händelse som ska aktiveras. Som svar kan beteendet sedan anropa en ICommand på vymodellen eller en metod i vymodellen.
ViewModel
Vymodellen implementerar egenskaper och kommandon som vyn kan binda data till och meddelar vyn om eventuella tillståndsändringar genom ändringsmeddelandehändelser. De egenskaper och kommandon som visningsmodellen tillhandahåller definierar de funktioner som ska erbjudas av användargränssnittet, men vyn avgör hur den funktionen ska visas.
Dricks
Håll användargränssnittet dynamiskt med asynkrona åtgärder.
Appar med flera plattformar bör hålla användargränssnittstråden avblockerad för att förbättra användarens prestandauppfattning. I vymodellen använder du därför asynkrona metoder för I/O-åtgärder och genererar händelser för att asynkront meddela vyer om egenskapsändringar.
Vymodellen ansvarar också för att samordna vyns interaktioner med alla modellklasser som krävs. Det finns vanligtvis en en-till-många-relation mellan vymodellen och modellklasserna. Vymodellen kan välja att exponera modellklasser direkt till vyn så att kontroller i vyn kan binda data direkt till dem. I det här fallet måste modellklasserna utformas för att stödja databindning och ändringsmeddelandehändelser.
Varje vymodell innehåller data från en modell i ett formulär som vyn enkelt kan använda. För att åstadkomma detta utför vymodellen ibland datakonvertering. Det är en bra idé att placera datakonverteringen i vymodellen eftersom den innehåller egenskaper som vyn kan binda till. Vymodellen kan till exempel kombinera värdena för två egenskaper så att den blir enklare att visa i vyn.
Dricks
Centralisera datakonverteringar i ett konverteringslager.
Det går också att använda konverterare som ett separat datakonverteringslager som finns mellan vymodellen och vyn. Detta kan till exempel vara nödvändigt när data kräver särskild formatering som inte tillhandahålls av vymodellen.
För att vymodellen ska kunna delta i tvåvägsdatabindning med vyn måste dess egenskaper generera PropertyChanged
händelsen. Visa modeller uppfyller det här kravet genom att implementera INotifyPropertyChanged
gränssnittet och höja PropertyChanged
händelsen när en egenskap ändras.
För samlingar tillhandahålls den vyvänliga ObservableCollection<T>
. Den här samlingen implementerar meddelande om att samlingen har ändrats, vilket gör att utvecklaren inte behöver implementera gränssnittet i INotifyCollectionChanged
samlingar.
Modell
Modellklasser är icke-visuella klasser som kapslar in appens data. Därför kan modellen anses representera appens domänmodell, som vanligtvis innehåller en datamodell tillsammans med affärs- och valideringslogik. Exempel på modellobjekt är dataöverföringsobjekt (DTU: er), Oformaterade gamla CLR-objekt (POCO: er) och genererade entitets- och proxyobjekt.
Modellklasser används vanligtvis tillsammans med tjänster eller lagringsplatser som kapslar in dataåtkomst och cachelagring.
Ansluta vymodeller till vyer
Vymodeller kan anslutas till vyer med hjälp av databindningsfunktionerna i .NET MAUI. Det finns många metoder som kan användas för att konstruera vyer och visa modeller och associera dem vid körning. Dessa metoder är indelade i två kategorier, så kallade visa första sammansättning, och visa modellens första sammansättning. Att välja mellan att visa den första kompositionen och visa modellens första sammansättning är ett problem med inställningar och komplexitet. Alla metoder har dock samma mål, vilket är att vyn ska ha en vymodell tilldelad till egenskapen BindingContext.
Med visa första komposition appen är konceptuellt består av vyer som ansluter till de vymodeller som de är beroende av. Den främsta fördelen med den här metoden är att den gör det enkelt att skapa löst kopplade, enhetstestbara appar eftersom vymodellerna inte är beroende av själva vyerna. Det är också lätt att förstå appens struktur genom att följa dess visuella struktur, i stället för att behöva spåra kodkörning för att förstå hur klasser skapas och associeras. Dessutom överensstämmer den första konstruktionen med Microsoft Mauis navigeringssystem som ansvarar för att konstruera sidor när navigering sker, vilket gör en vymodell först komposition komplex och feljusterad med plattformen.
Med visningsmodellens första sammansättning består appen konceptuellt av vymodeller, med en tjänst som ansvarar för att hitta vyn för en vymodell. Visa modellens första sammansättning känns mer naturlig för vissa utvecklare, eftersom skapandet av vyn kan abstraheras bort, så att de kan fokusera på appens logiska icke-gränssnittsstruktur. Dessutom kan visningsmodeller skapas av andra vymodeller. Den här metoden är dock ofta komplex och det kan bli svårt att förstå hur de olika delarna i appen skapas och associeras.
Dricks
Håll visningsmodeller och vyer oberoende.
Bindningen av vyer till en egenskap i en datakälla bör vara vyns huvudsakliga beroende av dess motsvarande vymodell. Mer specifikt refererar du inte till vytyper, till exempel Knapp och ListView, från vymodeller. Genom att följa principerna som beskrivs här kan vymodeller testas isolerat, vilket minskar sannolikheten för programvarufel genom att begränsa omfånget.
I följande avsnitt beskrivs de viktigaste metoderna för att ansluta vymodeller till vyer.
Skapa en vymodell deklarativt
Den enklaste metoden är att vyn deklarativt instansierar motsvarande vymodell i XAML. När vyn är konstruerad skapas även motsvarande vymodellobjekt. Den här metoden visas i följande kodexempel:
<ContentPage xmlns:local="clr-namespace:eShop">
<ContentPage.BindingContext>
<local:LoginViewModel />
</ContentPage.BindingContext>
<!-- Omitted for brevity... -->
</ContentPage>
ContentPage
När skapas skapas en instans av LoginViewModel
den automatiskt och anges som vyns BindingContext
.
Den här deklarativa konstruktionen och tilldelningen av vymodellen i vyn har fördelen att den är enkel, men har nackdelen att den kräver en standardkonstruktor (parameterlös) i vymodellen.
Skapa en vymodell programmatiskt
En vy kan ha kod i filen code-behind, vilket resulterar i att vymodellen tilldelas till dess BindingContext
egenskap. Detta görs ofta i vyns konstruktor, vilket visas i följande kodexempel:
public LoginView()
{
InitializeComponent();
BindingContext = new LoginViewModel(navigationService);
}
Den programmatiska konstruktionen och tilldelningen av vymodellen i vyns kod bakom har fördelen att den är enkel. Den största nackdelen med den här metoden är dock att vyn måste tillhandahålla vymodellen med eventuella nödvändiga beroenden. Genom att använda en container för beroendeinmatning kan du upprätthålla lös koppling mellan vyn och vymodellen. Mer information finns i Beroendeinmatning.
Uppdatera vyer som svar på ändringar i den underliggande vymodellen eller modellen
Alla vymodell- och modellklasser som är tillgängliga för en vy bör implementera INotifyPropertyChanged gränssnittet. Genom att implementera det här gränssnittet i en vymodell eller modellklass kan klassen tillhandahålla ändringsmeddelanden till alla databundna kontroller i vyn när det underliggande egenskapsvärdet ändras.
Appens bör utformas för korrekt användning av egenskapsändringsmeddelande genom att uppfylla följande krav:
- Skapa alltid en
PropertyChanged
händelse om en offentlig egenskaps värde ändras. Anta inte att en höjningPropertyChanged
av händelsen kan ignoreras på grund av kunskap om hur XAML-bindning sker. - Skapa alltid en
PropertyChanged
händelse för beräknade egenskaper vars värden används av andra egenskaper i vymodellen eller modellen. -
PropertyChanged
Höj alltid händelsen i slutet av metoden som gör en egenskapsändring, eller när objektet är känt för att vara i ett säkert tillstånd. När händelsen aktiveras avbryts åtgärden genom att händelsens hanterare anropas synkront. Om detta händer mitt i en åtgärd kan objektet exponeras för återanropsfunktioner när det är i ett osäkert, delvis uppdaterat tillstånd. Dessutom är det möjligt att sammanhängande ändringar utlöses avPropertyChanged
händelser. Sammanhängande ändringar kräver vanligtvis att uppdateringarna slutförs innan den sammanhängande ändringen är säker att köra. - Skapa aldrig en
PropertyChanged
händelse om egenskapen inte ändras. Det innebär att du måste jämföra gamla och nya värden innan duPropertyChanged
höjer händelsen. - Skapa aldrig händelsen
PropertyChanged
under en vymodells konstruktor om du initierar en egenskap. Databundna kontroller i vyn har inte prenumererat på att ta emot ändringsmeddelanden just nu. - Skapa aldrig fler än en
PropertyChanged
händelse med samma egenskapsnamnargument inom en enda synkron anrop av en offentlig metod för en klass. Om en metod till exempel ökarNumberOfItems
femtio gånger under körningen av en loop med en_numberOfItems
egenskap vars lagringsplats är_numberOfItems
fältet, bör den bara generera ett meddelande om egenskapsändring påNumberOfItems
egenskapen en gång när allt arbete har slutförts. För asynkrona metoder genererar duPropertyChanged
händelsen för ett angivet egenskapsnamn i varje synkront segment i en asynkron fortsättningskedja.
Ett enkelt sätt att tillhandahålla den här funktionen är att skapa ett tillägg för BindableObject
klassen. I det här exemplet ExtendedBindableObject
ger klassen ändringsmeddelanden, vilket visas i följande kodexempel:
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 ...
}
}
.NET-klassen MAUIBindableObject
implementerar INotifyPropertyChanged
gränssnittet och tillhandahåller en OnPropertyChanged
metod. Klassen ExtendedBindableObject
tillhandahåller metoden RaisePropertyChanged
för att anropa meddelande om egenskapsändring och använder då de funktioner som tillhandahålls av BindableObject
klassen.
Visa modellklasser kan sedan härledas från ExtendedBindableObject
klassen. Därför använder RaisePropertyChanged
varje vymodellklass metoden i ExtendedBindableObject
klassen för att tillhandahålla egenskapsändringsmeddelande. Följande kodexempel visar hur eShop-multiplattformsappen anropar meddelande om egenskapsändring med hjälp av ett lambda-uttryck:
public bool IsLogin
{
get => _isLogin;
set
{
_isLogin = value;
RaisePropertyChanged(() => IsLogin);
}
}
Att använda ett lambda-uttryck på det här sättet innebär en liten prestandakostnad eftersom lambda-uttrycket måste utvärderas för varje anrop. Även om prestandakostnaden är liten och vanligtvis inte påverkar en app kan kostnaderna ackumuleras när det finns många ändringsmeddelanden. Fördelen med den här metoden är dock att den ger stöd för kompileringstidstypsäkerhet och refaktorisering vid namnbyte av egenskaper.
MVVM-ramverk
MVVM-mönstret är väl etablerat i .NET och communityn har skapat många ramverk som underlättar den här utvecklingen. Varje ramverk tillhandahåller en annan uppsättning funktioner, men det är standard för dem att tillhandahålla en gemensam vymodell med en implementering av INotifyPropertyChanged
gränssnittet. Ytterligare funktioner i MVVM-ramverken är anpassade kommandon, navigeringshjälpare, beroendeinmatnings-/tjänstlokaliseringskomponenter och integrering av användargränssnittsplattformen. Även om det inte är nödvändigt att använda dessa ramverk kan de påskynda och standardisera din utveckling. EShop-multiplattformsappen använder .NET Community MVVM Toolkit. När du väljer ett ramverk bör du ta hänsyn till programmets behov och teamets styrkor. Listan nedan innehåller några av de vanligaste MVVM-ramverken för .NET MAUI.
Interaktion med användargränssnittet med hjälp av kommandon och beteenden
I appar med flera plattformar anropas vanligtvis åtgärder som svar på en användaråtgärd, till exempel ett knappklick, som kan implementeras genom att skapa en händelsehanterare i filen bakom koden. I MVVM-mönstret ligger dock ansvaret för att genomföra åtgärden på vymodellen, och att placera kod i koden bakom bör undvikas.
Kommandon är ett bekvämt sätt att representera åtgärder som kan bindas till kontroller i användargränssnittet. De kapslar in koden som implementerar åtgärden och hjälper till att hålla den frikopplad från den visuella representationen i vyn. På så sätt blir dina vymodeller mer portabla för nya plattformar, eftersom de inte har ett direkt beroende av händelser som tillhandahålls av plattformens gränssnittsramverk. .NET MAUI innehåller kontroller som kan deklarativt anslutas till ett kommando, och dessa kontroller anropar kommandot när användaren interagerar med kontrollen.
Beteenden gör också att kontroller kan deklarativt anslutas till ett kommando. Beteenden kan dock användas för att anropa en åtgärd som är associerad med en rad händelser som genereras av en kontroll. Beteenden hanterar därför många av samma scenarier som kommandoaktiverade kontroller, samtidigt som de ger en större grad av flexibilitet och kontroll. Dessutom kan beteenden också användas för att associera kommandoobjekt eller metoder med kontroller som inte är särskilt utformade för att interagera med kommandon.
Implementera kommandon
Visningsmodeller exponerar vanligtvis offentliga egenskaper för bindning från vyn, som implementerar ICommand
gränssnittet. Många .NET-kontroller MAUI och gester tillhandahåller en Command
egenskap som kan vara data som är bundna till ett ICommand
objekt som tillhandahålls av vymodellen. Knappkontrollen är en av de vanligaste kontrollerna, vilket ger en kommandoegenskap som körs när knappen klickas.
Kommentar
Även om det är möjligt att exponera den faktiska implementeringen av ICommand
gränssnittet som din vymodell använder (till exempel Command<T>
eller RelayCommand
), rekommenderar vi att du exponerar dina kommandon offentligt som ICommand
. På så sätt kan du enkelt byta ut om du behöver ändra implementeringen vid ett senare tillfälle.
Gränssnittet ICommand
definierar en Execute
metod som kapslar in själva åtgärden, en CanExecute
metod som anger om kommandot kan anropas och en CanExecuteChanged
händelse som inträffar när ändringar inträffar som påverkar om kommandot ska köras. I de flesta fall anger Execute
vi bara metoden för våra kommandon. En mer detaljerad översikt över ICommand
finns i kommandodokumentationen för .NET MAUI.
Tillhandahålls med .NET MAUI är klasserna Command
och Command<T>
som implementerar ICommand
gränssnittet, där T
är typen av argument till Execute
och CanExecute
.
Command
och Command<T>
är grundläggande implementeringar som ger den minimala uppsättning funktioner som krävs för ICommand
gränssnittet.
Kommentar
Många MVVM-ramverk erbjuder fler funktionsrika implementeringar av ICommand
gränssnittet.
Konstruktorn Command
eller Command<T>
konstruktorn kräver ett åtgärdsåteranropsobjekt som anropas när ICommand.Execute
metoden anropas. Metoden CanExecute
är en valfri konstruktorparameter och är en Func som returnerar en bool.
EShop-appen för flera plattformar använder RelayCommand och AsyncRelayCommand. Den främsta fördelen för moderna program är att AsyncRelayCommand
ger bättre funktioner för asynkrona åtgärder.
Följande kod visar hur en Command
instans, som representerar ett registerkommando, konstrueras genom att ange ett ombud för metoden Register view model ::
public ICommand RegisterCommand { get; }
Kommandot exponeras för vyn via en egenskap som returnerar en referens till en ICommand
.
Execute
När metoden anropas för Command
objektet vidarebefordras helt enkelt anropet till metoden i vymodellen via ombudet som angavs i Command
konstruktorn. En asynkron metod kan anropas av ett kommando med hjälp av nyckelorden async och await när du anger kommandots Execute
ombud. Detta indikerar att återanropet är en Task
och bör inväntas. Följande kod visar till exempel hur en ICommand
instans, som representerar ett inloggningskommando, konstrueras genom att ange ett ombud för SignInAsync
visningsmodellmetoden:
public ICommand SignInCommand { get; }
...
SignInCommand = new AsyncRelayCommand(async () => await SignInAsync());
Parametrar kan skickas till Execute
åtgärderna och CanExecute
med hjälp AsyncRelayCommand<T>
av klassen för att instansiera kommandot. Följande kod visar till exempel hur en AsyncRelayCommand<T>
instans används för att indikera att NavigateAsync
metoden kräver ett argument av typen sträng:
public ICommand NavigateCommand { get; }
...
NavigateCommand = new AsyncRelayCommand<string>(NavigateAsync);
I både klasserna RelayCommand
och RelayCommand<T>
är ombudet CanExecute
till metoden i varje konstruktor valfri. Om ett ombud inte har angetts Command
returneras true för CanExecute
. Vymodellen kan dock indikera en ändring i kommandots CanExecute
status genom att anropa ChangeCanExecute
-metoden för Command
objektet. Detta gör att händelsen CanExecuteChanged
genereras. Alla användargränssnittskontroller som är bundna till kommandot uppdaterar sedan sin aktiverade status för att återspegla tillgängligheten för det databundna kommandot.
Anropa kommandon från en vy
Följande kodexempel visar hur en Grid
i LoginView
bindningarna till RegisterCommand
i LoginViewModel
klassen med hjälp av en TapGestureRecognizer
instans:
<Grid Grid.Column="1" HorizontalOptions="Center">
<Label Text="REGISTER" TextColor="Gray"/>
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding RegisterCommand}" NumberOfTapsRequired="1" />
</Grid.GestureRecognizers>
</Grid>
Du kan också definiera en kommandoparameter med hjälp av CommandParameter
egenskapen . Typen av det förväntade argumentet anges i målmetoderna Execute
och CanExecute
.
TapGestureRecognizer
Anropar automatiskt målkommandot när användaren interagerar med den anslutna kontrollen. , CommandParameter
om det anges, skickas som argumentet till kommandots Kör-ombud.
Implementera beteenden
Beteenden gör att funktioner kan läggas till i användargränssnittskontroller utan att behöva underklassa dem. I stället implementeras funktionen i en beteendeklass och kopplas till kontrollen som om den vore en del av själva kontrollen. Med beteenden kan du implementera kod som du vanligtvis måste skriva som kod bakom, eftersom den direkt interagerar med API:et för kontrollen, på ett sådant sätt att den kan kopplas koncist till kontrollen och paketeras för återanvändning i mer än en vy eller app. När det gäller MVVM är beteenden en användbar metod för att ansluta kontroller till kommandon.
Ett beteende som är kopplat till en kontroll via anslutna egenskaper kallas för ett kopplat beteende. Beteendet kan sedan använda det exponerade API:et för det element som det är kopplat till för att lägga till funktioner i kontrollen, eller andra kontroller, i det visuella trädet i vyn.
Ett .NET-beteende MAUI är en klass som härleds från Behavior
klassen eller Behavior<T>
, där T är den typ av kontroll som beteendet ska tillämpas på. Dessa klasser tillhandahåller OnAttachedTo
och OnDetachingFrom
metoder som ska åsidosättas för att tillhandahålla logik som ska köras när beteendet är kopplat till och kopplas från kontrollerna.
I eShop-appen BindableBehavior<T>
för flera plattformar härleds klassen från Behavior<T>
klassen. Syftet med BindableBehavior<T>
klassen är att tillhandahålla en basklass för .NET-beteenden MAUI som kräver BindingContext
att beteendet anges till den anslutna kontrollen.
Klassen BindableBehavior<T>
innehåller en åsidosättningsmetod OnAttachedTo
som anger BindingContext
beteendet och en åsidosättningsmetod OnDetachingFrom
som rensar upp BindingContext
.
EShop-appen för flera plattformar innehåller en EventToCommandBehavior-klass som tillhandahålls av MAUI Community Toolkit.
EventToCommandBehavior
kör ett kommando som svar på en händelse som inträffar. Den här klassen härleds från BaseBehavior<View>
klassen så att beteendet kan binda till och köra en ICommand
angiven av en Command
egenskap när beteendet används. Följande kodexempel visar EventToCommandBehavior
klassen:
/// <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);
}
}
}
Metoderna OnAttachedTo
och OnDetachingFrom
används för att registrera och avregistrera en händelsehanterare för händelsen som definieras i egenskapen EventName
. När händelsen utlöses OnTriggerHandled
anropas sedan metoden, som kör kommandot.
Fördelen med att använda EventToCommandBehavior
för att köra ett kommando när en händelse utlöses är att kommandon kan associeras med kontroller som inte har utformats för att interagera med kommandon. Dessutom flyttas kod för händelsehantering för att visa modeller, där den kan testas.
Anropa beteenden från en vy
EventToCommandBehavior
Är särskilt användbart för att koppla ett kommando till en kontroll som inte stöder kommandon. Till exempel använder EventToCommandBehavior
LoginView för att köra ValidateCommand
när användaren ändrar värdet för sitt lösenord, enligt följande kod:
<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>
Vid körning svarar EventToCommandBehavior
kommer att svara på interaktionen Entry
med . När en användare skriver in i Entry
fältet TextChanged
utlöses händelsen, som kör ValidateCommand
i LoginViewModel
. Som standard skickas händelseargumenten för händelsen till kommandot . Om det behövs kan egenskapen EventArgsConverter
användas för att konvertera den EventArgs
som tillhandahålls av händelsen till ett värde som kommandot förväntar sig som indata.
Mer information om beteenden finns i Beteenden i .NET MAUI Developer Center.
Sammanfattning
MVVM-mönstret (Model-View-View-ViewModel) hjälper till att separera ett programs affärs- och presentationslogik från användargränssnittet (UI). Genom att upprätthålla en ren separation mellan programlogik och användargränssnittet kan du lösa många utvecklingsproblem och göra ett program enklare att testa, underhålla och utveckla. Det kan också avsevärt förbättra möjligheterna till återanvändning av kod och gör det möjligt för utvecklare och användargränssnittsdesigners att samarbeta enklare när de utvecklar sina respektive delar av en app.
Med MVVM-mönstret delas appens användargränssnitt och den underliggande presentationen och affärslogik upp i tre separata klasser: vyn, som kapslar in användargränssnittet och UI-logiken. vymodellen, som kapslar in presentationslogik och -tillstånd. och modellen, som kapslar in appens affärslogik och data.