Megosztás a következőn keresztül:


Model-View-ViewModel (MVVM)

Tipp.

Ez a tartalom egy részlet az eBook, Enterprise Application Patterns Using .NET MAUI, elérhető a .NET Docs vagy egy ingyenesen letölthető PDF, hogy lehet olvasni offline.

Vállalati alkalmazásminták .NET-alapú MAUI eBook-borító miniatűr használatával.

A .NET MAUI fejlesztői felülete általában egy felhasználói felület létrehozását foglalja magában az XAML-ben, majd a felhasználói felületen működő kód mögé való hozzáadást. Összetett karbantartási problémák merülhetnek fel az alkalmazások módosításakor és méretének és hatókörének növekedésében. Ezek a problémák közé tartozik a felhasználói felület vezérlői és az üzleti logika szoros összekapcsolása, ami növeli a felhasználói felület módosításának költségeit, valamint az ilyen kód egységtesztelési nehézségeit.

Az MVVM-minta segít az alkalmazás üzleti és megjelenítési logikájának tisztán elválasztásában a felhasználói felülettől (UI). Az alkalmazáslogika és a felhasználói felület tiszta elkülönítésének fenntartása számos fejlesztési probléma megoldásában segít, és megkönnyíti az alkalmazások tesztelését, karbantartását és fejlesztését. Emellett jelentősen javíthatja a kód újrahasználati lehetőségeit, és lehetővé teszi, hogy a fejlesztők és a felhasználói felület tervezői könnyebben működjenek együtt az alkalmazás megfelelő részeinek fejlesztésekor.

Az MVVM-minta

Az MVVM-mintában három alapvető összetevő található: a modell, a nézet és a nézetmodell. Mindegyik külön célt szolgál. Az alábbi ábrán a három összetevő közötti kapcsolatok láthatók.

Az MVVM-minta

Az egyes összetevők felelősségi körének megértése mellett fontos tisztában lenni azzal is, hogyan működnek együtt. Magas szinten a nézet "tud" a nézetmodellről, a nézetmodell pedig "tud" a modellről, de a modell nem ismeri a nézetmodellt, és a nézetmodell nem tud a nézetről. Ezért a nézetmodell elkülöníti a nézetet a modelltől, és lehetővé teszi, hogy a modell a nézettől függetlenül fejlődjön.

Az MVVM-minta használatának előnyei a következők:

  • Ha egy meglévő modell implementációja magában foglalja a meglévő üzleti logikát, annak módosítása nehéz vagy kockázatos lehet. Ebben a forgatókönyvben a nézetmodell a modellosztályok adaptereként működik, és megakadályozza, hogy jelentős módosításokat hajt végre a modellkódon.
  • A fejlesztők a nézet használata nélkül is létrehozhatnak egységteszteket a nézetmodellhez és a modellhez. A nézetmodell egységtesztjei pontosan ugyanazt a funkciót tudják használni, mint amelyet a nézet használ.
  • Az alkalmazás felhasználói felülete a nézetmodell és a modellkód érintése nélkül újratervezhető, feltéve, hogy a nézet teljes mértékben XAML-ben vagy C#-ban van implementálva. Ezért a nézet új verziójának a meglévő nézetmodellel kell működnie.
  • A tervezők és fejlesztők a fejlesztés során egymástól függetlenül és egyidejűleg dolgozhatnak az összetevőiken. A tervezők a nézetre összpontosíthatnak, míg a fejlesztők a nézetmodellen és a modell összetevőin dolgozhatnak.

Az MVVM hatékony használatának kulcsa annak megértése, hogyan lehet az alkalmazáskódokat a megfelelő osztályokba befoglalni, és hogyan működnek együtt az osztályok. Az alábbi szakaszok az MVVM-minta egyes osztályainak feladatait ismertetik.

Nézet

A nézet feladata annak meghatározása, hogy a felhasználó milyen struktúrát, elrendezést és megjelenést lát a képernyőn. Ideális esetben minden nézet az XAML-ben van definiálva, korlátozott mögöttes kóddal, amely nem tartalmaz üzleti logikát. Bizonyos esetekben azonban a mögöttes kód tartalmazhat olyan felhasználói felületi logikát, amely az XAML-ben nehezen kifejezendő vizuális viselkedést valósít meg, például animációkat.

A .NET-alkalmazásokban MAUI a nézet általában ContentPage-származtatott vagy ContentView-származtatott osztály. A nézeteket azonban egy adatsablon is képviselheti, amely meghatározza azokat a felhasználói felületi elemeket, amelyek vizuálisan ábrázolják az objektumot a megjelenítéskor. Az adatsablonok nézetként nem rendelkeznek kóddal, és egy adott nézetmodell-típushoz való kötésre szolgálnak.

Tipp.

Ne engedélyezze és tiltsa le a felhasználói felület elemeit a mögöttes kódban.

Győződjön meg arról, hogy a nézetmodellek felelősek a nézet bizonyos aspektusait érintő logikai állapotváltozások meghatározásáért, például hogy elérhető-e parancs, vagy hogy egy művelet függőben van-e. Ezért engedélyezze és tiltsa le a felhasználói felület elemeit kötéssel a modelltulajdonságok megtekintéséhez ahelyett, hogy engedélyezi és letiltja őket a kód mögötti kódban.

A nézetmodellen több lehetőség is van a kódot a nézet interakcióira válaszul, például gombkattintással vagy elemkijelöléssel. Ha egy vezérlő támogatja a parancsokat, a vezérlő Command tulajdonsága adathoz kötött lehet a nézetmodell ICommand tulajdonságához. A vezérlő parancsának meghívásakor a rendszer végrehajtja a nézetmodellben lévő kódot. A parancsokon kívül viselkedések csatolhatók egy objektumhoz a nézetben, és meghallgathatják a meghívandó parancsokat vagy az eseményt. Válaszul a viselkedés ezután meghívhat egy ICommand-et a nézetmodellen vagy egy metódust a nézetmodellen.

Nézetmodell

A nézetmodell olyan tulajdonságokat és parancsokat implementál, amelyekhez a nézet adatkötést végezhet, és a változásértesítési eseményeken keresztül értesíti az állapotváltozások nézetét. A nézetmodell által biztosított tulajdonságok és parancsok határozzák meg a felhasználói felület által kínált funkciókat, de a nézet határozza meg a funkció megjelenítésének módját.

Tipp.

A felhasználói felület aszinkron műveletekkel rugalmasan kezelhető.

A többplatformos alkalmazásoknak blokkolva kell tartaniuk a felhasználói felületi szálat, hogy javuljon a felhasználó teljesítményfelfogása. Ezért a nézetmodellben aszinkron metódusokat használjon az I/O-műveletekhez, és eseményeket állítsunk elő, hogy aszinkron módon értesítsük a nézeteket a tulajdonságváltozásokról.

A nézetmodell felelős a nézet és a szükséges modellosztályok közötti interakciók összehangolásáért is. Általában egy-a-többhöz kapcsolat van a nézetmodell és a modellosztályok között. A nézetmodell dönthet úgy, hogy közvetlenül a nézet számára teszi elérhetővé a modellosztályokat, hogy a nézetben lévő vezérlők közvetlenül hozzájuk kössenek adatokat. Ebben az esetben a modellosztályokat úgy kell megtervezni, hogy támogassák az adatkötést és az értesítési események módosítását.

Minden nézetmodell olyan formában biztosít adatokat egy modellből, amelyet a nézet könnyen felhasználhat. Ennek érdekében a nézetmodell néha adatkonvertálást hajt végre. Az adatkonvertálás elhelyezése a nézetmodellben jó ötlet, mert olyan tulajdonságokat biztosít, amelyekhez a nézet kapcsolódhat. Előfordulhat például, hogy a nézetmodell két tulajdonság értékeit kombinálja, hogy megkönnyítse a nézet általi megjelenítést.

Tipp.

Adatkonvertálás központosítása konverziós rétegben.

A konvertereket külön adatkonvertálási rétegként is használhatja, amely a nézetmodell és a nézet között helyezkedik el. Erre például akkor lehet szükség, ha az adatok speciális formázást igényelnek, amelyet a nézetmodell nem biztosít.

Ahhoz, hogy a nézetmodell kétirányú adatkötésben vegyen részt a nézettel, a tulajdonságainak növelnie kell az eseményt PropertyChanged . A modellek megtekintése megfelel ennek a követelménynek az INotifyPropertyChanged interfész implementálásával, és az PropertyChanged esemény növelése egy tulajdonság módosításakor.

Gyűjtemények esetén a nézetbarát ObservableCollection<T> lehetőség biztosított. Ez a gyűjtemény a gyűjtemény módosított értesítését valósítja meg, így a fejlesztőnek nem kell implementálnia a felületet a INotifyCollectionChanged gyűjteményeken.

Modell

A modellosztályok nem vizuális osztályok, amelyek beágyazják az alkalmazás adatait. Ezért a modell az alkalmazás tartománymodelljét jelképezheti, amely általában az üzleti és érvényesítési logika mellett egy adatmodellt is tartalmaz. A modellobjektumok közé tartoznak például az adatátviteli objektumok (DTO-k), az egyszerű régi CLR-objektumok (POCO-k), valamint a létrehozott entitás- és proxyobjektumok.

A modellosztályokat általában olyan szolgáltatásokkal vagy adattárakkal együtt használják, amelyek adathozzáférést és gyorsítótárazást foglalnak magában.

Nézetmodellek csatlakoztatása nézetekhez

A nézetmodellek a .NET MAUIadatkötési képességeinek használatával csatlakoztathatók a nézetekhez. Számos megközelítés használható nézetek létrehozására és modellek megtekintésére és futtatókörnyezetben való társítására. Ezek a megközelítések két kategóriába sorolhatók, az úgynevezett első nézetösszeállításra, és a modell első összetételének megtekintésére. A nézet első összetétele és a modell első összetétele közötti választás a preferencia és az összetettség kérdése. Minden megközelítésnek ugyanaz a célja, vagyis a nézetnek a BindingContext tulajdonságához hozzárendelt nézetmodellnek kell rendelkeznie.

A nézet első összetételével az alkalmazás elméletileg olyan nézetekből áll, amelyek az általuk használt nézetmodellhez csatlakoznak. Ennek a megközelítésnek az az elsődleges előnye, hogy megkönnyíti a lazán összekapcsolt, egységben tesztelhető alkalmazások összeállítását, mivel a nézetmodellek nem függenek maguktól a nézetektől. Az alkalmazás struktúráját is könnyen megértheti a vizualizáció szerkezetének követésével, ahelyett, hogy nyomon kellene követnie a kódvégrehajtást az osztályok létrehozásának és társításának megértéséhez. Emellett a nézet első felépítése igazodik a Microsoft Maui navigációs rendszeréhez, amely a navigáció során lapok létrehozásáért felelős, így a nézetmodell első összeállítása összetettebbé válik, és nem felel meg a platformnak.

A nézetmodell első összetételével az alkalmazás elméletileg nézetmodellekből áll, és egy szolgáltatás felelős a nézetmodell nézetének helyéért. A modell első összeállítása természetesebbnek tűnik egyes fejlesztők számára, mivel a nézetlétrehozás absztrakciós jellegű, így az alkalmazás logikai nem felhasználói felületi struktúrájára összpontosíthatnak. Emellett lehetővé teszi, hogy a nézetmodelleket más nézetmodellek is létrehozva legyenek. Ez a megközelítés azonban gyakran összetett, és megnehezítheti az alkalmazás különböző részeinek létrehozásának és társításának megértését.

Tipp.

A nézetmodellek és nézetek függetlenek maradnak.

A nézetek adatforrásbeli tulajdonsághoz való kötésének a nézet fő függőségének kell lennie a megfelelő nézetmodellhez. A nézetmodellekből ne hivatkozzon a nézettípusokra, például a Buttonra és a ListView-ra. Az itt ismertetett alapelvek követésével a modellek külön vizsgálhatók, így a hatókör korlátozásával csökkenthető a szoftverhibák valószínűsége.

A következő szakaszok a nézetmodellek nézetekhez való csatlakoztatásának fő megközelítéseit ismertetik.

Nézetmodell deklaratív létrehozása

A legegyszerűbb módszer, hogy a nézet deklaratív módon példányosíthassa a megfelelő nézetmodellt az XAML-ben. A nézet létrehozásakor a megfelelő nézetmodell-objektum is létre lesz hozva. Ezt a megközelítést a következő kód példában szemlélteti:

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

A létrehozáskor a ContentPage rendszer automatikusan létrehozza és LoginViewModel beállítja a nézet BindingContextegy példányát.

A nézetmodell deklaratív felépítése és hozzárendelése a nézethez azzal az előnnyel jár, hogy egyszerű, de hátránya, hogy alapértelmezett (paraméter nélküli) konstruktort igényel a nézetmodellben.

Nézetmodell programozott létrehozása

Egy nézet kóddal rendelkezhet a kód mögötti fájlban, így a nézetmodell hozzá lesz rendelve a tulajdonságához BindingContext . Ez gyakran a nézet konstruktorában történik, ahogy az alábbi kód példában is látható:

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

A nézetmodell programozott felépítése és hozzárendelése a nézet mögötti kód mögött azzal az előnnyel jár, hogy egyszerű. Ennek a megközelítésnek a fő hátránya azonban az, hogy a nézetnek minden szükséges függőséget meg kell adnia a nézetmodellnek. A függőségi injektálási tároló használata segíthet fenntartani a nézet és a nézetmodell közötti laza kapcsolatot. További információ: Függőséginjektálás.

Nézetek frissítése az alapul szolgáló nézetmodell vagy modell változásaira válaszul

A nézethez elérhető összes nézetmodellnek és modellosztálynak implementálnia kell a INotifyPropertyChanged felületet. Ennek az interfésznek a nézetmodellben vagy modellosztályban való implementálása lehetővé teszi, hogy az osztály változásértesítéseket biztosítson a nézet adathoz kötött vezérlőinek, amikor az alapul szolgáló tulajdonság értéke megváltozik.

Az alkalmazásokat a tulajdonságváltozásról szóló értesítés megfelelő használatára kell kikövetelni az alábbi követelmények teljesítésével:

  • Mindig emeljen fel egy eseményt PropertyChanged , ha egy nyilvános tulajdonság értéke megváltozik. Ne feltételezze, hogy az PropertyChanged esemény emelése figyelmen kívül hagyható az XAML-kötés működésének ismerete miatt.
  • Mindig eseményt PropertyChanged hoz létre minden olyan számított tulajdonsághoz, amelynek értékeit a nézetmodellben vagy modellben más tulajdonságok használják.
  • Mindig emelje fel az PropertyChanged eseményt a tulajdonságmódosítást okozó metódus végén, vagy amikor az objektum biztonságos állapotban van. Az esemény emelése az esemény kezelőinek szinkron meghívásával megszakítja a műveletet. Ha ez egy művelet közepén történik, előfordulhat, hogy az objektumot visszahívási függvények számára teszi elérhetővé, ha nem biztonságos, részben frissített állapotban van. Emellett előfordulhat, hogy a kaszkádolt módosításokat események aktiválják PropertyChanged . A kaszkádolt módosítások általában megkövetelik a frissítések befejezését, mielőtt a kaszkádolt módosítás biztonságosan végrehajtható lenne.
  • Soha ne emeljen eseményt PropertyChanged , ha a tulajdonság nem változik. Ez azt jelenti, hogy az esemény növelése előtt össze kell hasonlítania a régi és az PropertyChanged új értékeket.
  • Soha ne emelje fel az PropertyChanged eseményt a nézetmodell konstruktorában, ha egy tulajdonságot inicializál. A nézetben az adathoz kötött vezérlők jelenleg nem fognak előfizetni a változásértesítések fogadására.
  • Soha ne emeljen fel egynél több eseményt PropertyChanged ugyanazzal a tulajdonságnévargumentummal egy osztály nyilvános metódusának egyetlen szinkron meghívásán belül. Ha például egy NumberOfItems olyan tulajdonság, amelynek háttértárolója a _numberOfItems mező, ha egy metódus ötvenszeresére nő _numberOfItems egy ciklus végrehajtása során, akkor csak egyszer, az összes munka befejezése után kell tulajdonságmódosítási értesítést kapnia a tulajdonságról NumberOfItems . Aszinkron metódusok esetén az aszinkron folytatási lánc minden szinkron szegmensében adja meg PropertyChanged egy adott tulajdonságnév eseményét.

Ennek a funkciónak egy egyszerű módja az osztály bővítményének BindableObject létrehozása. Ebben a példában az ExtendedBindableObject osztály változásértesítéseket biztosít, amelyek az alábbi kód példában láthatók:

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 ...
    }
}

A .NET MAUIosztálya BindableObject implementálja az INotifyPropertyChanged interfészt, és egy metódust OnPropertyChanged biztosít. Az ExtendedBindableObject osztály megadja a RaisePropertyChanged tulajdonságváltozásról szóló értesítés meghívásának módját, és ennek során az osztály által BindableObject biztosított funkciókat használja.

A modellosztályok megtekintése ezután az ExtendedBindableObject osztályból származhat. Ezért minden nézetmodell-osztály az RaisePropertyChanged osztály metódusát használja a ExtendedBindableObject tulajdonságváltozásról szóló értesítés megadásához. Az alábbi példakód bemutatja, hogyan hívja meg az eShop többplatformos alkalmazás a tulajdonságmódosítási értesítést lambda kifejezéssel:

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

A lambda kifejezés ily módon történő használata kisebb teljesítményköltséget jelent, mivel a lambda kifejezést minden híváshoz ki kell értékelni. Bár a teljesítményköltségek kicsik, és általában nem befolyásolják az alkalmazásokat, a költségek akkor merülhetnek fel, ha sok változásértesítés van. Ennek a megközelítésnek azonban az az előnye, hogy fordítási idő típusú biztonságot és újrabontási támogatást nyújt a tulajdonságok átnevezésekor.

MVVM-keretrendszerek

Az MVVM-minta jól bevált a .NET-ben, és a közösség számos keretrendszert hozott létre, amelyek megkönnyítik ezt a fejlesztést. Mindegyik keretrendszer különböző funkciókkal rendelkezik, de szabványos, hogy egy közös nézetmodellt biztosítsanak az INotifyPropertyChanged interfész implementálásával. Az MVVM-keretrendszerek további funkciói közé tartoznak az egyéni parancsok, a navigációs segédek, a függőséginjektálás/szolgáltatáskereső összetevők és a felhasználói felületi platform integrációja. Bár nem szükséges ezeket a keretrendszereket használni, felgyorsíthatják és egységesíthetik a fejlesztést. Az eShop többplatformos alkalmazás a .NET Community MVVM Toolkitet használja. A keretrendszer kiválasztásakor figyelembe kell vennie az alkalmazás igényeit és a csapat erősségeit. Az alábbi lista tartalmazza a .NET-hez MAUIkészült MVVM-keretrendszerek némelyikét.

Felhasználói felületi interakció parancsokkal és viselkedésekkel

A többplatformos alkalmazásokban a műveletek általában egy felhasználói műveletre( például egy gombkattintásra) reagálva lesznek meghívva, amely egy eseménykezelő létrehozásával valósítható meg a kód mögötti fájlban. Az MVVM-mintában azonban a művelet végrehajtásának felelőssége a nézetmodellé, és a kód mögötti kód elhelyezését el kell kerülni.

A parancsok kényelmes módot biztosítanak a felhasználói felületen lévő vezérlőkhöz kötött műveletek ábrázolására. Belefoglalják a műveletet megvalósító kódot, és segítenek abban, hogy a nézetben el legyen választva a vizualizációtól. Így a nézetmodellek hordozhatóbbak lesznek az új platformokon, mivel nem függenek közvetlenül a platform felhasználói felületi keretrendszere által biztosított eseményekhez. A .NET MAUI olyan vezérlőket tartalmaz, amelyek deklaratív módon csatlakoztathatók egy parancshoz, és ezek a vezérlők meghívják a parancsot, amikor a felhasználó interakcióba lép a vezérlővel.

A viselkedések azt is lehetővé teszik, hogy a vezérlők deklaratív módon csatlakozhassanak egy parancshoz. A viselkedések azonban olyan műveletek meghívására használhatók, amelyek egy vezérlő által létrehozott eseménytartományhoz kapcsolódnak. Ezért a viselkedések számos olyan forgatókönyvet kezelnek, mint a parancsokkal kompatibilis vezérlők, ugyanakkor nagyobb fokú rugalmasságot és vezérlést biztosítanak. Emellett a viselkedésekkel parancsobjektumokat vagy metódusokat is társíthat olyan vezérlőkhöz, amelyeket nem kifejezetten parancsok kezelésére terveztek.

Parancsok implementálása

A modellek megtekintése általában nyilvános tulajdonságokat tesz elérhetővé a nézetből való kötéshez, amely megvalósítja az interfészt ICommand . Számos .NET-vezérlő MAUI és kézmozdulat biztosít egy tulajdonságot Command , amely a nézetmodell által biztosított objektumhoz ICommand kötött adat lehet. A gombvezérlő az egyik leggyakrabban használt vezérlő, amely egy parancstulajdonságot biztosít, amely a gombra kattintáskor fut.

Feljegyzés

Bár a nézetmodell által használt felület tényleges implementációját ICommand is közzéteheti (például Command<T>RelayCommand), a parancsokat nyilvánosan is közzéteheti.ICommand Így, ha később módosítania kell a megvalósítást, könnyen felcserélhető.

Az ICommand interfész meghatároz egy metódust Execute , amely magában foglalja a műveletet, egy metódust CanExecute , amely azt jelzi, hogy a parancs meghívható-e, és egy CanExecuteChanged olyan eseményt, amely olyan változások esetén következik be, amelyek befolyásolják, hogy a parancsot végre kell-e hajtani. A legtöbb esetben csak a parancsok metódusát Execute fogjuk megadni. A részletes áttekintésért ICommandtekintse meg a dokumentációját.

A .NET-ben MAUI az Command illesztőt megvalósító Command<T> osztályok és ICommand osztályok találhatók, ahol T az argumentumok Execute típusa és CanExecutea . Command alapvető Command<T> implementációk, amelyek a felülethez szükséges minimális funkcionalitást ICommand biztosítják.

Feljegyzés

Számos MVVM-keretrendszer több funkciógazdag implementációt kínál a ICommand felülethez.

A Command vagy Command<T> konstruktorhoz szükség van egy műveleti visszahívási objektumra, amelyet a ICommand.Execute metódus meghívásakor hívunk meg. A CanExecute metódus egy opcionális konstruktorparaméter, és egy Func, amely egy bool értéket ad vissza.

Az eShop többplatformos alkalmazás a RelayCommand és az AsyncRelayCommand alkalmazást használja. A modern alkalmazások elsődleges előnye, hogy jobb funkciókat biztosítanak az AsyncRelayCommand aszinkron műveletekhez.

Az alábbi kód bemutatja, hogyan jön létre egy Command regisztrációs parancsot képviselő példány a Register nézetmodell metódusának delegáltjának megadásával:

public ICommand RegisterCommand { get; }

A parancs egy olyan tulajdonságon keresztül jelenik meg a nézet számára, amely egy .ICommand Amikor a Execute metódust meghívják az Command objektumon, egyszerűen továbbítja a hívást a nézetmodell metódusának a Command konstruktorban megadott meghatalmazotton keresztül. Az aszinkron metódusok az aszinkron paranccsal hívhatók meg, és a parancs delegáltjának Execute megadásakor kulcsszavakra várnak. Ez azt jelzi, hogy a visszahívás egy Task , és várni kell. Az alábbi kód például bemutatja, hogyan jön létre egy ICommand bejelentkezési parancsot jelképező példány a nézetmodell metódusának SignInAsync delegáltjának megadásával:

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

A paraméterek átadhatók a ExecuteCanExecute parancs példányosításához az AsyncRelayCommand<T> osztály használatával a műveleteknek és a műveleteknek. A következő kód például azt mutatja be, hogy a AsyncRelayCommand<T> példányok hogyan jelzik, hogy a NavigateAsync metódushoz sztring típusú argumentum szükséges:

public ICommand NavigateCommand { get; }

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

Az egyes konstruktorokban és RelayCommandRelayCommand<T> osztályokban a metódus delegálása CanExecute nem kötelező. Ha nincs megadva meghatalmazott, akkor a Command függvény igaz CanExecuteértéket ad vissza. A nézetmodell azonban az objektum metódusának meghívásával CanExecute jelezheti a parancs állapotának ChangeCanExecute változásátCommand. Ez okozza az CanExecuteChanged eseményt. A parancshoz kötött felhasználói felületi vezérlők ezután frissítik az engedélyezett állapotukat, hogy tükrözzék az adathoz kötött parancs rendelkezésre állását.

Parancsok meghívása nézetből

Az alábbi példakód azt mutatja be, hogyan köti egy Grid adott osztály egy példányával az LoginViewRegisterCommand osztály egy adott példányátLoginViewModel:TapGestureRecognizer

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

A parancsparaméter opcionálisan definiálható a CommandParameter tulajdonság használatával is. A várt argumentum típusa a célmetelyekben ExecuteCanExecute van megadva. A TapGestureRecognizer rendszer automatikusan meghívja a célparancsot, amikor a felhasználó interakcióba lép a csatlakoztatott vezérlővel. Ha CommandParametermeg van adva, a rendszer argumentumként átadja a parancs végrehajtási meghatalmazottjának.

Viselkedések implementálása

A viselkedések lehetővé teszik a funkciók hozzáadását a felhasználói felület vezérlőihez anélkül, hogy alosztályba kellene őket sorolniuk. Ehelyett a funkció egy viselkedési osztályban van implementálva, és úgy csatlakozik a vezérlőhöz, mintha maga a vezérlő része lenne. A viselkedések lehetővé teszik olyan kód implementálását, amelyet általában kód mögött kell írnia, mivel az közvetlenül kommunikál a vezérlő API-jával úgy, hogy tömören csatolható legyen a vezérlőhöz, és több nézetben vagy alkalmazásban újra felhasználható legyen. Az MVVM kontextusában a viselkedés hasznos módszer a vezérlők parancsokhoz való csatlakoztatásához.

A vezérlőelemhez csatolt tulajdonságokon keresztül csatolt viselkedést csatolt viselkedésnek nevezzük. A viselkedés ezután annak az elemnek a közzétett API-ját használhatja, amelyhez hozzá van kapcsolva, hogy funkciókat adjon hozzá a vezérlőhöz vagy más vezérlőkhöz a nézet vizualizációs fájában.

A .NET-viselkedés MAUI olyan osztály, amely az vagy Behavior az Behavior<T> osztályból származik, ahol a T annak a vezérlőnek a típusa, amelyre a viselkedésnek vonatkoznia kell. Ezek az osztályok biztosítanak OnAttachedTo és OnDetachingFrom metódusokat, amelyeket felül kell bírálni, hogy olyan logikát biztosítson, amely akkor lesz végrehajtva, amikor a viselkedést a vezérlőkhöz csatolják, és leválasztják a vezérlőkről.

Az eShop többplatformos alkalmazásban az BindableBehavior<T> osztály az Behavior<T> osztályból származik. Az osztály célja BindableBehavior<T> egy alaposztály biztosítása olyan .NET-viselkedésekhez MAUI , amelyekhez a BindingContext viselkedést be kell állítani a csatlakoztatott vezérlőre.

Az BindableBehavior<T> osztály egy felülbírírozható OnAttachedTo metódust biztosít, amely beállítja a BindingContext viselkedést, és egy felülírható OnDetachingFrom metódust, amely megtisztítja a BindingContext.

Az eShop többplatformos alkalmazás tartalmaz egy EventToCommandBehavior osztályt, amelyet a MAUI közösségi eszközkészlet biztosít. EventToCommandBehavior parancsot hajt végre egy eseményre válaszul. Ez az osztály az BaseBehavior<View> osztályból származik, így a viselkedés a viselkedés felhasználásakor egy tulajdonság által ICommand megadotthoz kötődhet és végrehajthatóCommand. Az alábbi példakód az osztályt EventToCommandBehavior mutatja be:

/// <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);
        }
    }
}

A OnAttachedTo metódusokkal regisztrálhat és OnDetachingFrom törölhet egy eseménykezelőt a EventName tulajdonságban meghatározott eseményhez. Ezután, amikor az esemény aktiválódik, a rendszer meghívja a OnTriggerHandled metódust, amely végrehajtja a parancsot.

A parancsok eseménytüzek esetén történő végrehajtásának előnye EventToCommandBehavior , hogy a parancsok olyan vezérlőkkel társíthatók, amelyek nem a parancsokkal való interakcióra lettek tervezve. Emellett áthelyezi az eseménykezelési kódot a modellek megtekintéséhez, ahol egységtesztelhető.

Viselkedések meghívása nézetből

Ez EventToCommandBehavior különösen akkor hasznos, ha parancsokat csatol egy olyan vezérlőhöz, amely nem támogatja a parancsokat. A LoginView például az EventToCommandBehavior alábbi kódban látható módon hajtja végre azt ValidateCommand , amikor a felhasználó módosítja a jelszó értékét:

<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>

Futásidőben a rendszer válaszol a EventToCommandBehaviorEntry. Amikor egy felhasználó betér a Entry mezőbe, az TextChanged esemény aktiválódik, amely végrehajtja a ValidateCommand következőt: LoginViewModel. Alapértelmezés szerint az esemény eseményargumentumait a rendszer átadja a parancsnak. Szükség esetén a EventArgsConverter tulajdonság segítségével az EventArgs esemény által biztosított értéket olyan értékké alakíthatja, amelyet a parancs bemenetként vár el.

A viselkedésekről további információt a .NET fejlesztői központban található ViselkedésekMAUI.

Összegzés

A Model-View-ViewModel (MVVM) minta segít az alkalmazás üzleti és megjelenítési logikájának tisztán elválasztásában a felhasználói felülettől (UI). Az alkalmazáslogika és a felhasználói felület tiszta elkülönítésének fenntartása számos fejlesztési probléma megoldásában segít, és megkönnyíti az alkalmazások tesztelését, karbantartását és fejlesztését. Emellett jelentősen javíthatja a kód újrahasználati lehetőségeit, és lehetővé teszi, hogy a fejlesztők és a felhasználói felület tervezői könnyebben működjenek együtt az alkalmazás megfelelő részeinek fejlesztésekor.

Az MVVM-mintát használva az alkalmazás felhasználói felülete, valamint a mögöttes bemutató és üzleti logika három külön osztályba van osztva: a nézet, amely magában foglalja a felhasználói felületet és a felhasználói felület logikáját; a megjelenítési logikát és állapotot magában foglaló nézetmodell; és a modellt, amely az alkalmazás üzleti logikáját és adatait foglalja magában.