WPF MeasureOverride ArrangeOverride explained

By | July 22, 2015

This is a brief tutorial on WPF MeasureOverride ArrangeOverride functions. The details on these two are rather sketchy online and hence I decided to write this tutorial.

So what are these methods?
When do you need them?

As usual I will start with WHY first and then discuss the HOW.

WHY
Sometimes you want to change the way the elements are laid out. For example when you put a list of TextBlock elements as ItemsSource for a Listbox the default way the Listbox displays the elements is like this:

Simple ListBox

What if you want the ListBox to display elements like:

ListBox with custom Panel

e.g. First element on top, then second element at bottom, third element under top element, fourth element above the element at bottom an so on. A basic toggle just for demo.

HOW
One way is to derive from Panel class and then implement MeasureOverride and ArrangeOverride methods.

So what is the story of MeasureOverride and ArrangeOverride?
I will not go into gory details. MSDN is there for that.

To put it bluntly –
MeasureOverride : Is used by an element to calculate the area it needs to display itself properly.
ArrangeOverride : Is used to arrange the child elements in the containing element.

WPF layout system works in two stages:
Stage 1:

WPF layout system tells an element how much sizeĀ is actually available and then asks element how much size it needs to display itself. The process of finding the size the element needs is recursive.

Two methods are involved here. Measure() and MeasureOverride().

  • Measure() leads to a call to MeasureOverride().
  • Measure() has a void return type. It sets the DesiredSize property for the element.
  • The WPF layout system calls on the Measure() for the element of interest.
  • This Measure() call leads to a call to MeasureOverride() of the element.
  • In the MeasureOverride(), the element launches the Measure() method on each of the child elements it has.
  • This Measure() method then calls the MeasureOverride() for those child elements.
  • The return of the MeasureOverride() is then used by the Measure() method as one of the inputs to set the DesiredSize property of the child elements.
  • Finally the return value of the MeasureOverride() for the element of interest is used by the Measure() method as one of the factors to set the DesiredSize property of the element.

Stage 2:
In this stage the child elements are arranged inside the containing element. There are two methods involved here. Arrange() and ArrangeOverride(). Arrange() method triggers a call to ArrangeOverride() method.

Points to remember:

  • MeasureOverride takes a parameter availableSize of type Size. This tells the element how much space is available. This parameter has two fields, Width and Height. And they can have values from 0 to Positive Infinity.
    • When the Width and Height fields of availableSize parameter are zero then it means that there is no space for the element to be rendered.
    • Positive infinity means that there is no restriction on amount of space available. For example suppose the element is contained inside a ScrollViewer with HorizontalScrollBarVisibility and VerticalScrollBarVisibility set to Auto. In this case the “availableSize” size passed has Width and Height set to positive infinity.
    • A finite value in the Width and Height fields of availableSize parameter define how much space is available to the element. If the element finds that it needs size more than what has been passed to it then you can handle that situation by writing custom logic in the MeasureOverride() method.
    • MeasureOverride returns parameter of type Size. This is the size which the element wants for itself to display properly. It is usually the sum of the sizes of the child elements.
    • Do not return the availableSize parameter. There will be cases where the availableSize parameter will have the Width and Size parameter set to positive infinity. WPF layout system does not allow you to return a Size object having Width and Size parameter set to positive infinity. In case you return availableSize parameter you will see this exception :Layout measurement override of element ‘XYZ’ should not return PositiveInfinity as its DesiredSize, even if Infinity is passed in as available size.The reason is very simple. You cannot have an element tell the WPF layout system that is requires infinite size for displaying itself. How much area then WPF layout system will allocate to display that element? It cannot determine that. And hence the error. It is always good manners to find the amount of space the element needs and then return that only. Even if the available space is infinite. This makes sure that the available area is used optimally between the elements to be displayed.
    • Sometimes you can return a Size object having Width and Height set to zero. Even when the element has child elements inside it which require non zero area to display themselves. This means that the element does not want any size to display itself. It will work with whatever explicit size is set for it. Canvas will be one example.

Time for some Code. I will derive from the Panel class and implement the MeasureOverride and ArrangeOverride.

using System;
using System.Windows;
using System.Windows.Controls;

namespace CustomPanel
{
    public class DemoPanel : Panel
    {
        protected override Size MeasureOverride(Size availableSize)
        {
            Double childHeight = 0.0;
            Double childWidth = 0.0;
            Size size = new Size(0, 0);

            foreach (UIElement child in InternalChildren)
            {
                child.Measure(new Size(availableSize.Width, availableSize.Height));
                if (child.DesiredSize.Width > childWidth)
                {
                    childWidth = child.DesiredSize.Width;   //We will be stacking vertically.
                }
                childHeight += child.DesiredSize.Height;    //Total height needs to be summed up.
            }

            size.Width = double.IsPositiveInfinity(availableSize.Width) ? childWidth : availableSize.Width;
            size.Height = double.IsPositiveInfinity(availableSize.Height) ? childHeight : availableSize.Height;

            return size;
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            Boolean toggle = false;

            double yAxisHeight = 0.0;

            foreach (UIElement child in InternalChildren)
            {
                if (toggle == false)
                {
                    Rect rec = new Rect(new Point(0, yAxisHeight), child.DesiredSize);
                    child.Arrange(rec);
                    toggle = true;
                }
                else
                {
                    yAxisHeight += child.DesiredSize.Height;
                    Rect rec = new Rect(new Point(0, finalSize.Height - yAxisHeight), child.DesiredSize);
                    child.Arrange(rec);
                    toggle = false;
                }
            }
            return finalSize;
        }
    }
}

Let us go through the important steps to get a better understanding of what is happening here.

  • Line 13 : We create an object of type size and initialise it with default value of zero.
  • Line 15 : We iterate over each child of the element. We are using InternalChildren instead of Children to get the collection of children. The reason is that InternalChildren contains everything which the Children collection has + the children added via Data binding.
  • Line 17 : We call the Measure method on each child. We pass it the available size.
  • Line 18 : Once the Measure() method has been executed, the DesiredSize is set. We check if the desired width is greater than the width needed by previous elements. If yes then this becomes the new width. This allows us to calculate the maximum width needed by the element.
  • Line 22 : I keep adding the desired length of the child to the height property of the size. This will allow us to calculate the total height needed given the number of child elements.
  • Line 25 : If the availableSize had width set to positive infinity then the element returns only the width it needs (Good Manners !!!). Else it returns the width which was passed to it in the first place via availableSize.
  • Line 37 : Iterating over each child element. Note the use of InternalChildren.
  • Line 41 : Creating a Rect object and initialising it. It needs the object of type Point and Size. Note the use of DesiredSize property of the child. This proves very useful in some situations because it includes the margin set on the element. Essentially I am letting the child item to occupy the space it desires rather than me dictating it.

Here is how the XAML looks like.

<Window x:Class="CustomPanel.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:src="clr-namespace:CustomPanel"
        Title="MainWindow" Height="350" Width="300">
    <Grid>
        <ListBox ItemsSource="{Binding Path=DataItems}"
                 HorizontalAlignment="Left" Height="287" Margin="10,10,0,0"
                 VerticalAlignment="Top" Width="52">
            <ListBox.ItemsPanel>
                <ItemsPanelTemplate>
                    <src:DemoPanel />
                </ItemsPanelTemplate>
            </ListBox.ItemsPanel>
        </ListBox>
    </Grid>
</Window>
  • Line 4 : I add the reference to the source file comtaining the implementation of the custom panel.
  • Line 7 : I set the itemsource.
  • Line 10 – 14 : I set the ItemsPanelTemplate

And the code behind file is:

using System.Collections.Generic;
using System.Windows.Controls;

namespace CustomPanel
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow
    {
        public List<TextBlock> DataItems { get; set; }

        public MainWindow()
        {
            InitializeComponent();
            DataItems = new List<TextBlock>();

            for (int i = 0; i < 6; i++)
            {
                TextBlock elem = new TextBlock { Text = ((char)(i + 65)).ToString() };
                DataItems.Add(elem);
            }
            DataContext = this;
        }
    }
}

Nothing much to see here. Just that I am creating a list of TextBlock types and then setting it in the XAML as the ItemsSource for my listbox via databinding.

With this I think you can start off using WPF MeasureOverride ArrangeOverride methods. Ofcourse you can go as deep as you want. Usually Attached property and VirtualizingStackPanel join the party. Might write a post on them sometime in future.

 

 

One thought on “WPF MeasureOverride ArrangeOverride explained

  1. Arkane5

    This article gives a very good explanation and I find the toggling example interesting.

    I would like to point a small mistake (I wouldn’t have if the sentence wasn’t in bold, and this page the first result when searching for “wpf measureoverride example”).
    The statement “[…] InternalChildren contains everything which the Children collection has + the children added via Data binding.” is incorrect. They are both the same collection, as one can see in the source code. The Microsoft documentation is incorrect.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.