3

In my project, I need to DataBind to a UserControl that resides in another UserControl. For the sake of brevity, I created a conceptually similar but very simple project.

Imagine that I am creating a Phone Book application, with two user controls in it, that looks like below.

enter image description here

The large blue box in the window would be one UserControl, which displays the owner's name (Jane Doe), and each of the yellow boxes within it are also UserControls, which display contacts' names and phone numbers.

I have two classes that I hold related data, as follows:

public class Person
{
    public string Name { get; set; }
    public string Phone { get; set; }
    public Person() { }
}

public class PhoneBook
{
    public string OwnerName { get; set; }
    public ObservableCollection<Person> ContactList { get; set; }
    public PhoneBook() { }
}

In my MainWindow, I use a ViewModel and bind to the PhoneBook UserControl like so:

<Window x:Class="UserControlDataBinding.MainWindow"
        Title="MainWindow" Height="300" Width="350"
        DataContext="{Binding Source={StaticResource mainViewModelLocator},Path=ViewModelPhoneBook}">
    <Grid>
        <local:UCPhoneBook x:Name="ucPhoneBook" MainPhoneBook="{Binding PhoneBookData}"></local:UCPhoneBook>
    </Grid>
</Window>

PhoneBookData is an instance of PhoneBook class on the ViewModel.

My two user controls, and their DependancyProperties look like below.

UCPhoneBook UserControl (the blue box):

Here I'm using an ItemsControl to dynamically bind the UCPerson UserControls so I can add as many as I like in runtime.

<UserControl x:Class="UserControlDataBinding.UCPhoneBook"
             d:DesignHeight="300" d:DesignWidth="450">
    <Canvas x:Name="ucCanvasPhoneBook" Background="White">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <GroupBox Grid.Row="0" Header="Phonebook">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition/>
                    </Grid.RowDefinitions>
                    <Label Grid.Row="0" Name="lblOwnerName" 
                           Content="{Binding Path=MainPhoneBook.OwnerName}">
                    </Label>
                </Grid>
            </GroupBox>

            <ItemsControl Grid.Row="1"
                          ItemsSource="{Binding PhoneBookData.ContactList}">
                <ItemsControl.ItemTemplate>
                    <DataTemplate DataType="{x:Type local:Person}">
                        <local:UCPerson PersonData="{Binding Person}"></local:UCPerson>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <StackPanel Orientation="Vertical"/>
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
            </ItemsControl>
        </Grid>
    </Canvas>
</UserControl>

Its DependancyProperty:

public partial class UCPhoneBook : UserControl
{
    private static readonly DependencyProperty PhoneBookProperty = DependencyProperty.Register("MainPhoneBook", typeof(PhoneBook), typeof(UCPhoneBook), new PropertyMetadata(null));
    public PhoneBook MainPhoneBook
    {
        get { return (PhoneBook)GetValue(PhoneBookProperty); }
        set { SetValue(PhoneBookProperty, value); }
    }

    public UCPhoneBook()
    {
        InitializeComponent();
        ucCanvasPhoneBook.DataContext = this;
    }
}

UCPerson UserControl (the yellow boxes):

<UserControl x:Class="UserControlDataBinding.UCPerson"
             d:DesignHeight="26" d:DesignWidth="400">
    <Canvas x:Name="ucCanvasPerson" Background="WhiteSmoke">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <Label Grid.Column="0" Name="lblName" 
                   HorizontalAlignment="Left" VerticalAlignment="Center"
                   Content="{Binding Name}"></Label>
            <Label Grid.Column="2" Name="lblPhone" 
                   HorizontalAlignment="Right" VerticalAlignment="Center"
                   Content="{Binding Phone}"></Label>
        </Grid>
    </Canvas>
</UserControl>

Its DependancyProperty:

public partial class UCPerson : UserControl
{
    private static readonly DependencyProperty PersonProperty = DependencyProperty.Register("PersonData", typeof(Person), typeof(UCPerson), new PropertyMetadata(null));
    public Person PersonData
    {
        get { return (Person)GetValue(PersonProperty); }
        set { SetValue(PersonProperty, value); }
    }

    public UCPerson()
    {
        InitializeComponent();
        ucCanvasPerson.DataContext = this;
    }
}

When I run this, I can see the Owner's Name at the top of the first UserControl (blue box) just fine. However, it doesn't seem to correctly bind the UCPerson user controls within, and I get an empty list like so:

enter image description here

My guess is that I'm not correctly binding to the ItemsControl inside the first UserControl. I'm pretty new to DataBinding and can't seem to figure out what the correct approach is.

What am I doing wrong here?

Sach
  • 10,091
  • 8
  • 47
  • 84

1 Answers1

6

This can all be greatly simplified.

First, get rid of every Canvas in your UserControls. Canvas isn't just a neutral panel/container control. The Canvases will cause everything to be superimposed. Only use a Canvas when you want to position children arbitrarily, and potentially superimposed. WPF layout usually uses "flow" and relative positioning. The standard layout parents are StackPanel, Grid, WrapPanel, and the occasional UniformGrid. You can omit the ItemsPanelTemplate for the ItemsControl, since the default is already a vertically-oriented StackPanel.

  • 1
    @Will It's the datacontext. – 15ee8f99-57ff-4f92-890c-b56153 Jan 03 '18 at 19:22
  • @EdPlunkett, thanks, that seems to work! I was following this (http://blog.scottlogic.com/2012/02/06/a-simple-pattern-for-creating-re-useable-usercontrols-in-wpf-silverlight.html) example, and tried to edit here and there. So basically I don't need DependancyProperty for this? – Sach Jan 03 '18 at 19:28
  • 1
    @Sach That model you chose unfortunately isn't ideal. To be fair it's six years old. The `LayoutRoot.DataContext = this;` thing is much better than the deservedly-reviled `DataContext = this;` (which I think some folks mistakenly thought you were doing), but it's still not regarded as good practice these days. – 15ee8f99-57ff-4f92-890c-b56153 Jan 03 '18 at 19:31
  • I am encountering a different problem now by the way, although probably not related. The `StackPanel` in the 'ItemsPanel' does not stack items, and instead they are on top of each other as seen here: https://i.imgur.com/O6hQzl9.png – Sach Jan 03 '18 at 19:33
  • @EdPlunkett thanks for the explanation! Think I will go back to the beginnings and try to understand the concept better, but this is a great starting point. – Sach Jan 03 '18 at 19:34
  • 1
    @Sach It works very well to provide UC's with their viewmodels as I illustrated above, but there are cases when you might also want to add a dependency property or two, just for purely visual stuff: For example, a parent view might want to pass in some brushes or styles to be applied to part of the UC. So you could write dependnecy properties for that stuff, and inside the UC XAML you would bind like `{Binding BlahStyleProp, RelativeSource={RelativeSource AncestorType=UserControl}}`. DPs on UCs aren't a Bad Thing -- merely not needful for what you were doing. – 15ee8f99-57ff-4f92-890c-b56153 Jan 03 '18 at 19:34
  • @Sach Looking at that screenshot... not sure what the deal is. Did you try omitting the `ItemsControl.ItemsPanel` element altogether? The default is a vertical stack panel anyhow. – 15ee8f99-57ff-4f92-890c-b56153 Jan 03 '18 at 19:37
  • I removed the ItemsPanel but still the same result. UserControls seem to be stacked up as in the screenshot. – Sach Jan 03 '18 at 19:41
  • 1
    @Sach I got it! It's the Canvases! Get rid of all the Canvases. They do weird things to layout, it's not just a neutral panel control. – 15ee8f99-57ff-4f92-890c-b56153 Jan 03 '18 at 19:48
  • You're right! I used a Canvas so I have more control over Background Color etc, but I'm sure I can achieve the same result without one. Thanks again! – Sach Jan 03 '18 at 19:51