Get Microsoft Silverlight

Tuesday, April 22, 2008

A Simple Radial Panel for WPF and SilverLight

Geometry was one of the coolest topics in school for me. Probably because it uses a lot of figures to convey the concepts. Here I am trying to explain how we can leverage the Layout system in WPF and Silver light by taking an example of creating a Radial Panel. A RadialPanel lays out its children in a circular fasion. So before going in to the technical details, let us see how this problem get solved algorithmically.If you are asked to arrange a bunch of items in a circular path, what all things would you consider?.
1) How much space do I have? – Say X * Y rectangular area is the available size.
2) I will draw an approximate Circle (Or ellipse) inside the X*Y space. Assuming that the Radii are ‘Rx and Ry'
3) How many items are there to arrange? Say N – number
4) I know that 360 degrees are the total angular space for a circle. So angular distance between each item will be 360/N degrees.
6) I can calculate the coordinate point to which an item should be placed. That is by doing the following simple trigonometry operation. The coordinate point will be at Rx * Cos (Angle), Ry*Sin (Angle).
The math can be visualized from the figure bellow.




It is so interesting that WPF has kept the custom panel implementation in such a way that we can easily code the above steps directly in to code. First of all you have to create a class derived from the System.Controls.Panel. The two methods we need to override are

protected override Size MeasureOverride(Size availableSize) - In this pass you can do the following.


  1. Iterate the collection of children that are part of layout, call Measure() on each child element.

  2. Compute the net desired size of the parent based upon the measurement of the child elements.

protected override Size ArrangeOverride(Size finalSize) -



  1. Iterate the collection of children that are part of layout, call Arrange() function on each child element.

  2. Compute the net final size of the panel based upon the arrangement of the child elements.

Here is the implementation of the RadialPanel. Hope the comments in between explains the relvance of each step.


    public class RadialPanel : Panel

    {

        // Measure each children and give as much room as they want 

 

        protected override Size MeasureOverride(Size availableSize)

        {

            foreach (UIElement elem in Children)

            {

                //Give Infinite size as the avaiable size for all the children

                elem.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));

            }

            return base.MeasureOverride(availableSize);

        }

 

        //Arrange all children based on the geometric equations for the circle.

        protected override Size ArrangeOverride(Size finalSize)

        {

            if (Children.Count == 0)

                return finalSize;

 

            double _angle = 0;

 

            //Degrees converted to Radian by multiplying with PI/180

            double _incrementalAngularSpace = (360.0 / Children.Count) * (Math.PI / 180);

 

            //An approximate radii based on the avialable size , obviusly a better approach is needed here.

            double radiusX = finalSize.Width / 2.4;

            double radiusY = finalSize.Height / 2.4;

 

            foreach (UIElement elem in Children)

            {

                //Calculate the point on the circle for the element

 

                Point childPoint = new Point(Math.Cos(_angle) * radiusX, -Math.Sin(_angle) * radiusY);

                //Offsetting the point to the Avalable rectangular area which is FinalSize.

                Point actualChildPoint = new Point(finalSize.Width / 2 + childPoint.X - elem.DesiredSize.Width / 2,finalSize.Height / 2 + childPoint.Y - elem.DesiredSize.Height / 2);

 

                //Call Arrange method on the child element by giving the calculated point as the placementPoint.

                elem.Arrange(new Rect(actualChildPoint.X, actualChildPoint.Y, elem.DesiredSize.Width, elem.DesiredSize.Height));

 

                //Calculate the new _angle for the next element

                _angle += _incrementalAngularSpace;

 

            }

 

            return finalSize;

        }

    }


And finally the XAML file and its preview has shown bellow from VS2008 , And you can use the same Panel class in a SilverLight2.0 project with the same XAML usage .


The following screen shows all the System.Media.Colors in an ItemsControl which intern has RadialPanel as its ItemsPanel.

The XAML for this is pasted bellow.

<Window x:Class="WPFSample.Window1"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:local="clr-namespace:WPFSample" 

    Title="Window1" >

    <Window.Resources>        

        <ItemsPanelTemplate x:Key="radialTemplate">

            <local:RadialPanel />

        </ItemsPanelTemplate>        

        <DataTemplate x:Key="enumTemplate">

                <Rectangle Width="20" Height="20" Fill="{Binding Name}" ToolTip="{Binding Name}" Stroke="#FF000000"/>

        </DataTemplate>

    </Window.Resources>

 

    <ItemsControl Name="itemsControl" ItemTemplate="{DynamicResource enumTemplate}" ItemsPanel="{DynamicResource radialTemplate}"/>

 

</Window>


You will also need this in the code behind to set the ItemsSource property of the ItemsControl.

 itemsControl.ItemsSourcetypeof(Colors).GetProperties();



For more details about the WPF Panel concept, I recommend you to read Dr.WPF’s ItemsControl: 'P' is for Panel