Unity & WPF - injecting a DataContext via property injection to a child control
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, Wind开发者_开发百科ow1ViewModel>();
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.
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
}
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.
There are two ways to do this. Following code snippet should clarify it.
//creating Container
IUnityContainer _container = new UnityContainer();
//Data Source
TasksListViewModel _tasksSource = new TasksListViewModel(); //My Data Source
_container.RegisterInstance<TasksListViewModel>(_tasksSource); //Registering it
//Resolve Window
Window1 window = _container.Resolve<Window1>();
//Answer to your question: Inject ViewModel into the View (User control)
//Two ways:
//1. Using Build (assuming View IS already added to the main window with the name "myView")
_container.BuildUp(typeof(TasksListView), window.FindName("myView"));
//---- OR ----
//2. Adding the view to the grid (assuming View IS NOT already added to the main window)
//Resolve View
//TasksListView view = _container.Resolve<TasksListView>();
//Make sure you have grid (content control) named LayoutRoot
//window.LayoutRoot.Children.Add(view); //Add it to the Main Window's grid (LayoutRoot)
window.Show();
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.
精彩评论