Make a panel that contains variable size item.

This is the first post in my blog concerning about Panel.

To know more detailed and Chinese post about making Panel, please visit Higan's blog. I wrote this post aiming to clear my mind and share some code to you if you have the similar case.

Let's call the Panel VariableSizeItemPanel.

Here is the case:

  • This panel is used in ItemsControl based controls(e.g. ListView & GridView);

  • The width of each item is unknown and variable from each other;

  • The arrangement direction of items is left to right and if one line can't fit more one item(the width space is not enough) , this item should be placed in the next line.

A picture should say more than words:

Ok, lets' build the panel.

First, as we are building a panel, the control should derived from Panel class.

And there are two important method to override:

  • protected override Size MeasureOverride(Size availableSize)

  • protected override Size ArrangeOverride(Size finalSize)

The first one MeasureOverride returns the Size which specifies how much space the items in the panel would take. The param availableSize is the size specifies the space this panel can provide. To make it simple, let's say there are 5 items: each item has the same size of 200x100 and they stacks from top to down without any gaps. The return value of this method should be new Size(200,100*5);

If the size of each item in this panel is unknown, you should can get the DesiredSize of each item after calling item.Measure(availableSize);.

Here is the code:

protected override Size MeasureOverride(Size availableSize)
        {
            Size requestSize = new Size(availableSize.Width, 0);

            var rowsCount = 1;
            var occupiedWidth = 0d;

            //Get the desire size of each child
            foreach (var item in Children)
            {
                item.Measure(availableSize);

                var finalWidth = occupiedWidth + item.DesiredSize.Width;
                if (finalWidth > availableSize.Width)
                {
                    rowsCount++;
                    occupiedWidth = 0;
                }
                occupiedWidth += item.DesiredSize.Width;
            }
            //In this case, 50 is the fixed height of each item.
            requestSize.Height += 50 * rowsCount;
            return requestSize;
        }

The second one ArrangeOverride is the process that how the panel place each item. The param finalSize is the actual available size of the panel and the return param is kind of no use and we should just return the finalSize as normal.

To place an item in this panel, we should call item.Arrange(new Rect()) method. Note that the param Rect should contains the size and the coordinate value (X&Y) of this item in the panel.

Code:

protected override Size ArrangeOverride(Size finalSize)
        {
            var offsetX = 0d;
            var offsetY = 0d;
            var rowIndex = 0;
            _UIElements = new List<List<UIElement>>();
            _UIElements.Add(new List<UIElement>());
            foreach (var item in Children)
            {
                var finalX = offsetX + item.DesiredSize.Width;
                //Put this into the next line
                if (finalX > finalSize.Width)
                {
                    offsetY += item.DesiredSize.Height;
                    offsetX = 0;
                    rowIndex++;
                    _UIElements.Add(new List<UIElement>());
                }
                item.Arrange(new Rect(offsetX, offsetY, item.DesiredSize.Width, item.DesiredSize.Height));
                offsetX += item.DesiredSize.Width;
                _UIElements[rowIndex].Add(item);
            }
            return finalSize;
        }

How to used in ListView or GridView? Just change the ItemsPanel shown as below:

                <ListView
                    >
                    <ItemsControl.ItemsPanel>
                        <ItemsPanelTemplate>
                            <cc:VariableSizeItemPanel />
                        </ItemsPanelTemplate>
                    </ItemsControl.ItemsPanel>
                </ListView>