## 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.ItemsSource =  typeof(Colors).GetProperties();`

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

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

Kastor said...

Awesome ! Thank you for sharing this

Andreas said...

Thank you for sharing!

Prashant Jain said...

Hey .. thanks for this nice article.

morat said...

I feel lucky can read this useful news. Now I find something what i want to know.
Thank you for this great information.

Kenali dan Kunjungi Objek Wisata di Pandeglang | Blog SEO | cah bagoes | oes tsetnoc | blogger

Your blog is wonderful, I like it very much, thank you!
By the way, do you like Burberry Polos, which are very chic, especially the Ralph Lauren Polo Shirts, I love them very much. I also like playing sports, it can keep healthy, what do you like to do?
We are the outlet of Wholesale Polo Shirt, Polo Ralph Lauren shirts on sale,these products are best-seller in our store cheap polo shirts Cheap Polo ShirtsToday is Christmas, and our products also have a corresponding discount activitiesWholesale Polo Shirts -50% OFF,cheap polo shirts Cheap Polo ShirtsSo if you love these, you should not miss our store, we can meet what you want, and you can find many surprise in our store

Derin said...

It is a great work. And I am thankful to you.

sudarsan said...

Wow, that is great. Thanks

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

sergey miryanov said...

Thanks!

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?