Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Command Binding inside DataTemplate in Parent-Child Scenario in an MVVM Application

0.00/5 (No votes)
21 Feb 2011 2  
Discusses a simple and testable way to bind Commands inside DataTemplates (Parent-Child scenario) in any WPF or Silverlight application built with the MVVM design pattern

Introduction

This article will address one of the problems a developer might run into when binding commands inside DataTemplate in Parent-Child scenario in Model-View-ViewModel pattern. Basic knowledge of this pattern is expected. The demo application is created in Visual Studio 2010.

Example Scenario

The demo application discussed in this article is available for download at the top of this page. It contains a very simple application that allows a user to add, update and remove brand and add, remove product in a brand. If the user types a valid brand name and hits Add Brand button, the brand will be added and shown in the tabs. I have used Header Editable Tab Control (see my previous article on this, this time I edited the HeaderTemplate of TabItem to add a delete button). Inside a tab, the user can add and remove product under specific brand.

Sample application to add brand and products

Now, to develop the application, let's identify the Views and ViewModels first. Initially, one can say there is only one View and Viewmodel (i.e. BrandsView and BrandsViewModel). Well, let's have a deeper look.

ViewModels identified

It's clear now, right? The red border shows BrandsView and BrandsViewModel, the green and blue border identify (SingleBrandView, SingleBrandViewModel) and (ProductView, ProductViewModel) respectively. Let's concentrate on the class diagram now:

Class Diagram of ViewModels

The parent-child relation is clear as BrandsViewModel contains a collection of SingleBrandViewModel which has a collection of ProductViewModel. So, we have three Views corresponding to these three ViewModels. Let's code the BrandsView.

<UserControl x:Class="DemoApp.Views.BrandsView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:tab="clr-namespace:FormattedTabControl;
		assembly=FormattedTabControl"
             xmlns:vm="clr-namespace:DemoApp.ViewModels"
             xmlns:vw="clr-namespace:DemoApp.Views">
    <UserControl.Resources>
        <DataTemplate DataType="{x:Type vm:ProductViewModel}">
            <vw:ProductView />
        </DataTemplate>
        <DataTemplate DataType="{x:Type vm:SingleBrandViewModel}">
            <vw:SingleBrandView />
        </DataTemplate>
    </UserControl.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="15*" />
            <ColumnDefinition Width="70*" />
            <ColumnDefinition Width="15*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <TextBlock Text="Brand Name" Grid.Row="0" Grid.Column="0" Margin="5" />
        <TextBox x:Name="txtBrandName"
            Grid.Row="0" 
            Grid.Column="1"
            Margin="5" />
        <Button Grid.Row="0" 
                Grid.Column="2" 
                Margin="5" 
                Content="Add Brand"
                Command="{Binding AddBrand}"
                CommandParameter="{Binding ElementName=txtBrandName, Path=Text}"/>
        <tab:FormattedTab x:Name="tab"
                          Grid.ColumnSpan="3"
                          Grid.Row="1"
                          ItemSource="{Binding Brands}"/>
    </Grid>
</UserControl>

So, binding of AddBrand Command is trivial, but what about DeleteBrand? If we see the picture where we bordered the Viewmodels, the close button is actually inside the FormattedTabControl (FormattedTab is the custom TabControl used here). So, it seems that we should put DeleteBrand inside SingleBrandViewModel. Ok, I agree, but remember our SingleBrandViewModel collection is in BrandsViewModel. So, how do we will perform the delete operation? A simple answer is, we will put an event in SingleBrandViewModel, fire that when the Command executes and hook the event in BrandsViewModel when we add a Brand. This will certainly work, but a Brand should not raise an event to delete itself. This makes the ViewModel class coupled and hard to test. We can actually bind the command with parent ViewModel, BrandViewModel here using FindAncestor or ElementName in Binding expression. Let's see how we can do that inside the ItemContainerStyle of FormattedTabControl.

<Style x:Key="TabItemHeaderContainerStyle" TargetType="TabItem">
            <Setter Property="HeaderTemplate">
                <Setter.Value>
                    <DataTemplate>
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition />
                                <ColumnDefinition />
                            </Grid.ColumnDefinitions>
                            <local:EditableTabHeaderControl Grid.Column="0" 
				Style="{StaticResource EditableTabHeaderControl}">
                                <local:EditableTabHeaderControl.Content>
                                    <Binding Path="Header" Mode="TwoWay"/>
                                </local:EditableTabHeaderControl.Content>
                            </local:EditableTabHeaderControl>
                            <Button x:Name="cmdTabItemCloseButton" 
                                    Style="{StaticResource TabItemCloseButtonStyle}"
                                    Grid.Column="1" Margin="15,0,0,0"
                                    Command="{Binding RelativeSource=
					{RelativeSource FindAncestor, 
					AncestorType={x:Type TabControl}}, 
					Path=DataContext.DeleteBrand}"
                                    CommandParameter="{Binding}"/>
                        </Grid>
                    </DataTemplate>
                </Setter.Value>
            </Setter>
        </Style>

This is the style of FormattedTab's HeaderTemplate. It contains two columns, one for EditableTabHeaderControl and the other for a Close Button. Now, we have to bind the DeleteBrand command with this button. The FormattedTab's ItemSource is binded with Brands collection of BrandsViewModel. As each TabItem is binded with SingleBrandViewModel, so from this button, we have to find its ancestor TabControl and bind to the ancestor's DataContext. Similar case arises for the Remove button, it's datacontext is ProductViewModel but we put and bind the DeleteProduct in SingleBrandViewModel. So, at Remove button of ProductView, out target ancestor will be the ListBox which is in SingleBrandView.

<Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>
            <TextBlock Grid.Column="0" Text="{Binding ProductName}" Margin="5"/>
            <Button Grid.Column="1" Margin="5"
                    Content="Remove"
                    Command="{Binding RelativeSource=
			{RelativeSource FindAncestor, 
			AncestorType={x:Type ListBox}}, 
			Path= DataContext.DeleteProduct}"
                    CommandParameter="{Binding}" />
</Grid>

Have a close look at CommandParameter in both cases. The whole binded object is passed as parameter, which makes the Command's execution extremely easy. Have a look at Execute method for DeleteProduct Command:

private DelegateCommand<productviewmodel> deleteProduct;

public DelegateCommand<productviewmodel> DeleteProduct
        {
            get
            {
                return this.deleteProduct ?? (this.deleteProduct = 
				new DelegateCommand<productviewmodel>(
                                   this.ExecuteDeleteProduct,
                                   (arg) => true));
            }
        }

private void ExecuteDeleteProduct(ProductViewModel obj)
{
	if (this.Products.Contains(obj))
	{
		this.Products.Remove(obj);
	}
}

This is the way we can bind commands in parent-child scenario in MVVM pattern keeping the ViewModels more testable.

Code

The solution contains two projects, one for the demo application and the other for the FormattedTabControl.

Notes

I have used Prism's DelegateCommand class here. Also, for the style of close button in the custom TabControl, I looked at this four part article on WPF TabControl. Thanks to Olaf Rabbachin for writing such a wonderful article.

What Now?

I would like to hear your thoughts about this implementation. Share your ideas, just leave a comment...

History

  • 21st February, 2011: Initial post

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here