27

I want to reuse my UserControls in other UserControls like page or window as DataTemplates, in this example inside a ListBox. Everything is MVVM.

I've a UserControl called "CardControl" to display a simple object "Card". Card has two Properties, "ID" and "CardImage". The controls DataContext is set via XAML. If I open this UserControl in VS or Blend it shows me the dummy Card that I have defined in the corresponding ViewModel.

Now I have another UserControl called "CardSetControl", which should display a collection of Cards. So the ViewModel has one property of type ObservableCollection<Card> called "Cards".

Here is the code:

<ListBox x:Name="MyList" ItemsSource="{Binding CardSet.Cards}">
  <ListBox.ItemTemplate>
    <DataTemplate>
      <StackPanel>

        <!-- WORKING, but not what i want -->
        <TextBlock Text="{Binding ID}" /> // would display ID of Card
        <Image Source="{Binding Image}" /> // would display Image of Card

        <!-- NOT WORKING, but this is how i want it to work -->
        <UserControls:CardControl DataContext="{Binding "Current listbox item as DataContext of CardControl???"}" />

      </StackPanel>
    </DataTemplate>
  </ListBox.ItemTemplate>
</ListBox>

After reading tons of articles about MVVM and DataContext/Binding I still didn't get it to work. How is this whole hierarchical USerControls/DataContexts thing done the best clean way?

4 Answers 4

25

In your example, the DataContext of the UserControl would be the currently selected Card. It flows into the UserControl and its child controls like any other UIElement receives the DataContext of its parent control.

This would work:

<ListBox x:Name="MyList" ItemsSource="{Binding CardSet.Cards}">
  <ListBox.ItemTemplate>
    <DataTemplate>
        <UserControls:CardControl />
    </DataTemplate>
  </ListBox.ItemTemplate>
</ListBox>

Where CardControl is:

<UserControl x:Class="MySolution.CardControl" 
             OtherProperties="Not shown to keep this example small">
      <StackPanel>
        <TextBlock Text="{Binding ID}" /> 
        <Image Source="{Binding Image}" />
      </StackPanel>
</UserControl>
4
  • Will, my apologies I basically gave the exact same answer as you. I should refresh the page before I answer next time ;) Commented Sep 12, 2011 at 20:43
  • 1
    This way a model (Card) gets passed as a DataContext to the user control. What if I would like to have that user control to use its view model? How should I pass this received model to a view model and bind the view model to the control's view? Commented Jul 16, 2014 at 11:55
  • 1
    @OndrejJanacek: UserControls should not be designed to have their own view models. They should have public bindable properties on their surface that users will bind to their own view models. This answer is shaped more for the OP than best practices :/
    – user1228
    Commented Jul 16, 2014 at 15:07
  • 1
    @Will That's very insightful information, thank you. Commented Jul 16, 2014 at 15:14
19

For the ListBox control an inferred ListBoxItem is created for each item in the items source. The Item is set as the DataContext and your ItemTemplate is set as the template. Since DataContext inherits, you don't have to explicitly set it because it's already the instance of Card in your DataTemplate.

For this case, you don't have to set the DC on the CardControl because it's set for you.

<ListBox x:Name="MyList" ItemsSource="{Binding CardSet.Cards}"> 
  <ListBox.ItemTemplate> 
    <DataTemplate> 
      <StackPanel> 

        <!-- WORKING, but not what i want --> 
        <TextBlock Text="{Binding ID}" /> // would display ID of Card 
        <Image Source="{Binding Image}" /> // would display Image of Card 

        <!-- NOT WORKING, but this is how i want it to work --> 
        <UserControls:CardControl /> 

      </StackPanel> 
    </DataTemplate> 
  </ListBox.ItemTemplate> 
</ListBox> 

Should work for you.

1

Thanks you for your answers, but I found out that my problem was another one that I had mentioned. If do it the way you described I can see my UserControls (CardControl) being used as template for the ListBox Items and the ID and the Image is displayed correctly.

Beside that I always wondered why ID and Image can be displayed while I can't bind to some other properties that I've defined in the ViewModel.

Today I found an interesting article about DataContext hierachy. There it is said that the DataContext inside a ListBox IS NOT the same DataContext as on the Page in which the ListBox is in. I didn't see that before so I thought I have to set the DataContext in some way like I mentioned in the question. Now I can bind to all properties.

Here is the article: http://blog.thekieners.com/2010/09/08/relativesource-binding-with-findancestor-mode-in-silverlight/

1
  • Sadly the site you refer to is perhaps unsurprisingly no longer around.
    – naskew
    Commented Dec 20, 2020 at 18:03
0

I had a similar issue and I fixed it using the below codes. I am posting here so that somebody else might benefit from it.

Please don't forget to upcast my answer, if it helps you.

EmpUserControl.xaml file

<UserControl x:Class="Sample.UserControls.EmpUserControl"
             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:uc="clr-namespace:Sample.UserControls"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="20"></RowDefinition>
            <RowDefinition Height="20"></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Label Grid.Row="0" Grid.Column="0">First Name: </Label>
        <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding FirstName}" />
        <Label Grid.Row="1" Grid.Column="0">Last Name: </Label>
        <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding LastName}" />
    </Grid>
</UserControl>

EmpUserControl.xaml.cs

namespace Sample.UserControls
{
    /// <summary>
    /// Interaction logic for EmpUserControl.xaml
    /// </summary>
    public partial class EmpUserControl : UserControl
    {
        public EmpUserControl()
        {
            InitializeComponent();
        }
    }
}

MainWindow.xaml file

<Window x:Class="Sample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Sample"
        xmlns:uc="clr-namespace:Sample.UserControls" 
        mc:Ignorable="d"
        Title="MainWindow" Height="600" Width="900" WindowStyle="SingleBorderWindow" 
        ResizeMode="NoResize">
    <Grid>
        <ListBox Name="LbEmp" Width="220" Height="220" >
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <uc:EmpUserControl DataContext="{Binding}"></uc:EmpUserControl>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>

MainWindow.xaml.cs file

namespace Sample
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    
        private void LoadEmpListBox(){
            var empList = new List<EmpViewModel>{
                new EmpViewModel { FirstName = "Rajeev", LastName = "Kumar" },
                new EmpViewModel { FirstName = "Sita", LastName = "Hedge" },
                new EmpViewModel { FirstName = "Deepika", LastName = "PL" }
            };
            LbEmp.ItemsSource = empList;
        }
    }
}

EmpViewModel model

public class EmpViewModel
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
2

Not the answer you're looking for? Browse other questions tagged or ask your own question.