Click here to Skip to main content
15,867,488 members
Articles / Desktop Programming / WPF

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

Rate me:
Please Sign up or sign in to vote.
4.81/5 (15 votes)
21 Feb 2011CPOL3 min read 116.2K   2.8K   22   19
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.

XML
<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.

XML
<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.

XML
<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:

C#
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Student University of Alabama at Birmingham
United States United States

A very simple guy, who codes with his heart (and with his brain, of course).

Homepage: http://www.amitdutta.net
View my profile on Linkedin: http://www.linkedin.com/in/am1tdutta/

Comments and Discussions

 
PraiseThank you so much. Pin
evnacdc31-May-17 10:59
evnacdc31-May-17 10:59 
QuestionPlease explain your magick in resources Pin
Виталий Белоусов29-Oct-16 9:19
Виталий Белоусов29-Oct-16 9:19 
Praisethanks Pin
Member 1241595330-Mar-16 13:41
Member 1241595330-Mar-16 13:41 
GeneralMy vote of 5 Pin
TheRealSteveJudge14-Jan-15 3:07
TheRealSteveJudge14-Jan-15 3:07 
GeneralThanks Pin
NaserAsadi4-Jan-13 19:30
professionalNaserAsadi4-Jan-13 19:30 
GeneralRe: Thanks Pin
Amit Kumar Dutta11-Aug-13 23:23
Amit Kumar Dutta11-Aug-13 23:23 
GeneralMulti Event Command Binder Pin
Member 432946918-Dec-11 7:27
Member 432946918-Dec-11 7:27 
GeneralMy vote of 5 Pin
CodeShoveler7-Mar-11 11:34
CodeShoveler7-Mar-11 11:34 
GeneralRe: My vote of 5 Pin
Amit Kumar Dutta3-Jun-11 5:15
Amit Kumar Dutta3-Jun-11 5:15 
GeneralMy vote of 5 Pin
RaviRanjanKr21-Feb-11 20:23
professionalRaviRanjanKr21-Feb-11 20:23 
Nice Article!
GeneralRe: My vote of 5 Pin
Amit Kumar Dutta22-Feb-11 3:50
Amit Kumar Dutta22-Feb-11 3:50 
GeneralMy vote of 5 Pin
Mahmudul Haque Azad21-Feb-11 17:28
Mahmudul Haque Azad21-Feb-11 17:28 
GeneralRe: My vote of 5 Pin
Amit Kumar Dutta22-Feb-11 3:50
Amit Kumar Dutta22-Feb-11 3:50 
GeneralMy vote of 5 Pin
Slacker00721-Feb-11 8:26
professionalSlacker00721-Feb-11 8:26 
GeneralRe: My vote of 5 Pin
Amit Kumar Dutta22-Feb-11 3:49
Amit Kumar Dutta22-Feb-11 3:49 
GeneralMy vote of 5 Pin
sayem.bd21-Feb-11 6:23
sayem.bd21-Feb-11 6:23 
GeneralRe: My vote of 5 Pin
Amit Kumar Dutta21-Feb-11 6:25
Amit Kumar Dutta21-Feb-11 6:25 
GeneralMy vote of 5 Pin
kdgupta8721-Feb-11 5:27
kdgupta8721-Feb-11 5:27 
GeneralRe: My vote of 5 Pin
Amit Kumar Dutta21-Feb-11 5:41
Amit Kumar Dutta21-Feb-11 5:41 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.