Implementatie van het MVVM patroon in een WPF applicatie
Dit artikel en bijbehorende Git repository tonen aan hoe een implementatie van het MVVM patroon een basis kan vormen voor een WPF applicatie. Het ontwerp, de structuur en de realisatie worden toegelicht.
Men is hierbij niet (verplicht) afhankelijk van externe libraries.
Aangezien het patroon een basis vormt voor de verdere ontwikkeling van een applicatie is het belangrijk om, alvorens verder te gaan, aan te halen dat er uitgebreide MVVM WPF frameworks bestaan welke functioneel omvangrijker zijn dan deze implementeringswijze. Dit StackOverflow bericht geeft een overzicht van dergelijke frameworks.
Desalniettemin vormt deze onafhankelijke implementatie door zijn karakteristieken, zoals verder omschreven, een goede basis voor verdere ontwikkeling.
💾 Github repository
📦 NuGet packages
Standaard heeft de solution volgende afhankelijkheden:
- Fody (v6.6.0)
- PropertyChanged.Fody (v3.4.0)
- MaterialDesignColors (v2.0.5-ci57)
- MaterialDesignThemes (v4.4.0-ci57)
📺 Interactie
💡 Initiatie
📝 Reflectie
🌐 Beheer
- Door de gebruiker
- Door interactie met de View van het beherende paar
- Bijvoorbeeld door een menu, tabbladen of buttons
- Door interactie met een View welke beheerd wordt
- Bijvoorbeeld door een submenu, tabbladen of buttons
- Door het beherende paar, initiatie door het ViewModel
- Indien aan bepaalde voorwaarden voldaan is
- Door paren die beheerd worden, initiatie door View of ViewModel
- Indien aan bepaalde voorwaarden voldaan is
🖼️ Ontwerp
In het geval van het beherende paar, welke zelf een View- en ViewModel zijn, is er sprake van een ObservableCollection met ViewModels die beheerd worden en een property die het actieve ViewModel aangeeft.
Het actieve ViewModel wordt geobserveerd en de corresponderende View weergegeven binnen een gebied van de beherende View.
Aangezien het beherende paar ook een View- en ViewModel is kunnen er meerdere niveaus van beheer bestaan, en dus Views genest weergegeven worden binnen de applicatie.
Men dient dit echter af te wegen tegenover een veralgemeende aanpak met een centraliserende beheerder, welke iets uitgebreider is qua functionaliteit.
Indien men nodeloos (voor slechts enkele View(Model)s afgezonderd te beheren) meerdere niveaus aanmaakt wordt men afhankelijk van deze hierarchie en dienen alle verwijzingen naar de properties en commands bij wijziging aangepast te worden.
Ter anecdote, bij het ontwikkelen van de graduaatsproef was er sprake van een twintigtal Views en ViewModels waarvan ~5 ondergebracht konden worden onder een nieuw niveau van beheer, er werd echter beslist om het beheer gecentraliseerd te houden wat verder geen obstakel vormde.
🏗️ MVVM Core
- Indien men Fody gebruikt (default)
- Door onrechtstreekse overerving van INotifyPropertyChanged worden de ViewModel properties omgevormd at compile-time zodat zij PropertyChanged events emitten
- Indien men Fody niet wenst te gebruiken
- Door overerving kan in de property setter gebruik gemaakt worden van de Update functie. Dit laat toe om met 1 lijn code, de aanroeping, de waarde van het veld aan te passen en het PropertyChanged event te emitten.
Een Binding middels de Command en CommandParameter element props in de View's XAML verwacht namelijk een ICommand implementatie.
💢 Aanverwante ViewModels
Mits er selectief private functies omgezet worden naar protected kan een deel van de functionaliteiten beschikbaar gesteld worden.
ℹ️ MVVM met beheer, hoe is het toegepast?
Nu er een beeld is gevormd van de structuur en hoe het beheer werkt, zal dit hoofdstuk aanhalen hoe het patroon en het beheer daadwerkelijk tot stand komen binnen het project.
De solution is tevens voorzien van in-line commentaar.
Start van de applicatie
- App codebehind
- App staat ingesteld als het startup object van het project, het instantieert het beherend paar ApplicatieOverzicht en zorgt voor diens weergave
- Deze Window heeft als DataContext diens ViewModel, ApplicatieOverzichtViewModel
- De verbanden tussen de View(Model)s die beheert worden, worden opgenomen in de Window.Resources
- Een ContentControl met Binding op het ActieveViewModel laat toe om van weergegeven View te veranderen
- De tabblad buttons laten toe om middels het WijzigHuidigViewModel Command van ActieveViewModel te veranderen
In de constructor worden de beheerde ViewModels geinstantieerd.
- Properties
- ActieveViewModel
- Bepaalt welke View weergegeven wordt
- BeheerdeViewModels
- Bevat de namen van de Views als key en een instantie van diens ViewModel als value
- Commands
- WijzigHuidigViewModel
- zie functie WijzigActiefViewModel
- ResetViewModelCommand
- zie functie ResetViewModel
- Functies
- WijzigActiefViewModel
- Wijzigt het ActieveViewModel, probeert indien nodig een instantie van het ViewModel aan te maken
- ResetViewModel
- Zal de instantie in BeheerdeViewModels vervangen door een nieuwe instantie
- MaakNieuwViewModel
- Maakt een nieuwe instantie aan de hand van het type van de meegegeven instantie
- Indien het ViewModel beschikt over een BeginStartupRoutine Command wordt deze uitgevoerd
- Aangezien diens View mogelijk reeds weergegeven wordt en het Loaded event in dat geval niet meer zal plaatsvinden
- ViewModel constructors en BeginStartupRoutine
- Aangezien de beheerder alle ViewModels tegelijk aanmaakt dient men bedachtzaam met ViewModel constructors om te gaan
- Men gebruikt de state pas indien de View gerendered wordt
- Men roept mogelijk resource-intensive functies op, zoals het vergaren van veel data voor een overzicht
- BeginStartupRoutine & StartupRoutine
- Laat toe om bij het renderen van de View het commando en dus diens functie aan te roepen welke acties verricht
- Bij first-render bijvoorbeeld om data te vergaren en een initial state te hebben
- Bij overige renders, indien toegelaten, om de state te "resetten"
- Introductie
- Bevat uitleg over de applicatie
- Toont aan hoe het ViewModel ingesteld wordt als DataContext, hetzelfde geld voor de overige Views
- Producten
- Bevat uitleg over de View
- Toont aan hoe het Loaded event behandeld wordt door UserControl_Loaded in de codebehind
- Deze handler voert BeginStartupRoutine van het ViewModel uit
- Toont aan hoe men met EersteRenderPlaatsgevonden bij een volgende rerender nog over dezelfde state beschikt (testen kan door datagrid aan te passen en van tabblad te veranderen)
- Bevat een DataGrid met Binding op een ObservableCollection, Producten
- Wijzigingen hebben effect op de collectie in het ViewModel
- Account
- Bevat uitleg over de View
- Toont aan hoe het Loaded event behandeld wordt door UserControl_Loaded in de codebehind
- Deze handler voert BeginStartupRoutine van het ViewModel uit
- Door geen conditie op te nemen in StartupRoutine zal bij iedere rerender de routine opnieuw uitgevoerd worden
- Toont aan hoe wijzigingen die aangebracht worden direct invloed hebben op het ViewModel (zie groene tekst)
- Instellingen
- Bevat uitleg over de View
- Geen noemenswaardig ViewModel, ter illustratie wel ingesteld als DataContext
- Toont aan hoe vanuit de beheerde View de beheersfunctionaliteiten gebruikt kunnen worden
(button met Binding op ViewModel commando van een Ancestor) - Geeft PrivacyInstellingView weer welke hetzelfde toepast om terugkeren naar InstellingenView mogelijk te maken
Comments
Post a Comment