ItemsPanelTemplate в XAML игнорирует атрибут [ContentProperty]

У меня есть пользовательская панель, где я объявила пользовательское свойство для хранения содержимого (я не хочу использовать Children для контента):

[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);
        }
    }
}

Это отлично работает при использовании следующим образом:

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

Но когда я хочу использовать панель в качестве элемента ItemsPanelTemplate внутри ItemsControl, атрибут ContentProperty игнорируется и добавляет все в коллекцию Дети, а не в коллекцию PanelContent:

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

Это не так, как должно работать. Согласно документации:

Элемент объекта ItemsPanelTemplate должен содержать ровно один класс, основанный на FrameworkElement, который служит в качестве корневого элемента для элементов. В большинстве случаев это класс, созданный Panel. Расширенный шаблон служит в качестве родителя для реализованных элементов, и в целом имеется более одного элемента. Поэтому свойство содержимого XAML для предполагаемого корневого элемента элемента ItemsPanelTemplate должно поддерживать коллекцию, как это делает Panel.Children.

Ответ 1

Метод GenerateChildren Panel, который отвечает за эту задачу, выглядит (как показано в ILSpy), например

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);
            }
        }
    }
}

Как вы можете видеть, он всегда добавляет к этому._uiElementCollection, который является полем, поддерживающим свойство Children.

Ответ 2

Я думаю, что ItemsPanelTemplate может принимать только Panel.Children как цель для элементов макета. Фактически, если вы выберете свой CustomPanel, скажем, ContentControl, вы найдете следующее сообщение об исключении в стиле Metro:

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

Связанная с документацией документа может быть ошибкой документации, которая была скопирована из времени WPF, когда вы все еще можете программно вставлять визуальные изображения в визуальное дерево. В приложении Metro больше нет способа разместить собственный список UIElements в визуальное дерево без использования Panel.Children.

Ответ 3

Поскольку ItemsPanelTemplate используется ItemsControl для создания и использования Panel, и это не XAML, который что-то делает. ItemsControl просто вызовет Panel.Children.Add независимо от того, для чего установлен ContentProperty.

Предполагается, что панель предназначена только для размещения детей и никогда не стирает их. Таким образом, все, что вы должны сделать os, только переопределяет методы Arrange and Measure. Все панели только делают это.

Для стилизации и любой другой логики вы должны использовать другой подход.