Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ItemsPanelTemplate in XAML ignores [ContentProperty] attribute

I have a custom Panel where I declared a custom property to hold the content (I don't want to use Children for the content):

[ContentProperty(Name = "PanelContent")]
public class CustomPanel : Panel
{
    public static readonly DependencyProperty PanelContentProperty =
       DependencyProperty.Register("PanelContent", 
       typeof(Collection<UIElement>), typeof(CustomPanel), 
       new PropertyMetadata(new Collection<UIElement>(), null));

    public Collection<UIElement> PanelContent
    {
        get
        {
            return (Collection<UIElement>)GetValue(PanelContentProperty);
        }
    }
}

This works perfectly when used like this:

<CustomPanel>
   <TextBlock>A</TextBlock>
   <TextBlock>B</TextBlock>
</CustomPanel>

But when I want to use the panel as an ItemsPanelTemplate inside ItemsControl, the ContentProperty attribute is ignored and adds everything to the Children collection, not the PanelContent collection:

<ItemsControl ItemTemplate="{StaticResource ReviewTemplate}" ItemsSource="{Binding Reviews}">
   <ItemsControl.ItemsPanel>
      <ItemsPanelTemplate>
         <CustomPanel></CustomPanel>
      </ItemsPanelTemplate>
   </ItemsControl.ItemsPanel>
</ItemsControl>

This is not how it should work. According to the documentation:

An ItemsPanelTemplate object element should contain exactly one FrameworkElement-derived class that serves as the root element for items. In most cases this is a Panel-derived class. The expanded template serves as the parent for the realized items and there generally is more than one item. Therefore the XAML content property of the intended root element of an ItemsPanelTemplate should support a collection, as Panel.Children does.

like image 496
Philippe Leybaert Avatar asked Jul 16 '12 09:07

Philippe Leybaert


2 Answers

The Panel's GenerateChildren method, that is responsible for this task looks (as seen in ILSpy) like

internal virtual void GenerateChildren()
{
    IItemContainerGenerator itemContainerGenerator = this._itemContainerGenerator;
    if (itemContainerGenerator != null)
    {
        using (itemContainerGenerator.StartAt(new GeneratorPosition(-1, 0), GeneratorDirection.Forward))
        {
            UIElement uIElement;
            while ((uIElement = (itemContainerGenerator.GenerateNext() as UIElement)) != null)
            {
                this._uiElementCollection.AddInternal(uIElement);
                itemContainerGenerator.PrepareItemContainer(uIElement);
            }
        }
    }
}

As you can see, it always adds to this._uiElementCollection, which is the field backing the Children property.

like image 162
Jens Avatar answered Nov 15 '22 12:11

Jens


I think the ItemsPanelTemplate can only take Panel.Children as the target to layout items. In fact, if you derive your CustomPanel from, say, ContentControl, you will find following exception message in metro style app:

Exception: The ItemsControl.ItemsPanelTemplate must have a derivative of Panel as the root element.

The documentation you linked might be a documentation bug which was copied from WPF time, when you still can insert visuals programmatically into visual tree. In Metro app, there's no longer a way to put your own list of UIElements into visual tree without using Panel.Children.

like image 37
Whyllee Avatar answered Nov 15 '22 12:11

Whyllee