We saw last time that setting up dependency injection for our viewmodels involved a small number of moving parts. The same applies when applying dependency injection to our views, but with a few additional complexities.

To select the appropriate view to match a given view model, let’s use a naming convention, again based on changing the suffix of the type. We’ll remove ViewModel from the name, adding either View or Window.

View Model Type Transformation View Type
VocabularyBrowserViewModel - "ViewModel" + "View" VocabularyBrowserView
WordTutorApplicationViewModel - "ViewModel" + "Window" WordTutorApplicationWindow

Our existing window is called MainWindow, which doesn’t follow this naming convention, so we rename it to WordTutorApplicationWindow.

Once the appropriate type has been selected, the view factory uses our dependency injection container to create the view. After the container is created, it sets the DataContext of the new view to hook up our data-binding:

var result = (ContentControl)_container.GetInstance(viewType);
result.DataContext = viewModel;

The rest of the implementation of ViewFactory is entirely similar to the ViewModelFactory that we wrote last time, so I won’t dig into the rest of the code here.

Our next challenge is to put the views into place as a part of our user experience. Fortunately, the design of WPF comes to our rescue.

When using WPF data binding to a view model, you can specify a converter to transform the value. These converters can be very simple - such as applying number formatting to a price - or relatively complicated.

We’re going to create a converter that accepts a view model and returns the right display component to present that view model.

By convention, these converters are named for the transformation they provide, so we end up with the slightly clumsy class name of ViewModelToViewValueConverter.

The converter needs access to our factory, so we declare the dependency in our constructor:

public class ViewModelToViewValueConverter : IValueConverter
{
    private readonly ViewFactory _viewFactory;

    public ViewModelToViewValueConverter(ViewFactory viewFactory)
    {
        _viewFactory = viewFactory;
    }

To do the actual conversion, we check that we’ve got a ViewModel, then delegate to the factory to do the heavy lifting:

    public object Convert(
        object value, 
        Type targetType, 
        object parameter, 
        CultureInfo culture)
    {
        if (value is ViewModelBase vm)
        {
            return _viewFactory.Create(vm);
        }

        return value;
    }

We don’t need to support converting back in the other direction, so we just throw an exception:

    public object ConvertBack(
        object value, 
        Type targetType, 
        object parameter, 
        CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

In theory, we could provide a conversion back by extracting the DataContext property from the view. However, given the data flow of the redux architecture (where all state flows out from the core of the application), this doesn’t seem necessary.

We now configure data binding to populate our window. Replace the exiting ContentControl with this declaration:

<ContentControl  
    Content="{Binding CurrentScreen, 
        Converter={StaticResource ViewModelToViewValueConverter}}"/>

(Note that I’ve added extra word wrapping here for clarity.)

We’re binding the content of our content control to the CurrentScreen property of our view model, using the converter we just created.

For this to work, we need to add the converter into a resource dictionary on the window, with the name ViewModelToViewValueConverter. Most value converters are directly declared in the XAML for the file, but we can’t do this here because we need our converter to be created by SimpleInjector.

Instead, we make the converter a parameter on our window constructor and add it into the resource dictionary ourselves:

public WordTutorWindow(ViewModelToViewValueConverter converter)
{
    Resources.Add("ViewModelToViewValueConverter", converter);
    InitializeComponent();
}

Troubleshooting tip: If you do this yourself and have the InitializeComponent() call first, you’ll get an exception complaining that the resource isn’t available.

Finally, we need to modify our Program to use the view factory to create our window. The code becomes much simpler because we’re delegating the hard work to our dependency injection framework. The hardest bit is that we need to get to the view factory so that we can create the window.

static void Main()
{
    var container = CreateContainer();
    var app = new App();
    var factory = container.GetInstance<ViewFactory>();
    var wordTutorModel = container.GetInstance<WordTutorViewModel>();
    var wordTutorWindow = (Window)factory.Create(wordTutorModel);

    app.Run(wordTutorWindow);
}

If you run the program now, you should have everything come up working with the word browser screen. Modifying the WordTutorApplicationStateFactory class, you can also see that it works with the add-word screen.

But, the application isn’t fully working yet; we can’t transition between the screens. There are two reasons for this. Our view models don’t get updated as the application state changes, and we haven’t hooked up any buttons yet.

Prior post in this series:
Dependency Injection: ViewModels
Next post in this series:
Redux Subscriptions

Comments

blog comments powered by Disqus