11

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.

4

3 回答 3

2

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.

于 2012-07-26T16:06:28.013 回答
1

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.

于 2012-07-21T07:15:42.537 回答
0

Because ItemsPanelTemplate is used by ItemsControl to create and use the Panel and it's not XAML that does anything. ItemsControl will simply call Panel.Children.Add no matter what ContentProperty is set to.

Panel is supposed to only layout the children and never style them. So all you must do os only override Arrange and Measure methods. All panels only do that.

For styling and any other logic you must use another approach.

于 2012-07-19T19:13:23.360 回答