The World’s Leading Microsoft .NET Magazine
   
 
The .NET Addict's Blog

My Top Tags

                                                           

My RSS Feeds








I heart FeedBurner

Latest Diggs - Programming

Computers Blogs - Blog Top Sites

Site Hits

Total: 4,907,398
since: 19 Jan 2005

Spotlight on Silverlight 2 : Creating a Radial Layout Panel

posted Thu 06 Mar 08

One of the new features in Silverlight 2.0 that people have been absolutely begging for since v1.1 Alpha showed up is dynamic layout. Any programmer who has been using WPF has learned to rely on the dynamic layout capabilities of the StackPanel, the Grid, the DockPanel, and so on. When Silverlight 1.1a came out and it looked like WPF, smelled like WPF, and acted like WPF, I was pretty sad when I realized that I couldn't do any dynamic layout - everything was pixel-based on a fixed Canvas. Screw that!

Now, however, we have Silverlight 2.0 and we have layout capabilities and we have access to the Panel class. One of the things that, oddly enough, I find myself wanting for many of my WPF applications is a panel that lays out its child items radially. The MSDN library actually has a radial layout sample for WPF, but it isn't practical at all... the results are actually skewed so if you lay out buttons with text on them, the text appears rotated as well. I also found a couple of compatibility issues since I think that sample was written for an early version of WPF because the call to Arrange() in that sample takes a Point and a Size parameter whereas the current version of WPF takes 2 points. So, to modify the MSDN sample so that it worked with both Silverlight and the current goodies, I had to forego the rotate transform and *gasp* do my own Sin and Cosine math!! oh noez!!1

So, without further ado, here's the source code to my radial layout panel (if the geometry sucks, too bad, this is a Silverlight demo, not a math class :)):

public class RadialPanel : Panel
{
    double _maxChildHeight, _perimeter, _radius, _adjustFactor;
    protected override Size MeasureOverride(Size availableSize)
    {
        _perimeter = 0;
        _maxChildHeight = 0;
        foreach (UIElement uie in Children)
        {
            uie.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
            _perimeter += uie.DesiredSize.Width;
            _maxChildHeight = Math.Max(_maxChildHeight, uie.DesiredSize.Height);
        }
        // If the marginal angle is not 0, 90 or 180
        // then the adjustFactor is needed.
        if (Children.Count > 2 && Children.Count != 4)
            _adjustFactor = 10;
        // Determine the radius of the circle layout and determine
        // the RadialPanel's DesiredSize.
        _radius = _perimeter / (2 * Math.PI) + _adjustFactor;
        double _squareSize = 2 * (_radius + _maxChildHeight);
        return new Size(_squareSize, _squareSize);
    }
    protected override Size ArrangeOverride(Size finalSize)
    {
        double _currentOriginX = 0,
                _currentOriginY = 0,
                _currentAngle = 0,
                _centerX = 0,
                _centerY = 0,
                _marginalAngle = 0;
        _radius -= _adjustFactor;
        _centerX = finalSize.Width / 2;
        _centerY = finalSize.Height / 2;
        if (Children.Count != 0)
            _marginalAngle = 360 / Children.Count;
        foreach (UIElement uie in Children)
        {
            _currentOriginX = _centerX - uie.DesiredSize.Width / 2;
            _currentOriginY = _centerY - _radius - uie.DesiredSize.Height;
            TranslateTransform shift = new TranslateTransform();
            shift.X = _centerX - uie.DesiredSize.Width / 2;
            shift.Y = _centerY - uie.DesiredSize.Height / 2;
            uie.RenderTransform = shift;
          
            double xLoc = _currentOriginX * Math.Sin(_currentAngle);
            double yLoc = _currentOriginY * Math.Cos(_currentAngle);
            uie.Arrange(new Rect(
                new Point(xLoc, yLoc),
                new Point(xLoc + uie.DesiredSize.Width, yLoc + uie.DesiredSize.Height)));
            _currentAngle += _marginalAngle;
        }
        return finalSize;
    }
}

Once you've got your radial layout panel, all you need to do is use it in your WPF the same way you would use any other content containing panel:

<UserControl x:Class="RadialDemo.Page"
    xmlns="http://schemas.microsoft.com/client/2007"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:custom="clr-namespace:RadialDemo"
    Width="450" Height="450">
    <Grid>
            <custom:RadialPanel x:Name="radialPanel">
                <Button Content="Hello"/>
                <Button Content="Hello 2"/>
     </custom:RadialPanel>
    </Grid>
</UserControl>

And finally, here's a screenshot of the layout panel in action:

If you're wondering why it looks like the panel is squished and not fully circular, its because the panel adapts to accomodate the shape of the child controls. The buttons I'm using are wider than they are tall, so the required width to accomodate the buttons is more than the required height. Usually, when I build these things, I run a second round of refactoring where I allow some horizontal overlap to avoid squishing the region... Maybe I'll get to that someday.

Anyway, I hope you enjoy the layout panel and I hope you start to realize how much different Silverlight 2.0 is from Silverlight 1.1!

tags:            

links: digg this    del.icio.us    technorati    reddit

AddThis Social Bookmark Button




1. Teifion left...
Thu 06 Mar 08 9:26 am :: http://woarl.com/blog

Radial menus are awesome, I code PHP for a hobby and I'm not thinking of making something that'll create one. As always, a good read.


2. Daren left...
Sat 08 Mar 08 4:04 pm

Hi - interesting control.

I noticed you are doing "_marginalAngle = 360 / Children.Count;" - Math.Sin, etc. use Radians so that should be "_marginalAngle = (Math.PI *2) / Children.Count;"


3. Jobi left...

Cool.. Radial Panel is really interesting one.. I also have one implemented here http://jobijoy.blogspot.com/2008/04/simple-radial-panel-for-wpf-and.html