views:

509

answers:

3

I followed Jason Dollinger's MVVM sample from Lab49 to learn the basics of using Unity with an MVVM WPF application. I constructed a simple sample following his basic architecture, using property injection and the Dependency attribute to inject viewmodels into the views. My sample has a main window with a child user control created in the window's XAML. The child control (and the main window, too) has a property for assigning the viewmodel:

[Dependency]
public IChildViewModel VM
{
    set { this.DataContext = value;}
}

I wire everything up in app.xaml.cs:

protected override void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);

    IUnityContainer container = new UnityContainer();

    container.RegisterType<IWindowViewModel, Window1ViewModel>();
    container.RegisterType<IChildViewModel, UserControl1ViewModel>();

    Window1 window = container.Resolve<Window1>();
    window.Show();
}

The main window is getting its viewmodel injected, but the child control is not. Is there any direct way of getting the resolution to propagate down into child controls? What kind of architectural changes would I need to make to do so? I'm not wedded to Unity at this point, so I can change to another container if this kind of behavior is supported.

A: 

If I consolodate my child view models to be owned and exposed as properties on the window's viewmodel, and then set the DataContext of the user controls in XAML to the appropriate property, then I can remove the Dependency-attributed property from the child code-behind altogether. It makes for a kind of clunky window viewmodel, though. and I'm not entirely happy with it. I based this approach from PL's answer to this related question.

Tim Trout
+2  A: 

The answer depends on whether the main windows 'owns' the child windows as a Composite View, or it creates new Views on the fly (for modal or non-modal child windows).

In the first case, the main ViewModel must own the child ViewModels directly, which means that you can implement the child ViewModels as read-only properties on the main ViewModel and use databinding to bind the child Views to the appropriate properties.

Whether you want to let the main ViewModel control the creation of the children directly or have them injected into it using Constructor Injection depends on the degree of variability you need.

As always, if you need to create new instances of child Views at arbitrary times, an injected Abstract Factory is a better model.


As an example, I often define and inject this interface into those of my ViewModels that need it:

public interface IWindow
{
    void Close();

    IWindow CreateChild(object viewModel);

    void Show();

    bool? ShowDialog();
}

This allows the ViewModel to create new windows and show them (e.g. as dialogs). A simple implementation looks like this:

public class WindowAdapter : IWindow
{
    private readonly Window window;

    public WindowAdapter(Window window)
    {
        if (window == null)
        {
            throw new ArgumentNullException("window");
        }

        this.window = window;
    }

    #region IWindow Members

    public void Close()
    {
        this.window.Close();
    }

    public IWindow CreateChild(object viewModel)
    {
        var cw = new ContentWindow();
        cw.Owner = this.window;
        cw.WindowStartupLocation = WindowStartupLocation.CenterOwner;
        cw.DataContext = viewModel;
        return cw;
    }

    public void Show()
    {
        this.window.Show();
    }

    public bool? ShowDialog()
    {
        return this.window.ShowDialog();
    }

    #endregion
}
Mark Seemann
A: 

I have also struggled with the concept on injecting DataContext into my views (UserControls).
The idea of exposing child viewmodels via the main window viewmodel has a limited amount of appeal?

The following idea works but you do get negative feedback from the Visual studio IDE.

My App.xaml.cs look like this:

protected override void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);

    UnityContainer unityContainer = new UnityContainer();
    this.Properties["UnityContainer"] = unityContainer; 
    unityContainer.LoadConfiguration();
    unityContainer.Resolve<MainWindow>().Show();
}

public static IUnityContainer UnityContainer 
{ 
     get
    { 
        return (IUnityContainer)App.Current.Properties["UnityContainer"]; 
    } 
}

I have registered my containers in App.config, but that’s just personal choice.

In my user control code behind I have the following:

protected override void OnInitialized(EventArgs e)
{
    base.OnInitialized(e);
    this.DataContext = App.UnityContainer.Resolve<MyViewModel>();
}

In the above instance MyViewModel is not registered and does not have an interface.

As I said before the above works for me but the IDE complains about not being able to create an instance of the user control. However if you start the application it works perfectly.

Allan