UPDATE 07/30/2014:
Please be sure to check out the second part to this at Binding a Dependency Property of a View to its ViewModel, part Deux which contains a working sample solution of the code here.

I like MVVM in concept and I had prepared a long, off-topic rant about MVVM because of a bit of trouble I’m having with it at the moment.  I deleted it, however.  Maybe I’ll make it an on-topic post at some point in the future.  Regardless, one of the issues I had was I needed to make a user control that could not only be the primary control of a view but I needed it to be a child control of another view.  There are a lot of complex operations that the view model controls in terms of handling states and progress bars.  It made sense, therefore, when I needed almost the exact same operations but with four different classes that inherit from the same model to use the same view.

To do this I needed a way to define a “ViewMode” of the user control.  For the default view where it would be the full version of the control the mode would be “Full” and for where I was using it as a child control the mode would be “Compact”.    Given that I think one of the things we should strive for is re-use it seemed like this should be an easy problem.  After some googling around though the general consensus seemed to be that you should just incorporate this into your new view model.  This just seems stupid to me.

Not about incorporating it into the view model.  That much is obvious but that there wasn’t some easy, obvious solution on how to bind a dependency property to a property in the view model.  Fortunately my StackOverflow foo seemed to be working with me and I found the right answer.

You have to manually bind your dependency property to the property on your view model.  It should have been obvious and I’m not sure why I hit on so many wrong answers but here is my solution:

Add the dependency property to MyView that will bind to the view model

public ViewMode ViewMode
{
	get { return (ViewMode)GetValue(ViewModeProperty); }
	set { SetValue(ViewModeProperty, value); }
}

public static readonly DependencyProperty ViewModeProperty =
	DependencyProperty.Register("ViewMode", typeof(ViewMode), typeof(MyView), new PropertyMetadata(ViewMode.Full));

I’ve defined an enum “ViewMode” that has two values, “Full” and “Compact” and I utilize it here.

Then in the ctor of MyView I have

public MyView()
{
	InitializeComponent();

	//<a href="http://stackoverflow.com/questions/15132538/twoway-bind-views-dependencyproperty-to-viewmodels-property" target="_blank">http://stackoverflow.com/questions/15132538/twoway-bind-views-dependencyproperty-to-viewmodels-property</a>
	string propertyInViewModel = "ViewMode";
	var bindingViewMode = new Binding(propertyInViewModel) { Mode = BindingMode.TwoWay };
	this.SetBinding(ViewModeProperty, bindingViewMode);
}

This manually sets the binding to a property “ViewMode” that is in my view model. So “ViewMode” exists in both the view and the view model and the manual binding will keep everything in sync.

In my XAML where I use the control as a stand-alone view the XAML looks like

<views:MyView Grid.Row="1" Margin="50" />

and in my XAML where I use the control as a child control the XAML looks like

<views:MyView Grid.Row="0" Grid.Column="0" ViewMode="Compact" />

I have also added some converters to help that bind the visibility of controls to the “ViewMode” of the view model. Now I have to point out that what I need to show and/or hide based on the ViewMode was fairly minimalistic. It might make more sense to have different control but in my case it made more sense to use the same control.

This is great and allows us to customize the view based on the ViewMode, but it doesn’t keep MyParentViewModel of the parent control in sync with MyViewModel of the child control when it’s being used as a child control. To do this we need to add another dependency property in MyView and bind it like we did the ViewMode. Then in MyParentView we have to bind that property to the property of MyParentViewModel in XAML.  The kind of crappy part is there is some manual work you have to do to make this work right.  Not only that but the binding works out to be fairly ugly.

The view model of “MyViewModel” looks like:

class MyViewModel
	ViewMode ViewMode
	ObjectGroup ActiveObject

The view model of “MyParentViewModel” looks like:

class MyParentViewModel
	ObjectGroup ActiveObjectOne
	ObjectGroup ActiveObjectTwo
	ObjectGroup ActiveObjectThree
	ObjectGroup ActiveObjectFour

In “MyView” I add the following dependency property:

public ObjectGroup ActiveObject
{
	get { return (ObjectGroup)GetValue(ActiveObjectProperty); }
	set { SetValue(ActiveObject, value); }
}

public static readonly DependencyProperty ActiveObjectProperty =
	DependencyProperty.Register("ActiveObject", typeof(ObjectGroup), typeof(MyView), new FrameworkPropertyMetadata(null, OnActiveObjectChanged));

private static void OnActiveObjectChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
	((MyViewModel)((MyView)d).DataContext).ActiveObject = (ObjectGroup)e.NewValue;
}

which is incredibly ugly. The problem is that when I do the binding on the dependency it directly calls SetValue and ignores my setter. As such I have to set things up with the property changed call-back to make everything tie together correctly.

Unfortunately I still need to add the manual binding to my view

propertyInViewModel = "ActiveObjectGroup";
var bindingActiveObjectGroup = new Binding(propertyInViewModel) { Mode = BindingMode.TwoWay };
this.SetBinding(ActiveObjectGroupProperty, bindingActiveObjectGroup);

Okay, so now everything works correctly right? Nope. The problem is that the binding on MyView sees the local data context, not the binding I want to give the control from MyParentView. As such I need to play with the binding to make sure we’re all on the same page.

The XAML basically ends up looking like

<UserControl x:Class="MyParentView">
	<UserControl.DataContext>
		<viewModels:MyParentViewModel />
	</UserControl.DataContext>
	<Grid>
		<Grid.RowDefinitions>
			<RowDefinition />
			<RowDefinition />
			<RowDefinition />
			<RowDefinition />
    		</Grid.RowDefinitions>
		<views:MyView Grid.Row="0"
				 ViewMode="Compact" 
				 ActiveObject="{Binding DataContext.ActiveObjectOne, Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" />
		<views:MyView Grid.Row="1"
				 ViewMode="Compact" 
				 ActiveObject="{Binding DataContext.ActiveObjectTwo, Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" />
		<views:MyView Grid.Row="2"
				 ViewMode="Compact" 
				 ActiveObject="{Binding DataContext.ActiveObjectThree, Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" />
		<views:MyView Grid.Row="3"
				 ViewMode="Compact" 
				 ActiveObject="{Binding DataContext.ActiveObjectFour, Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" />
	</Grid>
</UserControl>

What we’re doing here is changing the default “ActiveObject” in the child control from the one created by its view model to the one created by the parent view model. This way everything is in sync due to the bindings we created in the constructor of “MyView”.

When I bind like I do above the ObjectGroup instances from MyParentViewModel end up being the same as the instance of ObjectGroup in the MyViewModel.

The cool thing about all this is that I can create a new instance of MyParentViewModel as needed and it will set everything up as it should.

And that’s it. Maybe this is one of those answers that is so obvious no one felt the need to write about it. Or maybe everyone’s found the same answer I did. Or maybe this is the wrong approach. After all, it’s not very “MVVM-like”, having to manually create the bindings and then having to manually set the ActiveObject in the dependency property. If you know of a better way to do this let me know.

Thanks,
Brian

Leave a Reply

Your email address will not be published. Required fields are marked *

FormatException

928 East Plymouth Drive Asbury Park, NJ 07712