Search code examples
c#wpfdata-bindinguser-controls

How do I update a series of user controls based on a collection of data structures?


I have a user control BookViewControl which contains only a StackPanel, I also have a control named BookInfoControl which contains two TextBlocks in a WrapPanel. In the Codebehind for BookViewControl I have an Observable collection (Books) of a Data Structure (named BookInfo which contains two string properties, one for the author and the other for the books name).

What I want is to set it up in such a way that when I add or remove an item to the observable collection (Books) the Stack Panel in BookViewControl would update by adding or removing an instance of the BookInfoControl (where txtName of BookInfoControl is bound to the corresponding BookInfo's BookName property, and the txtAuthor of BookInfoControl would be bound to the same BookInfos AuthorName property).

Thus my problem is two-fold: 1) How do I dynamically create / remove UserControls based on a collection. 2) How to do I add those controls to another Controls StackPanel.

I know that I can bind an instance of BookInfo and it's properties to an instance of BookInfoControls TextBlocks (either in XAMl with Text={Binding SomeDataMember.Path} or in the code behind with instanceName.SetBinding(new binding{ Source = SomeDataMember, Path = new PropertyPath("Path") }); ). However doing so wouldn't cause new members of my collection Books to create a new instance of BookInfoControl nor would it solve the issue of adding it to the StackPanel.

I also considered putting in a kludge to manage a secondary collection of BookInfoControls and then sync it by hand but this seems too messy and I believe there should be an easier and cleaner way with WPF.

BookViewControl.xaml

<UserControl x:Class="GenericNamespace.BookViewControl "
             ... Default Generated Namespace Definitions ...
    >
    <Grid>
        <StackPanel x:Name="pnlCollectionOfBookInfos" />
    </Grid>
</UserControl>

BookInfoControl.xaml

<UserControl x:Class="GenericNamespace.BookInfoControl "
             ... Default Generated Namespace Definitions ...
    >
    <Grid>
        <WrapPanel>
            <TextBlock x:Name="txtName"/>
            <TextBlock x:Name="txtAuthor"/>
        </WrapPanel>
    </Grid>
</UserControl>

BookInfo.cs

public class BookInfo
{
    public string AuthorName { get; set; } = "";
    public string BookName { get; set; } = "";
}

BookViewControl.cs

    public partial class BookViewControl: UserControl
    {
        public ObservableCollection<BookInfo> Books { get; set; } = new ObservableCollection<BookInfo>();

        public BookViewControl()
        {
            this.DataContext = this;
            InitializeComponent();
        }
    }

In the end, modifying Books should cause a BookInfoControl to be displayed or removed in the StackPanel of BookViewControl where the text properties of BookInfoControls TextBlocks reflect the properties exposed in BookInfo.

Edit: The following link is something I found following the answer I got here and goes more into depth about this subject, hopefully it'll be of use to the next person who asks this: https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/data-templating-overview


Solution

  • Welcome to SO!

    Whenever you want to render a collection of items in WPF using data binding you have to use an ItemsControl or one of it's derived controls:

    <ItemsControl ItemsSource="{Binding Books}" />
    

    If you do this you'll see each book displayed as a string, you change that by declaring a DataTemplate for your Book type:

    <UserControl.Resources>
    
        <DataTemplate DataType="{x:Type local:BookInfo}">
            <local:BookInfoControl />
        </DataTemplate>
    
    </UserControl.Resources>