Binding a Dependency Property of a View to its ViewModel, part Deux

GetTheCode

I’ve been getting quite a few hits lately on my post, Binding a Dependency Property of a View to its ViewModel. After reviewing it I realized that, while the post isn’t too bad, it doesn’t read as smooth as I would like (though it does have some pretty pictures with arrows and boxes and stuff, oh my!). The code samples seem a bit lacking in describing the overall complexity of what needed to be done.

I know I always prefer to have actual code that demonstrates a concept and so I decided to take the sample code, which was incomplete, from my post Composite and Memento Design Patterns – A Real World Example and put together a complete solution.

two tabs

What you see here are the two tabs of the solution.  The first tab is simply a single instance of the control.  It works completely stand-alone and has it’s own View, ViewModel and Model.  The second tab is four instances of the control but there is a lot going on here with the binding on the controls to make everything work.

The  single instance has nothing bound to it other than it’s own view model.  The xaml for the control looks like:

<UserControl x:Class="CompositeMementoSample.Views.FileGroupView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:utility="clr-namespace:CompositeMementoSample.Infrastructure"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <UserControl.Resources>
        <utility:ViewModeToVisibilityConverter x:Key="modeToVisConv"/>
        <utility:InverseViewModeToVisibilityConverter x:Key="invModeToVisConv"/>
    </UserControl.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Label Grid.ColumnSpan="2" Grid.Row="0" Margin="5" Foreground="Orange" HorizontalAlignment="Center" FontSize="15"
                   Content="{Binding Title, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}"
                   Visibility="{Binding ViewMode, Converter={StaticResource invModeToVisConv}}" />
        <ListView Grid.Row="1" MinWidth="150" ItemsSource="{Binding CompositeList}" />
        <StackPanel Grid.Row="2" Orientation="Horizontal">
            <Button Command="{Binding AddFilesCommand}">Add Files</Button>
            <Button Command="{Binding SaveXmlCommand}" Visibility="{Binding ViewMode, Converter={StaticResource modeToVisConv}}">Save XML</Button>
            <Button Command="{Binding LoadXmlCommand}" Visibility="{Binding ViewMode, Converter={StaticResource modeToVisConv}}">Load XML</Button>
        </StackPanel>
    </Grid>
</UserControl>

The ViewModeToVisibilityConverter and the InverseViewModeToVisibilityConverter just show/hide controls based on what mode we’re in. The default is “Full” but when we want to use the control within other controls we assume that we want “Compact”. In compact mode we may not want to enable all features of the control and so we hide/show what we need to. Here, we want to only show a title when it’s part of another control but we only want to allow them to load and save the xml file that contains the list of files if it is a standalone control.

What is critical here is how the properties are exposed.

public partial class FileGroupView : UserControl
{
	public ViewMode ViewMode
	{
		get { return (ViewMode)GetValue(ViewModeProperty); }
		set { SetValue(ViewModeProperty, value); }
	}

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

	public string Title
	{
		get { return (string)GetValue(TitleProperty); }
		set { SetValue(TitleProperty, value); }
	}

	public static readonly DependencyProperty TitleProperty =
		DependencyProperty.Register("Title", typeof(string), typeof(FileGroupView), new PropertyMetadata(null));

	public FileGroupModel ActiveFileGroup
	{
		get { return (FileGroupModel)GetValue(ActiveFileGroupProperty); }
		set { SetValue(ActiveFileGroupProperty, value); }
	}

	public static readonly DependencyProperty ActiveFileGroupProperty =
		DependencyProperty.Register("ActiveFileGroup", typeof(FileGroupModel), typeof(FileGroupView), new FrameworkPropertyMetadata(null, OnActiveFileGroupChanged));

	private static void OnActiveFileGroupChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
	{
		((FileGroupViewModel)((FileGroupView)d).DataContext).ActiveFileGroup = (FileGroupModel)e.NewValue;
	}

	public FileGroupView()
	{
		OpenSaveDialogInterface dialogInterface = new OpenSaveDialogInterface();
		this.DataContext = new FileGroupViewModel(dialogInterface, dialogInterface);
		InitializeComponent();

		string propertyInViewModel = "ViewMode";
		var bindingViewMode = new Binding(propertyInViewModel) { Mode = BindingMode.TwoWay };
		this.SetBinding(ViewModeProperty, bindingViewMode);

		propertyInViewModel = "ActiveFileGroup";
		var bindingActiveFileGroup = new Binding(propertyInViewModel) { Mode = BindingMode.TwoWay };
		this.SetBinding(ActiveFileGroupProperty, bindingActiveFileGroup);
	}
}

The important bits here are the “this.SetBinding” which is setting up the binding between our dependency properties (which is what this is all about) and the view model.

Now that we’ve done all this, lets look at the xaml for the view that houses all four of these controls.

<UserControl x:Class="CompositeMementoSample.Views.MultiFileGroupView"
		 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
		 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
		 xmlns:views="clr-namespace:CompositeMementoSample.Views"
		 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
		 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
		 mc:Ignorable="d" 
		 d:DesignHeight="300" d:DesignWidth="300">
<Grid>
	<Grid.RowDefinitions>
		<RowDefinition />
		<RowDefinition />
		<RowDefinition Height="40" />
	</Grid.RowDefinitions>
	<Grid.ColumnDefinitions>
		<ColumnDefinition />
		<ColumnDefinition />
	</Grid.ColumnDefinitions>
	<views:FileGroupView Grid.Row="0" Grid.Column="0" 
		ViewMode="Compact" 
		Title="File Group 1"
		ActiveFileGroup="{Binding DataContext.FileGroup1, Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" />
	<views:FileGroupView Grid.Row="0" Grid.Column="1" 
		ViewMode="Compact" 
		Title="File Group 2"
		ActiveFileGroup="{Binding DataContext.FileGroup2, Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" />
	<views:FileGroupView Grid.Row="1" Grid.Column="0" 
		ViewMode="Compact"
		Title="File Group 3"
		ActiveFileGroup="{Binding DataContext.FileGroup3, Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" />
	<views:FileGroupView Grid.Row="1" Grid.Column="1"
		ViewMode="Compact"
		Title="File Group 4"
		ActiveFileGroup="{Binding DataContext.FileGroup4, Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" />
	<StackPanel Grid.ColumnSpan="2" Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right">
		<Button Margin="7" Command="{Binding SaveXmlCommand}">Save XML</Button>
		<Button Margin="7" Command="{Binding LoadXmlCommand}">Load XML</Button>
	</StackPanel>
</Grid>
</UserControl>

The binding to the file group is a bit tricky because you have to explicitly call out the property on the data context or else it won’t see it.

And that’s it. Most of the solution is really the MVVM parts: views, view models and support pieces. Hopefully you grab the solution from the “GET THE CODE” button at the top of the post as that is probably the best way to understand what is going on here.

Thanks for reading,
Brian

Comment (1)

  1. […] Binding a Dependency Property of a View to its ViewModel, part Deux (Brian Mullen) […]

Leave a Reply