Databinding to a heterogeneous list

Go To StackoverFlow.com

3

I have a WPF UserControl with a ListBox and ContentPanel. The ListBox is bound to a ObservableCollection that has apples and oranges in it.

What is considered the proper way to have it setup so if I select an apple I see an AppleEditor on the right and if I select an orange an OrangeEditor shows up in the content panel?

2009-06-16 18:48
by Jake Pearson


5

I would suggest using DataTemplating to create and apply the different editors. Depending on how different your 'apples' and 'oranges' are I would recommend using a DataTemplateSelector. Also, if they had something like a Type property you could also use DataTriggers to switch out the editors.

Lets set up a small sample with apples and oranges. They'll have some shared properties, and a few different properties as well. And then we can create an ObservableCollection of the base IFruits to use in the UI.

public partial class Window1 : Window
{
    public ObservableCollection<IFruit> Fruits { get; set; }
    public Window1()
    {
        InitializeComponent();

        Fruits = new ObservableCollection<IFruit>();
        Fruits.Add(new Apple { AppleType = "Granny Smith", HasWorms = false });
        Fruits.Add(new Orange { OrangeType = "Florida Orange", VitaminCContent = 75 });
        Fruits.Add(new Apple { AppleType = "Red Delicious", HasWorms = true });
        Fruits.Add(new Orange { OrangeType = "Navel Orange", VitaminCContent = 130 });

        this.DataContext = this;
    }
}

public interface IFruit
{
    string Name { get; }
    string Color { get; }
}

public class Apple : IFruit
{
    public Apple() { }
    public string AppleType { get; set; }
    public bool HasWorms { get; set; }
    #region IFruit Members
    public string Name { get { return "Apple"; } }
    public string Color { get { return "Red"; } }
    #endregion
}

public class Orange : IFruit
{
    public Orange() { }
    public string OrangeType { get; set; }
    public int VitaminCContent { get; set; }
    #region IFruit Members
    public string Name { get { return "Orange"; } }
    public string Color { get { return "Orange"; } }
    #endregion
}

Next, we can create DataTemplateSelector, that will just check the type of the Fruit and assign the correct DataTemplate.

public class FruitTemplateSelector : DataTemplateSelector
{
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        string templateKey = null;

        if (item is Orange)
        {
            templateKey = "OrangeTemplate";
        }
        else if (item is Apple)
        {
            templateKey = "AppleTemplate";
        }

        if (templateKey != null)
        {
            return (DataTemplate)((FrameworkElement)container).FindResource(templateKey);
        }
        else
        {
            return base.SelectTemplate(item, container);
        }
    }
}

Then in the UI, we can create the two templates for Apples and Oranges, and use the selector to determine which gets applied to our content.

<Window x:Class="FruitSample.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:FruitSample"
    Title="Fruits"
    Height="300"
    Width="300">
<Window.Resources>

    <local:FruitTemplateSelector x:Key="Local_FruitTemplateSelector" />

    <DataTemplate x:Key="AppleTemplate">
        <StackPanel Background="{Binding Color}">
            <TextBlock Text="{Binding AppleType}" />
            <TextBlock Text="{Binding HasWorms, StringFormat=Has Worms: {0}}" />
        </StackPanel>
    </DataTemplate>

    <DataTemplate x:Key="OrangeTemplate">
        <StackPanel Background="{Binding Color}">
            <TextBlock Text="{Binding OrangeType}" />
            <TextBlock Text="{Binding VitaminCContent, StringFormat=Has {0} % of daily Vitamin C}" />
        </StackPanel>
    </DataTemplate>

</Window.Resources>

<DockPanel>
    <ListBox x:Name="uiFruitList"
             ItemsSource="{Binding Fruits}"
             DisplayMemberPath="Name" />
    <ContentControl Content="{Binding Path=SelectedItem, ElementName=uiFruitList}"
                    ContentTemplateSelector="{StaticResource Local_FruitTemplateSelector}"/>
</DockPanel>
</Window>
2009-06-16 20:23
by rmoore
Thanks for the super detailed answer - Jake Pearson 2009-06-16 20:28
Also, can I use a Template Selector to select between 2 different UserControls instead of separate templates - Jake Pearson 2009-06-16 20:31
If you want to switch between UserControls, then take a look at using a DataTrigger instead, and set the Content in the Trigger's setter - rmoore 2009-06-16 20:58
Ads