13

I am able to use an ItemTemplate within an ItemsControl to render items in a specific format. However, if one of the items within the ItemsControl happens to be, say, a TextBox, that TextBox is rendered rather than an instance of the ItemsTemplate. From what I can tell, this is true for any FrameworkElement. Is this intended behavior for an ItemsControl, or am I doing something incorrectly?

An example:

<ItemsControl>
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <Grid Margin="5">
        <Rectangle Fill="Blue" Height="20" Width="20" />
      </Grid>
    </DataTemplate>
  </ItemsControl.ItemTemplate>
  <ItemsControl.Items>
    <sys:Object />
    <TextBox />
    <sys:Object />
    <Rectangle Fill="Red" Height="20" Width="20" />
  </ItemsControl.Items>
</ItemsControl>

I expected this to display four blue rectangles. I thought that any time an ItemTemplate has been defined each item in the collection is rendered as an instance of the template. However, in this case the following is rendered: a blue rectangle followed by a TextBox followed by a blue rectangle followed by a red rectangle.

Drew
  • 817
  • 1
  • 11
  • 17
  • I'm guessing that this is intended behavior, and is intended to allow developers the ability to add special one-time use controls. For example, I might use this to add a Button to a ComboBox that clears the selection, or I might put a TextBox in a ListBox that filters the collection specified by ItemsSource. I would love to hear that someone has some official answer for this behavior because I found it counter-intuitive to the use of an ItemTemplate. – Drew Oct 01 '10 at 21:44
  • It certainly qualifies as a yuckky conflation of the UI and the data model. Ew. But done, one assumes, as an optimization which hopes to mitigate `ItemsControl` busywork whereby it has to wrap each and every POCO instance it encounters. Especially since the `ScrollViewer` in the `ItemsPanel` can quickly implicate hundreds of thousands of these so-called "containers". It would be probably better this was a purely opt-in feature (i.e. if the base call to `IsItemItsOwnContainerOverride` simply always returned `false`) rather than WPF barging in to assert the unexpected behavior you're reporting. – Glenn Slayden Feb 28 '21 at 23:06

2 Answers2

19

The ItemsControl has a protected member IsItemItsOwnContainerOverride which is passed an object from the items collection and returns true if that object can be added directly to the items panel without a generated container (and thereby be templated).

The base implementation returns true for any object that derives from UIElement.

To get the behaviour you would expect you would need to inherit from ItemsControl and override this method and have it always return false. Unfortunately thats not the end of the matter. The default implementation of PrepareContainerForItemOverride still doesn't assign the ItemTemplate to the container if the item is a UIElement so you need to override this method as well:-

    protected override bool IsItemItsOwnContainerOverride(object item)
    {
        return false;
    }


    protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
    {
        base.PrepareContainerForItemOverride(element, item);
        ((ContentPresenter)element).ContentTemplate = ItemTemplate;
    }
AnthonyWJones
  • 187,081
  • 35
  • 232
  • 306
  • 3
    As of 2015, they might have fixed the second part. With WPF in .NET 4.5.1, if I return `false` for `IsItemItsOwnContainerOverride,` then the template appears to be set on the item container. – Glenn Slayden May 18 '15 at 21:51
  • I didn't need the `PrepareContainerForItemOverride` override either in .NET 4. – Sheridan Jun 07 '19 at 14:07
2

I'm just speculating here, but I would bet that it's behavior that lives inside of the ItemContainerGenerator. I'd bet that the ItemContainerGenerator looks at an item, and if it's a UIElement it says, "cool, the item container's been generated, I'll just return it" and if it's not, it says, "I'd better generate a container for this item. Where's the DataTemplate?"

Robert Rossney
  • 94,622
  • 24
  • 146
  • 218
  • No, the logic is actually implemented in the `ItemsControl` itself, in its explicit interface implementation of `IGeneratorHost.GetContainerForItem(…)`, where it issues the `IsItemItsOwnContainerOverride` inquiry, and then either calls `GetContainerForItemOverride`—or casts the data item to `DependencyObject`. The latter is in fact the minimal requirement here, which begs the question why does the default behavior of `IsItemItsOwnContainerOverride` have a stricter requirement (namely, `UIElement`) than necessary? Maybe—as I alluded to above—to keep this WPF "feature" from kicking in even more. – Glenn Slayden Feb 28 '21 at 23:28