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

Comments

Anonymous said…
I am trying to incorporate a radialpanel in a listbox but it does not seem to scroll. I was hoping it would be as easy as changing the ItemsPanelTemplate to a radialpanel to have the list rotate. Is there an easy way to do this?

Thanks,

evan
Jobi Joy said…
This comment has been removed by the author.
Anonymous said…
Thanks this was useful!! :D
Anonymous said…
Awesome ! Thank you for sharing this
Unknown said…
Hey .. thanks for this nice article.
Derin said…
It is a great work. And I am thankful to you.
Anonymous said…
Thanks for this code. Took me a bit to get working since this was my first attempt at using a custom XAML class. Once I got it running, though it worked great. I couldn't get code you posted to create the color wheel to work, but I was able to use the code from the image you posted above as a template for laying out a series of images.
Orf Quarenghi said…
Thank you so much. C# + XAML is a difficult paradigm for a seasoned programmer used to think in terms of vertexes and triangles... You were of great help, thank you again.

Orf
DanTitan said…
When I use the RadialPanel inside any other element, the panel do not render in a circle but in a line. Is like the available height for the panel was 0. What could be the reason?

Popular posts from this blog

Time Picker User Control

A simple Multiselect ComboBox using expression Blend