Search code examples
c#wpfxamlitemscontrol

ItemsControl with multiple DataTemplates


I have a ItemsControl with a Canvas as ItemsPanelTemplate and multiple DataTemplates like this:

    <ItemsControl ItemsSource="{Binding Path=DisplayObjects}">

        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Canvas />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>

        <ItemsControl.Resources>
            <DataTemplate DataType="{x:Type viewModels:Object1ViewModel}">
                <views:Object1UIElement/>
            </DataTemplate>

            <DataTemplate DataType="{x:Type viewModels:Object2ViewModel}">
                <viewModels:Object2UIElement/>
            </DataTemplate>
        </ItemsControl.Resources >

        <ItemsControl.ItemContainerStyle>
            <Style TargetType="ContentPresenter">
                <Setter Property="Canvas.Left" Value="{Binding Path=X"/>
                <Setter Property="Canvas.Top" Value="{Binding Path=Y"/>
                <!-- Serveral more properties that are either attached to the Canvas or UIElement -->
            </Style>
        </ItemsControl.ItemContainerStyle>

    </ItemsControl>

The two viewmodels location (X,Y) is bonded to the Canvas Left and Top property. The problem is that I want to bind the viewModels properties to the Canvas in different ways.

For example Object1ViewModel use a MultiBinding converter that returns a value depending on factors like the size of the ItemControl, where Object2ViewModel should bind directly to the Canvas Left/Top properties like shown above.

I have tried to set the binding directly in the DataTemplate so I can have different binding styles for different DataTemplates but that does not work.. The objects will not be able to find the Canvas as they are created in a ContentPresenter.


Solution

  • So I figured it out with the comment from Clemens. As he mentioned I needed a ItemContainerStyleSelector so I made the class that would give me the appropriate style:

    public class MyItemContainerStyleSelector : StyleSelector
    {
        public override Style SelectStyle(object item, DependencyObject container)
        {
            var element = container as FrameworkElement; // this will be a ContentPresenter
            if (element == null) return null;
            var viewModel = element.DataContext;
            if (viewModel is Object1ViewModel)
            {
                return element.FindResource("Object1Style") as Style;
            }
            if (viewModel is Object2ViewModel)
            {
                return element.FindResource("Object2Style") as Style;
            }
            return null;
        }
    }
    

    In xaml I would define the styles in the resources and set ItemContainerStyleSelector to my new style selector:

    <UserControl.Resources>
        <utilities:MyItemContainerStyleSelector x:Key="MyStyleSelector"/>
    
        <Style x:Key="Object1Style" TargetType="ContentPresenter">
            ...
        </Style>
    
        <Style x:Key="Object2Style" TargetType="ContentPresenter">
            ...
        </Style>
    </UserControl.Resources>
    
    <ItemsControl
        ...
        ItemContainerStyleSelector="{StaticResource MyStyleSelector}" >
    </ItemsControl>
    

    When doing this the property ItemContainerStyle must not be set or the style selector will be ignored.