When the layout behavior of the built-in panels do not meet our requirements, we can easily define a new panel with custom layout behavior. All we need to do is to declare a class that extends the Panel class and to override its MeasureOverride and ArrangeOverride methods.
In the MeasureOverride method, we simply call the Measure method on each child item from the Children collection, passing in a Size element set to double.PositiveInfinity. This is equivalent to saying "set your DesriredSize property as if you had all of the space that you could possibly need" to each child item.
In the ArrangeOverride method, we use the newly determined DesriredSize property value of each child item to calculate its required position and call its Arrange method to render it in that position. Let's look at an example of a custom panel that positions its items equally around the circumference of a circle:
using System; using System.Windows; using System.Windows.Controls; namespace CompanyName.ApplicationName.Views.Panels { public class CircumferencePanel : Panel { public Thickness Padding { get; set; } protected override Size MeasureOverride(Size availableSize) { foreach (UIElement element in Children) { element.Measure( new Size(double.PositiveInfinity, double.PositiveInfinity)); } return availableSize; } protected override Size ArrangeOverride(Size finalSize) { if (Children.Count == 0) return finalSize; double currentAngle = 90 * (Math.PI / 180); double radiansPerElement = (360 / Children.Count) * (Math.PI / 180.0); double radiusX = finalSize.Width / 2.0 - Padding.Left; double radiusY = finalSize.Height / 2.0 - Padding.Top; foreach (UIElement element in Children) { Point childPoint = new Point(Math.Cos(currentAngle) * radiusX, -Math.Sin(currentAngle) * radiusY); Point centeredChildPoint = new Point(childPoint.X + finalSize.Width / 2 - element.DesiredSize.Width / 2, childPoint.Y + finalSize.Height / 2 - element.DesiredSize.Height / 2); Rect boundingBox = new Rect(centeredChildPoint, element.DesiredSize); element.Arrange(boundingBox); currentAngle -= radiansPerElement; } return finalSize; } } }
In our CircumferencePanel class, we first declare our own Padding property of type Thickness, which will be used to enable the users of the panel to lengthen or shorten the radius of the circle and therefore, adjust the position of the rendered items within the panel. The MeasureOverride method is a simple affair, as previously explained.
In the ArrangeOverride method, we calculate the relevant angles to position the child items with, depending upon how many of them there are. We take the value of our Padding property into consideration when calculating the X and Y radiuses, so that users of our custom panel will be better able to control the position of the rendered items.
For each child item in the panel's Children collection, we first calculate the point on the circle where it should be displayed. We then offset that value using the value of the element's DesiredSize property, so that the bounding box of each item is centered on that point.
We then create the element's bounding box using a Rect element, with the offset point and the element's DesiredSize property, and pass that to its Arrange method to render it. After each element is rendered, the current angle is changed for the next item. Remember that we can utilize this panel by adding a XAML namespace for the Panels CLR namespace and setting the ItemsPanel property of an ItemsControl or one of its derived classes:
xmlns:Panels="clr-namespace:CompanyName.ApplicationName.Views.Panels;
assembly=CompanyName.ApplicationName.Views"
...
<ItemsControl ItemsSource="{Binding Hours}" TextElement.FontSize="24"
Width="200" Height="200">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Panels:CircumferencePanel Padding="20" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Given some suitable data, we could use this panel to display the numbers on a clock control, for example. Let's see the Hours property that the ItemsSource property of our example ItemsControl is data bound to:
private List<int> hours = new List<int>() { 12 }; public List<int> Hours { get { return hours; } set { hours = value; NotifyPropertyChanged(); } } ... hours.AddRange(Enumerable.Range(1, 11));
As the hour numerals must start with 12 and then go back to 1, we declare the collection with the 12 element initially. At some later stage, possibly during construction, we then add the remaining numbers to the collection and this is what it looks like when using our new panel:

This concludes our coverage of the main panels that are available in WPF. While we don't have the space to have an in-depth look at every other WPF control, we'll find tips and tricks for a number of them throughout this book. Instead, let's now focus on a few essential controls and what they can do for us.