MeasureOverride and ArrangeOverride
To build an effective and appealing UI in WPF we should be aware of the layout process which takes places while creating the controls and in turn to understand the layout process we should be aware of the MeasureOverride() and ArrangeOverride() methods of the FrameworkElement class which can in turn help us to create custom panel in WPF.
MeasureOverride() and arrangeOverride() are the two methods which take part in the layout process of the WPF element tree. These methods basically use the two other methods provided by the UIElement class which are Measure() and Arrange() methods. To better understand this whole process of layout in WPF, first we need to know the class hierarchy of the controls in WPF.
Below figure should be able to make lots of thing clear about the class level hierarchy of the elements in WPF.
UIElement class from the above hierarchy contains two methods which are used in the layout process which I will discuss shortly. This class also contains the RenderTransform property which is used to do the transformations.
FrameworkElement class contains all the properties which help us to give the shape to an element. These properties are like Height, Width, All types of alignments and Margin. A FrameworkElement is best suited when you also want to provide custom sizing and positioning of elements, data binding, and styles.
Panel class contains a UIElementCollection property which contains all the elements of that class.
The Layout Process
The layout process is executed when the element is rendered for the first time. The layout system in WPF is a conversation between the layout container and its children.
This conversation is basically a two-step process which we will learn further.
- Step 1: Measure
- Step 2: Arrange
- Step 1(Measure): Following are the steps which takes places in the measure process.
- Each element calculates the desired size and determines how big it wants to be.
- This is achieved by the element by calling the Measure() method on each child and access each child’s desired size property. If the child element has more child elements in that case the child element also determines all its child’s desired size before returning its own desired size to the parent control
- The parent can access the desired size from the previous step and calculate its own desired size
- This process walks down the visual tree and starts at the top of level, that means the top most parent call the measure() method on each direct child and goes till end.
- After this step each element knows how big it wants to be and thus size is stored in desired size property of each element.
- Step 2 (Arrange): following are the steps which takes place in the arrange process.
- Each element arrange its child by calling Arrange() method on each direct child.
- The Arrange() method takes the final size(which we have calculated in Measure Step) and location i.e. the element knows the position where it has to render and the size which it has to render.
After these two steps the rendering occurs and element appears on the screen.
Layout Process Working
We can better understand the layout process when we should ourselves be able to create something which utilizes all the steps mentioned earlier.
And to better demonstrate the working of the MeasureOverride() and ArrangeOverride() methods I have created my own panel which arranges all the controls in a ‘V’ shaped layout as shown in the figure below. Though I am not sure where we should be able to use this king of panel but to show the demonstration I have created it.
NOTE: Please note that this panel only works if we have odd number of children(UIElements). I am leaving to reader to implement it for even number of UIElements
To create my own panel which should arrange elements in this particular shape we should first of all create a custom class derived from Panel class of WPF framework as shown in below code listing.
Please note that the coordinate system of WPF works ass shown below. To understand the below code we should know this thing.
Now in the below code listing you can see that I have implemented the overridden methods MeasureOverride() and ArrangeOverride()
public class DiagnolPanel:Panel { protected override Size MeasureOverride(Size availableSize) { var mySize = new Size(); foreach (UIElement child in this.InternalChildren) { child.Measure(availableSize); mySize.Width += child.DesiredSize.Width; mySize.Height += child.DesiredSize.Height; } return mySize; } protected override Size ArrangeOverride(Size finalSize) { var location = new Point(); int childNumber = 0; int middleChild = GetTheMiddleChild(this.InternalChildren.Count); foreach (UIElement child in this.InternalChildren) { if (childNumber < middleChild) { child.Arrange(new Rect(location, child.DesiredSize)); location.X += child.DesiredSize.Width; location.Y += child.DesiredSize.Height; } else { //The x location will always keep increasing, there is not need to take care of it location.X = GetXLocationAfterMiddleChild(childNumber); //If the UIElements are odd in number if (this.InternalChildren.Count % 2 != 0) { //We need to get the Y location of the child before middle location, to have the same //Y location for the child after middle child int relativeChildBeforeMiddle = middleChild - (childNumber - middleChild); location.Y = GetYLocationAfterMiddleChild(relativeChildBeforeMiddle); } else { ///TODO: Do the design for the even number of children } child.Arrange(new Rect(location, child.DesiredSize)); } childNumber++; } return finalSize; } private double GetXLocationAfterMiddleChild(int childNUmber) { double xLocation = 0; for (int i = 0; i < childNUmber; i++) { xLocation += this.InternalChildren[i].DesiredSize.Width; } return xLocation; } private double GetYLocationAfterMiddleChild(int relativeChildNumber) { UIElement correspondingChild = this.InternalChildren[relativeChildNumber - 2]; Point pointCoordinates = correspondingChild.TransformToAncestor((Visual)this.Parent).Transform(new Point(0, 0)); return pointCoordinates.Y; } private int GetTheMiddleChild(int count) { int middleChild; if (count % 2 == 0) { middleChild = count / 2; } else { middleChild = (count / 2) + 1; } return middleChild; } } }
And use this panel class in my xaml to arrange the UIElements as shown below.
<local:DiagnolPanel> <Button BorderBrush="Black" Background="Red" Content="0" Width="40"></Button> <Button BorderBrush="Black" Background="Red" Content="1" Width="40"></Button> <Button BorderBrush="Black" Background="Red" Content="2" Width="40"></Button> <Button BorderBrush="Black" Background="Red" Content="3" Width="40"></Button> <Button BorderBrush="Black" Background="Red" Content="4" Width="40"></Button> <Button BorderBrush="Black" Background="Red" Content="5" Width="40"></Button> <Button BorderBrush="Black" Background="Red" Content="6" Width="40"></Button> <Button BorderBrush="Black" Background="Red" Content="7" Width="40"></Button> <Button BorderBrush="Black" Background="Red" Content="8" Width="40"></Button> </local:DiagnolPanel>
Conclusion:
If we try to understand the layout process without proper example. it could be bit confusing and and it can be easily misunderstood.
I hope I have tried to explain the working of the layout process in WPF in a simple and better way.I have attached the sample code here XAMLLayoutBasic for your reference. Please let me know your thought about the article.