Search code examples
wpfcollectionsbindingdependencies

Pass a ObservableCollection<object> to UserControl


I have been trying to generalize the solution for passing ObservableCollection to a UserControl provided here:

How to bind collection dependency property in UserControl

I changed the code behind UserControl to:

/// <summary>
/// Interaction logic for myUserControl1.xaml
/// </summary>
public partial class myUserControl1 : UserControl
{
    #region Public Section
    public ObservableCollection<object> UCItems
    {
        get;
        set;
    }

    #endregion
    public myUserControl1()
    {
        InitializeComponent();

        UCItems = new ObservableCollection<object>();
    }

    #region UCItemsSource Property

    public static readonly DependencyProperty UCItemsSourceProperty = 
        DependencyProperty.Register("UCItemsSource", typeof(IEnumerable), typeof(myUserControl1));

    public IEnumerable UCItemsSource
    {
        get { return (IEnumerable)GetValue(UCItemsSourceProperty); }
        set { SetValue(UCItemsSourceProperty, value); }
    }

    #endregion
}

and changed TexBox to DataGrid in xaml:

<UserControl x:Class="OCasDPdemo.myUserControl1"
         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:local="clr-namespace:OCasDPdemo"
         mc:Ignorable="d" 
         d:DesignHeight="450" d:DesignWidth="800">
<Grid>
    <ItemsControl ItemsSource="{Binding Path=UCItemsSource, 
                                    RelativeSource={RelativeSource Mode=FindAncestor,
                                                                   AncestorType={x:Type UserControl}}}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <DataGrid x:Name="myDataGrid" ItemsSource="{Binding Path=UCItemsSource.Person}"/>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</Grid>

I populate the collection in a similar way to the original example:

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    public ObservableCollection<Person> WindowCollection
    {
        get;
        set;
    }
    public MainWindow()
    {
        InitializeComponent();

        DataContext = this;


        var bob = new Person { FirstName = "Bob", LastName = "Brown", Age = 32.1, ID = 101 };
        var jim = new Person { FirstName = "Jim", LastName = "Green", Age = 21.0, ID = 201 };
        var mel = new Person { FirstName = "Mel", LastName = "Black", Age = 20, ID = 111 };

        WindowCollection = new ObservableCollection<Person>() {bob, jim, mel };
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        var sue = new Person { FirstName = "Sue", LastName = "White", Age = 64.7, ID = 101 };
        var ted = new Person { FirstName = "Ted", LastName = "Grey", Age = 18.3, ID = 191 };

        WindowCollection.Add(sue);
        WindowCollection.Add(ted);
    }
}

and MainWindow xaml is:

<Grid>
    <StackPanel>
        <local:myUserControl1 UCItemsSource="{Binding Path=WindowCollection}" />
        <Button Content="Refresh" Click="Button_Click" />
    </StackPanel>
</Grid>

I am getting empty lines (same as the number of persons) instead of grid with columns. This setup works with native types like long and string types (with TextBox). Could somebody please let me know what I am doing wrong.


Solution

  • I haven't tried this out myself, but the problem seems to be in your myUserControl1 XAML.

    Just inside the root Grid is an ItemsControl with the ItemsSource bound to UCItemsSource. This means the ItemsControl will generate one ContentPresenter for each element in that collection- which in your case will be your list of Persons.

    Now, inside each of those ContentPresenters, an instance of your DataTemplate will be created. Your DataTemplate contains a DataGrid. This means you will get one entire DataGrid per Person.

    If instead you are trying to have one DataGrid with one row per Person, you might want something like this:

    <UserControl x:Class="OCasDPdemo.myUserControl1"
             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:local="clr-namespace:OCasDPdemo"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
        <Grid>
            <DataGrid x:Name="myDataGrid" ItemsSource="{Binding Path=UCItemsSource, 
                                                            RelativeSource={RelativeSource Mode=FindAncestor,
                                                                                           AncestorType={x:Type UserControl}}}">
                
            </DataGrid>
        </Grid>
    </UserControl>