ItemsControl with multiple DataTemplates for a viewmodel
Asked Answered
B

5

31

is it possible to bind an itemscontrol with canvas as template to multiple DataTemplates?

I have 2 collections and depending on the type I would like to display a different control on my canvas.

I am not sure but I could think about a Viewmodel which has 2 ObservableCollections. For example if I would have "Shapes" and "connections" and I would like to display them both on the canvas? In case of a diagraming scenario...

I would like to do this in the mvvm manner and I am not sure if the multiple DataTemplate approach is correct but this came to my mind. But I am still having problems to get the binding straight in my head. If I set the DataContext to the ViewModel for me it seems not possible to bind 2 collections to the items control... =( I am also open for other ideas, too....

Is this possible? And if so, how would the binding look like an

Barstow answered 29/3, 2011 at 13:8 Comment(3)
Do you need it BOTH for WPF and Silverlight?Foreplay
both would be nice long term... but first WPF would be great...Barstow
DataTemplateSelector will work for both WPF and Silverlight.Twicetold
E
62

You can create multiple ObservableCollections and then bind your ItemsSource to a CompositeCollection which joins those collections.

Then in your XAML you can create different DataTemplates for the respective types using the DataType property which like styles gets automatically applied if it is placed in the resources. (You can also create the composite in XAML which is shown on MSDN, if the CollectionContainers should be bound that is a bit more difficult though)

Example code:

ObservableCollection<Employee> data1 = new ObservableCollection<Employee>(new Employee[]
{
    new Employee("Hans", "Programmer"),
    new Employee("Elister", "Programmer"),
    new Employee("Steve", "GUI Designer"),
    new Employee("Stefan", "GUI Designer"),
    new Employee("Joe", "Coffee Getter"),
    new Employee("Julien", "Programmer"),
});
ObservableCollection<Machine> data2 = new ObservableCollection<Machine>(new Machine[]
{
    new Machine("E12", "GreedCorp"),
    new Machine("E11", "GreedCorp"),
    new Machine("F1-MII", "CommerceComp"),
    new Machine("F2-E5", "CommerceComp")
});
CompositeCollection coll = new CompositeCollection();
coll.Add(new CollectionContainer() { Collection = data1 });
coll.Add(new CollectionContainer() { Collection = data2 });
Data = coll;
<ItemsControl ItemsSource="{Binding Data}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.Resources>
        <DataTemplate DataType="{x:Type local:Employee}">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Name}"/>
                <TextBlock Text=" ("/>
                <TextBlock Text="{Binding Occupation}"/>
                <TextBlock Text=")"/>
            </StackPanel>
        </DataTemplate>
        <DataTemplate DataType="{x:Type local:Machine}">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Model}"/>
                <TextBlock Text=" - "/>
                <TextBlock Text="{Binding Manufacturer}"/>
            </StackPanel>
        </DataTemplate>
    </ItemsControl.Resources>
</ItemsControl>

Here i use a different panel but it should be the same for a canvas.

Encouragement answered 29/3, 2011 at 13:38 Comment(3)
Unfortunately, the COmpositeCollection isn't available for UWP Framework.Yeah
If Employee and Machine class are both inherit from the same class X, CompositeCollection is not needed and one ObservableCollection of X is enough.Marsha
@Viliam: Sure, but that is one major assumption which will not hold in many cases.Encouragement
S
5

You could have ObservableCollection<object> in your ViewModel and bind the ItemsControl's Source to this collection.

Then, to get a different look for different types of data, you could two DataTemplates without x:Key, but with properly set DataType in your Resources. The ItemsControl will then automatically select the appropriate DataTemplate for your item.

Seabrooke answered 29/3, 2011 at 13:18 Comment(0)
M
2

Have a look at the Data template selector: here or here.

Multiplechoice answered 29/3, 2011 at 13:16 Comment(0)
E
0

I believe there is a simpler solution compared to the accepted one, although I am using WPF without Silverlight for a desktop app:

Use the ItemsControl as before, but make the template for it use a ContentControl, which can easily do dynamic template switching based on type.

Models:

public class ViewModel : BindableBase
{
    /*
     *  Other stuff not relevant to the problem
     */

    public ObservableCollection<AbstractType> Properties { get; }
}
public abstract class AbstractType { }
public class ConcreteTypeP : AbstractType { }
public class ConcreteTypeQ : AbstractType { }

View:

<UserControl>
<!--I didn't include all the namesapce/viewmodel stuff in the UserControl attributes-->

<UserControl.Resources>
<!--These can be defined in a separate file for the sake of reuse. Not exactly sure on the syntax to correctly reference them though.-->
    <DataTemplate DataType="{x:Type local:ConcreteTypeP}">
        <!--text blocks and whatever other stuff go here-->
    </DataTemplate>

    <DataTemplate DataType="{x:Type local:ConcreteTypeQ}">
        <!--text blocks and whatever other stuff go here-->
    </DataTemplate>
</UserControl.Resources>

<DockPanel>
    <ItemsControl DockPanel.Dock="Top"
                  VerticalAlignment="Center"
                  ItemsSource="{Binding Properties}">
        <ItemsControl.ItemTemplate>
            <DataTemplate DataType="{x:Type local:AbstractType}">
                <ContentControl Content="{Binding}"/>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</DockPanel>
Ebracteate answered 25/7 at 21:8 Comment(0)
J
-6

Another option with less code behind would be to define two ListBoxes, each with their own templates and bound to their own collections. Define them each in the same physical space and just control which one is visible based on your state. You could even do this with the Visual State Manager and custom states.

Jacquelynnjacquenetta answered 29/3, 2011 at 14:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.