From 7a0ae3b3c70da4a1202d2d5fc5fe6ebf6c984b6e Mon Sep 17 00:00:00 2001 From: cygon Date: Sat, 27 Jul 2024 00:09:35 +0200 Subject: [PATCH] Added a bit of documentation for the WindowManager and its MVVM concept --- Documents/ViewModels.uml | Bin 0 -> 970 bytes Documents/WindowManager.svg | 109 ++++++++++++++++++++++++++++++++++ Documents/WindowManager.uml | Bin 0 -> 3018 bytes ReadMe.md | 115 ++++++++++++++++++++++++++++++++++++ 4 files changed, 224 insertions(+) create mode 100644 Documents/ViewModels.uml create mode 100644 Documents/WindowManager.svg create mode 100644 Documents/WindowManager.uml diff --git a/Documents/ViewModels.uml b/Documents/ViewModels.uml new file mode 100644 index 0000000000000000000000000000000000000000..4ae6575e638d7419ac164b486584b3a296cf01ce GIT binary patch literal 970 zcma)5y-(Xf6o0O9p>7bBpjo=O8z}0)iV#tiNC=4(q_mYfUYr*=16{gSAcm?P%ha*%d$tohX{Gkr@9y5culv1w_pY_w2Cx8N29m^rXYe|XwJ(K{ zTDA8@h?QSSa*!}t;6fu1ah&eE8draDU4`n z+CDMfnx{Jl=o|7K(|vukw1=GJl9-c(`F1(U{0Ds)ah{g$V&M3EIySRl_LW!XFPCbC zl!dvkf4S<@|D#?}cx$pldxt+#b~WstA#YV;>s+-CMt|XAW#1+os-*7BV%+U zWMx#mu5P{2M)n4~kq$93gT}s4d)S?>=Zl)RzK%zz%*KW{Wm`Vwf5PR=3}*qF7^3Q; z@&^-L;J~LFrmy2mn# literal 0 HcmV?d00001 diff --git a/Documents/WindowManager.svg b/Documents/WindowManager.svg new file mode 100644 index 0000000..2f0ad49 --- /dev/null +++ b/Documents/WindowManager.svg @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + (Optional) + + + + + + + IActiveWindowTracker + <<interface>> + Attributes + Methods + + ActiveWindow : Form + + + IWindowManager + <<interface>> + Methods + + OpenRoot<TViewModel>(viewModel : TViewModel = null) : Form + + ShowModal<TViewModel>(viewModel : TViewModel = null) : bool? + + CreateView<TViewModel>(viewModel : TViewModel = null) : Control + + CreateViewModel<TViewModel>() : TViewModel + + + + WindowManager + Attributes + Methods + + ActiveWindow : Form + + OpenRoot<TViewModel>(viewModel : TViewModel = null) : Form + + ShowModal<TViewModel>(viewModel : TViewModel = null) : bool? + + CreateView<TViewModel>(viewModel : TViewModel = null) : Control + + CreateViewModel<TViewModel>() : TViewModal + # LocateViewForViewModel(viewModelType : Type) : Type + # CreateInstance(type : Type) : object + + + + IView + <<interface>> + Attributes + Methods + + DataContext : object + + + + ViewForm + Attributes + Methods + + DataContext : object + # OnDataContextChanged(sender : object, oldDataContext : object, newDataContext : object) + # InvalidateAllViewModelProperties() + + + + MultiPageViewForm + Attributes + Methods + + DataContext : object + # OnDataContextChanged(sender : object, oldDataContext : object, newDataContext : object) + # InvalidateAllViewModelProperties() + # IdentifyPageContainer() : Control + # OnViewModelPropertyChanged(sender : object, arguments : PropertyChangedEventArgs) + # ActivePageView : Control + # ActivePageViewModel : object + + + + YourViewModel + Attributes + Methods + - ... + + ... + + + + INotifyPropertyChanged + <<interface>> + Attributes + Methods + + <<event>> PropertyChanged + + + + ViewControl + Attributes + Methods + + DataContext : object + # OnDataContextChanged(sender : object, oldDataContext : object, newDataContext : object) + # InvalidateAllViewModelProperties() + diff --git a/Documents/WindowManager.uml b/Documents/WindowManager.uml new file mode 100644 index 0000000000000000000000000000000000000000..a64378f863f0f2c7706ad0b41cc72e60400fd564 GIT binary patch literal 3018 zcmd^B%}*0S6rWNmAA({~Of)g8HKe3S!ohCD^r`Fn5n&g8^2V==~9 zC#zIqte4#omaw^LJmRh)t?7AQ5}kAm0z)SoHM!$d_n*`LTYCQ~%@BhD!h9@$&#($| zd6HYaDC`cya)teX>mp!kWg}^AswAx2Qo18EcMY*TDGS2Pq?V$kc2%qGYu7ZZY?=eK zIWFx*Xvw6t>3LEh3*0=IggGhAEJWne+NdqKE1>^iGDfB4+R}uSK~;+HMV%^?w2>|XR=5kV-ka~za z!gCe=HT2J{mIQ!9lMf)85z_u-h}gX4xZKi3%B^RVa|=Ru!5UDMa>iO4cJ=`Q5{#nn zVL8W0Khkn@G$(iRHb$@V_kc>Ur0b~f()3Ssh&&Q@rsFu^*Jhp+YBAjbOA+8B2^ zYD%oQ>gt_F5Plqp=n;t2Vd8Tyz{2)q9xR z!e*1&l%;qxI?t`5D5M-=6@*>&ZBUbDL19x4s5F}$48XjLNiA&Mb0f- zmW1sZ!b$mzfiJb}M}0in*(3WcY>+}QM-hoem`4vy0)pb+I6X7*Aa2cC!imYU=^D4F z^&c>RNB))oCMnVdVY$YGRk#6ca)YkN6v|QQ{?o{JV;WX>%{sS>WiX<78ylLjTF{>`J2*5Ur^Z*N*a6T*|at? zA^@FCrV&w9muT^aVT5M1{o`y*(B~OdfWGQ~@Ilu5rmpnjh^VHaGc{Gh7bG_al5{|Q z!p8xad@nt$KymLeJtydD+N}W}MQMfQrXuEF=)s37!1bmD*ufHXk+;#uVi!9`cXKa0 HO#%E3S2X@8 literal 0 HcmV?d00001 diff --git a/ReadMe.md b/ReadMe.md index dc6edfe..bbd2af8 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -15,3 +15,118 @@ and it will figure out what the correct view is by name (i.e. `ExampleForm`). There are unit tests for the whole library, so everything is verifiably working on all platforms tested (Linux, Windows, Raspberry). + + +MVVM +---- + +MVVM stands for Model-View-ViewModel. It is a way to design GUI applications +that improves testability and keeps the UI separate from the business logic. + +- The Model is the underlying data the application is working with, + not designed in any way to facilitate any GUI display or editing + +- The ViewModel is like an adapter - it presents the data in the model in + a way that can be easily dealt with by the view. For example, sets of + items may be exposed as `BindingList`. + +- The View is the UI itself - for example, the main window or a modal dialog + that is displayed would be a view, too. Each view has a connected ViewModel + and the only code in the view class should be what directly handles the UI, + like updating button states when the ViewModel signals a change. + + +Basic Design +------------ + +This library implements the MVVM pattern through its `WindowManager` class: + +![The WindowManager and its related classes](./Documents/WindowManager.svg) + +The `WindowManager` keeps track of all open windows and their view models, +so your basic `Main()` method, which normally looks like this: + +```csharp +[STAThread] +static void Main() { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new MainForm()); +} +``` + +Now becomes this: + +```csharp +[STAThread] +static void Main() { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + + using(var windowManager = new WindowManager()) { + Application.Run(windowManager.OpenRoot()); + } +} +``` + +As you can see, we no longer mention the `MainForm` by name, instead we ask +the `WindowManager` to construct a new `MainViewModel` and also create a view +that displays it. + +It does so by using a "convention over configuration" approach, meaning it +assumes that if you request a view for `FlurgleSettingsViewModel`, it will +look for a view named `FlurgleSettingsView`, `FlurgleSettingsForm`, +`FlurgeSettingsWindow` or `FlurgleSettingsDialog` class and try to construct +an instance of that class. + +Furthermore, if that class implements the `IView` interface, the view model +will be assigned to its `DataContext` property, establishing +the View/ViewModel relationship. + + +Adding an IoC Container +----------------------- + +In the previous example, the view and its view model were constructed using +`Activator.CreateInstance()` - a method provided by .NET that creates a new +instance via a type's default constructor. + +Most of the time, ViewModels have constructor parameters, however. For example +to provide the ViewModel with the data it is supposed to be an adapter for. +You can achieve that by constructing the ViewModel yourself and passing it +to the `WindowManager.OpenRoot()` or `WindowManager.ShowModal()` methods. + +A much better approach is to use a dependency injector - an IoC container with +automatic constructor parameter injection. My favorite one is Ninject (due to +its neat setup with a fluent interface), but you can use any container you +wish, simply by inheriting your own `WindowManager` class: + +```csharp +public class NinjectWindowManager : WindowManager { + + public NinjectWindowManager(IKernel kernel, IAutoBinder autoBinder = null) : + base(autoBinder) { + this.kernel = kernel; + } + + protected override object CreateInstance(Type type) { + return this.kernel.Get(type); + } + + private IKernel kernel; +} +``` + +Your `NinjectWindowManager` will now use `IKernel.Get()` to construct its +ViewModels, allowing their constructors to require any services and instances +you have set up in your Ninject kernel. + +```csharp +class MainViewModel { + + public MainViewModel(IMyService myService, IMySettings mySettings) { + // ... + } + +} +```