From c34d42a4b8f01559d1e28a6c08808da0974e9d37 Mon Sep 17 00:00:00 2001 From: Markus Ewald Date: Tue, 18 Jun 2024 19:47:20 +0200 Subject: [PATCH] Added some documentation for MVVM and a basic project template --- Documents/basic-mvvm-template.md | 153 +++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 Documents/basic-mvvm-template.md diff --git a/Documents/basic-mvvm-template.md b/Documents/basic-mvvm-template.md new file mode 100644 index 0000000..f360242 --- /dev/null +++ b/Documents/basic-mvvm-template.md @@ -0,0 +1,153 @@ +Basic MVVM Template (with Ninject) +================================== + + +`Program` Class +--------------- + +Your program entry point should be changed to the following code snippet. This is more complex +than the default one for .NET Windows Forms projects, but you'll have a fully usable Ninject +kernel, emergency error handling on missing dependencies and cleanup of any services that +implement `IDisposable`. + +```csharp +/// Setup code and main entry point for the application +internal static class Program { + + /// The application's main entry point + [STAThread] + static void Main() { + try { + + // Wrapping the application code in a method lets .NET's "fusion" linker to compile + // and run the Main() method, even when there are missing dependencies. This enables + // us to at least display some kind of error message rather than just fizzling out. + runApplication(); + + } + catch(Exception error) { + MessageBox.Show( + "Application failed to launch: " + error.Message, + "Error", + MessageBoxButtons.OK, + MessageBoxIcon.Error + ); + } + } + + /// + /// Initializes services, creates the main window and keeps the application running + /// until the main window closes. + /// + private static void runApplication() { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + + // Set up our dependency injector and all its bindings + using(var kernel = new StandardKernel()) { + setupBindings(kernel); + + // Then let the window manager create the view model and show the window + var windowManager = kernel.Get(); + Application.Run(windowManager.OpenRoot()); + } + } + + /// Setups up dependency bindings for the application + /// Dependency injector the bindings will be set up in + private static void setupBindings(IKernel kernel) { + kernel.Load(); + kernel.Bind().ToConstant(kernel); + } + +} +``` + +MainForm +-------- + +The main window should inherit from `ViewForm`. It won't change anything regarding how you use +Windows Forms designer and such, but it gives your main window a new property: `DataContext`. + +The name `DataContext` comes from WPF and UWP and is used for data binding. For example, a list +control would have a `DataContext` property to which you can assign any kind of list object to +make the list control display its contents. + +Similarly, the `DataContext` of your main window will be its view model. The `WindowManager` of +`Nuclex.Windows.Forms` checks if your Form inherits from `ViewModel` and automatically assigns +the view model to the `DataContext` if it can. + +```csharp +/// Main window for the application +public partial class MainForm : ViewForm { + + /// Initializes a new main window + public MainForm() { + InitializeComponent(); + } + + /// The view model for the main window under its proper type + private MainViewModel ViewModel { + get { return DataContext as MainViewModel; } + } + + // ... +} +``` + +MainViewModel +------------- + +As you could already see in the `Program` class at the top, rather than create and display +a form itself, the new `Program` class just asks the `WindowManager` to "open" +the `MainViewModel` and create its default view, which is discovered by name. Names it will +try are: `MainView`, `MainPage`, `MainForm`, `MainWindow`, `MainDialog` and `MainControl`. + +```csharp +var windowManager = kernel.Get(); +Application.Run(windowManager.OpenRoot()); +``` + +Thanks to Ninject, our `MainViewModel` can have constructor parameters and Ninject will +try to satisfy them by looking in its bound services: + +```csharp +/// View model for the application's main window +public class MainViewModel : ViewModel { + + /// Initializes a new view model for the main view + /// Window manager used to display child windows + /// Service to display messages to the user + public MainViewModel( + IWindowManager windowManager, + IMessageService messageService, + ) : base(windowManager) { + this.messageService = messageService; + } + + /// Displays an example message to the user + public void ShowExampleMessage() { + this.messageService.Inform( + new MessageText() { + Caption = "Example", + Message = "This is an example message." + } + ); + } + + /// Reports an error to the user + /// Error that will be reported + protected override void ReportError(Exception exception) { + this.messageService.ReportError( + new MessageText() { + Caption = "Error", + Message = "An error has occurred: " + exception.Message + } + ); + } + + /// Displays messages to the user + private IMessageService messageService; + +} +```