DataBinding to a UserControl inside of a UserControl [duplicate]

爱⌒轻易说出口 提交于 2020-02-05 04:47:12

问题


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.

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:

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?


回答1:


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.

First fix UCPerson.xaml.cs:

public partial class UCPerson : UserControl
{
    public UCPerson()
    {
        InitializeComponent();
    }
}

Then fix the datatemplate:

            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <local:UCPerson />
                </DataTemplate>
            </ItemsControl.ItemTemplate>

The DataContext of a usercontrol should be the viewmodel that it displays. Each item in the ItemsControl is a Person. In the ItemTemplate for the ItemsControl, the DataContext is the item -- a Person. The UserControl in the DataTemplate will inherit that DataContext.

No need to set the DataType of the DataTemplate. That's not used here.

While we're here, the other user control would benefit from the same treatment:

public partial class UCPhoneBook : UserControl
{
    public UCPhoneBook()
    {
        InitializeComponent();
    }
}

XAML (partial)

                <Label Grid.Row="0" Name="lblOwnerName" 
                       Content="{Binding OwnerName}">
                </Label>
            </Grid>
        </GroupBox>

        <ItemsControl Grid.Row="1"
                      ItemsSource="{Binding ContactList}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <local:UCPerson />
                </DataTemplate>
            </ItemsControl.ItemTemplate>

MainWindow.xaml

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

By the way, where did you get this notion of having two names for the same property?



来源:https://stackoverflow.com/questions/48083648/databinding-to-a-usercontrol-inside-of-a-usercontrol

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!