Avoiding ContentPresenter in ItemsControl
Asked Answered
C

1

8

Is there a way to avoid generation of ContentPresenter that ItemsControl wraps my items in? My ItemsControl is bound to a VM property and I'm using a DataTemplate in my ItemControl's Resources (without an x:Key) to customize the look of my collection objects. This all works fine, but inspecting through Snoop shows that all my collection objects are wrapped inside ContentPresenters and not directly added to the Panel. This fact is creating some other issues for me. Is there a way to avoid the extra wrapping?

Here's the XAML:

<ItemsControl ItemsSource="{Binding Path=Children}">
  <ItemsControl.Resources>
    <DataTemplate DataType="{x:Type vm:Ellipse}">
      <Ellipse Fill="{Binding Fill}" Stroke="{Binding Stroke}" />
    </DataTemplate>
  </ItemsControl.Resources>
  <ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
      <Canvas Focusable="true" Margin="10" FocusVisualStyle="{x:Null}" />
    </ItemsPanelTemplate>
  </ItemsControl.ItemsPanel>
  <ItemsControl.ItemContainerStyle>
    <Style>
      <Setter Property="Canvas.Left" Value="{Binding XLoc}" />
      <Setter Property="Canvas.Top" Value="{Binding YLoc}" />
      <Setter Property="Canvas.ZIndex" Value="{Binding ZOrder}" />
    </Style>
  </ItemsControl.ItemContainerStyle>
</ItemsControl>
Commeasure answered 3/8, 2015 at 13:17 Comment(3)
You could probably create a derived ItemsControl and override the GetContainerForItemOverride method to directly return an Ellipse control.Gulfweed
@Clemens: Won't it expect me to return a container and not the actual item that is to be displayed (which on the other hand is being managed by the DataTemplate)?Commeasure
You wouldn't have a DataTemplate anymore, otherwise you would need the ContentPresenter.Gulfweed
G
6

You could create a derived ItemsControl and override its GetContainerForItemOverride method:

public class MyItemsControl : ItemsControl
{
    protected override DependencyObject GetContainerForItemOverride()
    {
        return new Ellipse();
    }
}

Your ItemsControl XAML wouldn't set the ItemTemplate anymore, and have an ItemContainerStyle that directly targets the Ellipse:

<local:MyItemsControl ItemsSource="{Binding Items}">
    <local:MyItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas/>
        </ItemsPanelTemplate>
    </local:MyItemsControl.ItemsPanel>
    <local:MyItemsControl.ItemContainerStyle>
        <Style TargetType="Ellipse">
            <Setter Property="Width" Value="100"/>
            <Setter Property="Height" Value="100"/>
            <Setter Property="Fill" Value="{Binding Fill}"/>
            <Setter Property="Stroke" Value="{Binding Stroke}"/>
            <Setter Property="Canvas.Left" Value="{Binding XLoc}"/>
            <Setter Property="Canvas.Top" Value="{Binding YLoc}"/>
            <Setter Property="Panel.ZIndex" Value="{Binding ZOrder}"/>
        </Style>
    </local:MyItemsControl.ItemContainerStyle>
</local:MyItemsControl>

As a note, in order to draw ellipses that are centered at XLoc and YLoc, you should replace the Ellipse control by a Path with an EllipseGeometry.

Gulfweed answered 3/8, 2015 at 13:39 Comment(2)
Hmmm... One thing: This ItemsControl binds to a collection that has several types of objects in it, Ellipse being one of them. GetContainerForItemOverride doesn't appear to be telling me which object does it need a container for. Is there a way to place a switch or if and return appropriate object each time?Commeasure
None that I'm aware of. If you would use a Path instead of an Ellipse, you could bind its Data property to different geometries from your view model. But honestly, having different view items for different view model items is exactly why there is an item container. So you should probably just keep the standard ItemsControl and select DataTemplates by the item type, either by means of default DataTemplates, or by the ItemTemplateSelector.Gulfweed

© 2022 - 2024 — McMap. All rights reserved.