Whilst working on a proof of concept, we came up with an idea for an interesting menu in the form of an arc around a company logo at the top-left corner of the UI:
These icons formed a fish eye effect when the mouse moved over them and could be dragged onto the UI to open new windows.
Another project I worked on involved arranging similar icons around the mouse pointer on a particular key press to give fast access to a menu structure.
For these project, I created a simple layout panel which arranged elements in an arc or circle. The control is quite simple so I thought it would make a good example of creating a simple custom control in Silverlight.
First, create a new class called ArcPanel.cs which inherits from Panel.
namespace Controls{ using System; using System.ComponentModel; using System.Windows; using System.Windows.Controls; public class ArcPanel : Panel { public ArcPanel() { } }}
Now we need to add some dependency properties so that the properties can be set from XAML.
We’ll add properties to control the distance around the origin that the elements will be arranged as well as the start position and end position of the arc.
public static readonly DependencyProperty DistanceProperty = DependencyProperty.RegisterAttached("Distance", typeof(double), typeof(ArcPanel), null);public static readonly DependencyProperty StartAngleProperty = DependencyProperty.RegisterAttached("StartAngle", typeof(double), typeof(ArcPanel), null);public static readonly DependencyProperty EndAngleProperty = DependencyProperty.RegisterAttached("EndAngle", typeof(double), typeof(ArcPanel), null);
public static readonly DependencyProperty DistanceProperty = DependencyProperty.RegisterAttached("Distance", typeof(double), typeof(ArcPanel), null);
public static readonly DependencyProperty StartAngleProperty = DependencyProperty.RegisterAttached("StartAngle", typeof(double), typeof(ArcPanel), null);
public static readonly DependencyProperty EndAngleProperty = DependencyProperty.RegisterAttached("EndAngle", typeof(double), typeof(ArcPanel), null);
public double Distance{ get { return (double)GetValue(DistanceProperty); } set { SetValue(DistanceProperty, value); this.ArrangeItems(); }} public double StartAngle{ get { return (double)GetValue(StartAngleProperty); } set { SetValue(StartAngleProperty, value); this.ArrangeItems(); }}public double EndAngle{ get { return (double)GetValue(EndAngleProperty); } set { SetValue(EndAngleProperty, value); this.ArrangeItems(); }}
private int childrenCount;private double currentWidth;private double currentHeight;
Next we need to write a method to arrange the elements around the origin of the control. This method takes into account the distance, start angle and end angle properties.
private void ArrangeItems(){ this.childrenCount = this.Children.Count; double currentAngle = this.StartAngle; double angleInterval = (this.EndAngle - this.StartAngle) / this.childrenCount; foreach (UIElement element in this.Children) { double angle = Math.PI / 180 * (currentAngle - 90); currentAngle += angleInterval; double x = this.Distance * Math.Cos(angle); double y = this.Distance * Math.Sin(angle); element.Arrange(new Rect(x, y, this.currentWidth, this.currentHeight)); }}
private void ArrangeItems(){ this.childrenCount = this.Children.Count; double currentAngle = this.StartAngle; double angleInterval = (this.EndAngle - this.StartAngle) / this.childrenCount;
foreach (UIElement element in this.Children) { double angle = Math.PI / 180 * (currentAngle - 90); currentAngle += angleInterval;
double x = this.Distance * Math.Cos(angle); double y = this.Distance * Math.Sin(angle);
element.Arrange(new Rect(x, y, this.currentWidth, this.currentHeight)); }}
In the constructor we can set some default values in case these aren’t specified as properties. The following values arrange the elements in a full circle with radius of 50 pixels:
public ArcPanel(){ this.SizeChanged += new SizeChangedEventHandler(this.ArcPanelSizeChanged); this.Distance = 50.0; this.StartAngle = 0.0; this.EndAngle = 359.9;}
private void ArcPanelSizeChanged(object sender, SizeChangedEventArgs e){ if (this.currentWidth != e.NewSize.Width || this.currentHeight != e.NewSize.Height) { this.currentWidth = e.NewSize.Width; this.currentHeight = e.NewSize.Height; this.ArrangeItems(); }}
A couple of methods from the inherited Panel class need to be overridden. It will still work without these but it won’t have a proper width / height. First we override the MeasureOverride method:
protected override Size MeasureOverride(Size availableSize){ Size idealSize = new Size(0, 0); Size size = new Size(Double.PositiveInfinity, Double.PositiveInfinity); foreach (UIElement child in Children) { child.Measure(size); idealSize.Width += child.DesiredSize.Width; idealSize.Height = Math.Max(idealSize.Height, child.DesiredSize.Height); } if (double.IsInfinity(availableSize.Height) || double.IsInfinity(availableSize.Width)) { return idealSize; } return availableSize;}
protected override Size ArrangeOverride(Size finalSize){ if (this.Width != finalSize.Width || this.Height != finalSize.Height || this.childrenCount != this.Children.Count) { this.currentWidth = finalSize.Width; this.currentHeight = finalSize.Height; this.ArrangeItems(); } return base.ArrangeOverride(finalSize);}
That’s all that is needed to create the custom control for the layout panel.
To use it, we can add some XAML code to a page:
<Controls:ArcPanel Distance="70" StartAngle="0" EndAngle="359"> <Ellipse /> <Ellipse /> <Ellipse /> <Ellipse /> <Ellipse /> <Ellipse /> <Ellipse /> <Ellipse /></Controls:ArcPanel>
<Controls:ArcPanel Distance="70" StartAngle="0" EndAngle="359"> <Ellipse /> <Ellipse /> <Ellipse /> <Ellipse /> <Ellipse /> <Ellipse /> <Ellipse />
<Ellipse /></Controls:ArcPanel>
We can alter the angles to give an arc effect:
<Controls:ArcPanel Distance="70" StartAngle="30" EndAngle="240"> <Ellipse /> <Ellipse /> <Ellipse /> <Ellipse /> <Ellipse /> <Ellipse /> <Ellipse /></Controls:ArcPanel>
We can also add any type of UI element to the panel:
<Controls:ArcPanel Distance="70" StartAngle="0" EndAngle="359"> <Ellipse /> <Ellipse /> <Image /> <Button /> <TextBlock /> <Image/></Controls:ArcPanel>