Nuclex.Windows.Forms.Ninject/Documents/basic-mvvm-template.md

5.0 KiB

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.

/// <summary>Setup code and main entry point for the application</summary>
internal static class Program {

  /// <summary>The application's main entry point</summary>
  [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
      );
    }
  }

  /// <summary>
  ///   Initializes services, creates the main window and keeps the application running
  ///   until the main window closes.
  /// </summary>
  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<IWindowManager>();
      Application.Run(windowManager.OpenRoot<MainViewModel>());
    }
  }

  /// <summary>Setups up dependency bindings for the application</summary>
  /// <param name="kernel">Dependency injector the bindings will be set up in</param>
  private static void setupBindings(IKernel kernel) {
    kernel.Load<MvvmModule>();
    kernel.Bind<IServiceProvider>().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.

/// <summary>Main window for the application</summary>
public partial class MainForm : ViewForm {

  /// <summary>Initializes a new main window</summary>
  public MainForm() {
    InitializeComponent();
  }

  /// <summary>The view model for the main window under its proper type</summary>
  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.

var windowManager = kernel.Get<IWindowManager>();
Application.Run(windowManager.OpenRoot<MainViewModel>());

Thanks to Ninject, our MainViewModel can have constructor parameters and Ninject will try to satisfy them by looking in its bound services:

/// <summary>View model for the application's main window</summary>
public class MainViewModel : ViewModel {

  /// <summary>Initializes a new view model for the main view</summary>
  /// <param name="windowManager">Window manager used to display child windows</param>
  /// <param name="messageService">Service to display messages to the user</param>
  public MainViewModel(
    IWindowManager windowManager,
    IMessageService messageService,
  ) : base(windowManager) {
    this.messageService = messageService;
  }

  /// <summary>Displays an example message to the user</summary>
  public void ShowExampleMessage() {
    this.messageService.Inform(
      new MessageText() {
        Caption = "Example",
        Message = "This is an example message."
      }
    );
  }

  /// <summary>Reports an error to the user</summary>
  /// <param name="exception">Error that will be reported</param>
  protected override void ReportError(Exception exception) {
    this.messageService.ReportError(
      new MessageText() {
        Caption = "Error",
        Message = "An error has occurred: " + exception.Message
      }
    );
  }

  /// <summary>Displays messages to the user</summary>
  private IMessageService messageService;

}