WPF UserControls and ViewModels

Topics: Bootstrappers & IoC, Framework Services, Getting Started, UI Architecture
Aug 1, 2011 at 12:04 AM

Hi,

  I'm trying to make use of multiple UserControls within another WPF window.  I initially had my form with a DockManager (DevExpress) but I have since simplified the layout to basically just have a WPF Grid Layout with 2 of my own UserControls.  I have the following 

GS.Views.StartupView.xaml and GS.ViewModels.StartupViewModel.cs
GS.Views.MissionView.xaml and GS.Views.MissionViewModel.cs
GS.Views.MapView.xaml and GS.Views.MapViewModel.cs

My bootstrap code sets up the StartupViewModel and the view is displayed correctly.  If I add a button to this form I can then open one of my custom UserControls as a window with the WindowManager and the ViewModel is correctly bound.

My issue is that if I add the MissionView and/or MapView to the StartupView.xaml the ViewModels never get bound.  I know this is a lack of understanding on my part - but I'm not sure whether I can expect this architecture to work with Caliburn Micro or not.  I have added the views to the parent using simple <Views:MapView /> tags.   

Ideally I would like to return to using my DockLayout Manager or something similar - with each LayoutPanel containing a UserControl (for maximum flexibility)... 

I was using the 1.1 release of CM from NuGet and also tried the latest source-code without any change.  Any thoughts would be great, I can attach a sample project if required.

Aug 3, 2011 at 4:36 PM

First a minor comment that you have inconsistent namespaces, I don't know if it makes any difference:

GS.Views.StartupView.xaml and GS.ViewModels.StartupViewModel.cs
GS.Views.MissionView.xaml and GS.Views.MissionViewModel.cs
GS.Views.MapView.xaml and GS.Views.MapViewModel.cs

 

Second, from your story I get the feeling that you change the views, and then want to have the VM bound automaticly. Is this correct?

Because CM usually works the other way around. You add/remove/change viewmodels, and CM finds the correct view for that. Every view usually is a user control. You have a ShellViewModel (maybe this is your StartupViewModel). The VM has normal C# properties which can be ViewModels themselves. For each property which is a viewmodel, you can make a content control in your shell view. This allows a composition of viewmodels.

 

Example:

 

public class ShellViewModel : Screen 
{
	public MissionViewModel Mission {get; private set; }
	public MapViewModel Map {get; private set; }

	// this gets called on button click
	public void Start() 
	{
		// CM binds content control with name of property to the value of the property
		Mission = new MissionViewModel(); 
		// as with any property you have to let WPF know property has changed.
		NotifyOfPropertyChange(() => Mission);
		Map = new MapViewModel(); 
		NotifyOfPropertyChange(() => Map);
	}
}

<UserControl x:Class="...ShellView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
        <StackPanel >
            <Button x:Name="Start"
                    Content="Start Mission and Map" />
	    <ContentControl x:Name="Mission" />
	    <ContentControl x:Name="Map" />
        </StackPanel>
</UserControl>
More info about composition: http://caliburnmicro.codeplex.com/wikipage?title=Screens%2c%20Conductors%20and%20Composition&referringTitle=Documentation

Aug 4, 2011 at 11:32 PM
alwinuz wrote:

First a minor comment that you have inconsistent namespaces, I don't know if it makes any difference:

GS.Views.StartupView.xaml and GS.ViewModels.StartupViewModel.cs
GS.Views.MissionView.xaml and GS.Views.MissionViewModel.cs
GS.Views.MapView.xaml and GS.Views.MapViewModel.cs

I have those in the correct namespaces in my project - it was just when I typed the post that I made the error.

By changing my ShellView to use ContentControls as you describe works perfectly for the situation where the Views are directly within a stackpanel or grid etc.  However if I place these inside DockLayout controls or other more complex layout controls I have a problem.  I've seen a few posts on here regarding this issue, and I think in this instance it's because I'm using DevExpress controls where they inherit from FrameworkContentElement instead of FrameworkElement (or something to this effect).

What are my options to overcoming this?  I don't mind if I have to explicitly declare the binding in my ShellView XAML - but when I do a simple binding it doesn't work.  Eg:

<LayoutControl>
  <LayoutGroup>
    <LayoutItem>
<ContentControl x:Name="Map" Content="{Binding Map}" />
... etc

When I do something similar to above - my binding just results in the content being "ApplicationName.ViewModels.MapVieWModel".

The other alternative I have seen mention of is overriding the Caliburn.Micro.BindingScope.GetNamedElements function. Would this be correct?

Thanks for your help so far - I'm starting to understand more - sorry for the slow uptake... ! :)

Aug 5, 2011 at 4:14 AM
Edited Aug 5, 2011 at 4:17 AM

edit:  don't bind the Content... like you have right now in that content control, as long as you have a property named Map, it will automagically find the named control in the tree and map accordingly.  Its like binding it twice.  Choose which way you want to show your view.  Either name it or bind it don't do both.

 

morgan.