Click here to Skip to main content
15,894,540 members
Articles / Desktop Programming / WPF

WinRT - Custom WrapPanel

Rate me:
Please Sign up or sign in to vote.
4.33/5 (4 votes)
24 Sep 2012CPOL4 min read 35K   203   11  
How to create a custom (wrap) panel control for Windows RT / 8.
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace WrapPanelTest
{
    public class WrapPanel : Panel
    {
        #region Orientation Property
        //Gets or sets whether elements are stacked vertically or horizontally.
        public Orientation Orientation
        {
            get { return (Orientation)GetValue(OrientationProperty); }
            set { SetValue(OrientationProperty, value); }
        }

        public static readonly DependencyProperty OrientationProperty =
            DependencyProperty.Register("Orientation", typeof(Orientation), typeof(WrapPanel), new PropertyMetadata(Orientation.Vertical, OnOrientationPropertyChanged));

        private static void OnOrientationPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
        {
            (source as WrapPanel).InvalidateMeasure();
        }
        #endregion


        #region double BlockSize Property
        //Gets or sets the fixed dimension size of block.
        //Vertical orientation => BlockWidth
        //Horizontal orientation => BlockHeight
        public double BlockSize
        {
            get { return (double)GetValue(BlockSizeProperty); }
            set { SetValue(BlockSizeProperty, value); }
        }

        public static readonly DependencyProperty BlockSizeProperty =
            DependencyProperty.Register("BlockSize", typeof(double), typeof(WrapPanel), new PropertyMetadata(100.0, OnBlockSizePropertyChanged));

        private static void OnBlockSizePropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
        {
            (source as WrapPanel).InvalidateMeasure();
        }
        #endregion


        #region double BlockSpacing Property
        //Gets or sets the amount of space in pixels between blocks.
        public double BlockSpacing
        {
            get { return (double)GetValue(BlockSpacingProperty); }
            set { SetValue(BlockSpacingProperty, value); }
        }

        public static readonly DependencyProperty BlockSpacingProperty =
            DependencyProperty.Register("BlockSpacing", typeof(double), typeof(WrapPanel), new PropertyMetadata(0.0, OnBlockSpacingPropertyChanged));

        private static void OnBlockSpacingPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
        {
            (source as WrapPanel).InvalidateMeasure();
        }
        #endregion


        public WrapPanel()
            : base()
        {

        }

        protected override Size MeasureOverride(Size availableSize)
        {
            switch (Orientation)
            {
                case Orientation.Horizontal:
                    return MeasureOverrideHorizontal(availableSize);

                case Orientation.Vertical:
                default:
                    return MeasureOverrideVertical(availableSize);
            }
        }

        private Size MeasureOverrideVertical(Size availableSize)
        {
            //Create available size for child control
            //In Vertical orientation child control can have a maximum width of BlockSize
            //And it's height can be "unlimited" - I want to know what height would control like to have at given width
            Size childAvailableSize = new Size(BlockSize, double.PositiveInfinity);

            //Next, i want to stack my child controls under each other (i call it block), until i reach my available height. 
            //From that point i want to begin another block of controls next to the current one.

            int blockCount = 0;
            if (Children.Count > 0) //If i have any child controls, than i will have at least one block.
                blockCount = 1;

            var remainingSpace = availableSize.Height; //Set my limit as my available height.
            foreach (var item in Children)
            {
                item.Measure(childAvailableSize); //Let the child measure itself (result of this will be in item.DesiredSize
                if (item.DesiredSize.Height > remainingSpace) //If there is not enough space for this control
                {
                    //Then we will start a new block, but only if the current block is not empty
                    //if its empty, then remaining space will be equal to available height.
                    if (remainingSpace != availableSize.Height)
                    {
                        remainingSpace = availableSize.Height;
                        blockCount++;   //Reset remaining space and increase block count.
                    }
                }
                //In any case, decrease remaining space by desired height of control.
                remainingSpace -= item.DesiredSize.Height;
            }

            //Now we need to report back how much size we want,
            //thats number of blocks * their width, plus spaces between blocks
            //And for height, we will take what ever we can get.
            Size desiredSize = new Size();
            if (blockCount > 0)
                desiredSize.Width = (blockCount * BlockSize) + ((blockCount - 1) * BlockSpacing);
            else desiredSize.Width = 0;
            desiredSize.Height = availableSize.Height;

            return desiredSize;
        }

        private Size MeasureOverrideHorizontal(Size availableSize)
        {
            Size childAvailableSize = new Windows.Foundation.Size(double.PositiveInfinity, BlockSize);

            int blockCount = 0;
            if (Children.Count > 0)
                blockCount = 1;

            var remainingSpace = availableSize.Width;
            foreach (var item in Children)
            {
                item.Measure(childAvailableSize);
                if (item.DesiredSize.Width > remainingSpace)
                {
                    if (remainingSpace != availableSize.Width)
                    {
                        remainingSpace = availableSize.Width;
                        blockCount++;
                    }
                }

                remainingSpace -= item.DesiredSize.Width;
            }

            Size desiredSize = new Size();
            if (blockCount > 0)
                desiredSize.Height = (blockCount * BlockSize) + ((blockCount - 1) * BlockSpacing);
            else desiredSize.Height = 0;
            desiredSize.Width = availableSize.Width;

            return desiredSize;
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            switch (Orientation)
            {
                case Orientation.Horizontal:
                    return ArrangeOverrideHorizontal(finalSize);
                case Orientation.Vertical:
                default:
                    return ArrangeOverrideVertical(finalSize);
            }
        }

        private Size ArrangeOverrideVertical(Size finalSize)
        {
            //Each child control will be placed in rectangle with width of BlockSize
            //and height of child controls desired height.
            //Upper left corner of first controls rectangle will initialy start at 0,0 relative to this control
            //and move down by height of control and more to the left by BlockSize once the block runs out of free space
            double offsetX = 0;
            double offsetY = 0;
            foreach (var item in Children)
            {
                //If item will fit into remaining space, ....
                if ((finalSize.Height - offsetY) < item.DesiredSize.Height)
                {
                    if (offsetY != 0) //and the current block is not empty. (same rules as in measureoverride)
                    {
                        offsetX += BlockSpacing; //We will increse offset from left by the block size
                        offsetX += BlockSize;    //and spacing between blocks
                        offsetY = 0;             //and finally reset offset from top
                    }
                }
                //Create rectangle for child control
                Rect rect = new Rect(new Point(offsetX, offsetY), new Size(BlockSize, item.DesiredSize.Height));
                //And make it arrange within the rectangle, ...
                item.Arrange(rect);
                //Increment the offset by height.
                offsetY += item.DesiredSize.Height;
            }
            return base.ArrangeOverride(finalSize);
        }

        private Size ArrangeOverrideHorizontal(Size finalSize)
        {
            double offsetX = 0;
            double offsetY = 0;
            foreach (var item in Children)
            {
                if ((finalSize.Width - offsetX) < item.DesiredSize.Width)
                {
                    if (offsetX != 0)
                    {
                        offsetY += BlockSpacing;
                        offsetY += BlockSize;
                        offsetX = 0;
                    }
                }
                Rect rect = new Rect(new Point(offsetX, offsetY), new Size(item.DesiredSize.Width, BlockSize));
                item.Arrange(rect);
                offsetX += item.DesiredSize.Width;
            }
            return base.ArrangeOverride(finalSize);
        }
    }

}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Slovakia Slovakia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions