Click here to Skip to main content
15,867,141 members
Articles / Desktop Programming / WPF

Circular Context menu in WPF

Rate me:
Please Sign up or sign in to vote.
5.00/5 (6 votes)
18 Jul 2012CPOL3 min read 44.6K   1.9K   16   10
The purpose of this article is to illustrate a context menu that renders in cricular shape.

Introduction

Standard context menu is in rectangular shape. Now a days fancy UI is getting popular in LOB apps. Thanks to WPF framework. The purpose of this article is to illustrate a context menu that renders in cricular shape.

Sample Image

Background

The basic idea is to find points on circle using trignometery.

  1. x = centerX + cos (angle) * hypotenuse
  2. y = centerY + sin (angle) * hypotenuse
  3. Divde the circle into arcs that matches context menu item count. i.e If there are 2 context menu item, the circle will be created using 2 arcs.

Using the code

The usage is as below:

XML
<Grid.ContextMenu >
    <ContextMenu>
        <local:CustomMenuItem Header="Cut" Background="Red" 
             Foreground="Black" Click="CustomMenuItem_Click"/>
        <local:CustomMenuItem Header="Copy" Background="Green" Foreground="White" />
        <local:CustomMenuItem Header="Paste" Background="Blue" Foreground="Wheat" />
        <local:CustomMenuItem Header="View" Background="Aqua" Foreground="Black" />
        <local:CustomMenuItem Header="Tool" Background="Black" Foreground="Wheat" />
        <local:CustomMenuItem Header="Donate" Background="Chartreuse" Foreground="White" />
        <local:CustomMenuItem Header="Pend" Background="Brown" Foreground="Wheat" />
        <local:CustomMenuItem Header="Appr" Background="BlueViolet" Foreground="White" />
        <local:CustomMenuItem Header="Pend1" Background="Yellow" Foreground="Green" />
        <local:CustomMenuItem Header="Appr1" Background="Plum" Foreground="White" />
    </ContextMenu>
</Grid.ContextMenu>

The project contains 3 main files.

  • RoundPanel.cs - A custom panel derived from panel that layouts the context menu item. This panel can show menu items on a single track or on multitrack.
  • CustomMenuItem.cs - The class derives from MenuItem, that does the actual drawing of arc and text.
  • ContextMenu and ContextMenuItem styles - It is defiend in App.xaml's resource dictionary.

Let us look at RoundPanel.cs. In this class we override both MeasureOverride and ArrangeOverride.

Panel also has dependancy property "IsMultiTrack", based on its value the menu items will be arranged either on a single track or on multiple track.

In method "MeasureOverride" we find the required size for the ContextMenu panel. So that children can be rendered with out clipping. Method will look as below.

C#
protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize)
{
    Size infiniteSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
    double width = 30 + (this.InternalChildren.Count / 4 * 50);
    return new Size(width, width);
}

In "ArrangeOverride" we have various algorthim to find each childs Start and End Arc points. In case of "MultiTrack", it also find the required circle (Track) radius.

The algorthim to find number of arcs and position of each arc to render smooth cricle is in method ArrangeOverride and is shown below

C#
protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize)
{
    if (IsMultiTrack)
        DrawForMultiTrack(finalSize.Width / 2, finalSize.Height / 2);
    else
    {
        DrawForSingleTrack(finalSize.Width/2, finalSize.Height/2);
    }
    return finalSize; // Returns the final Arranged size
}

The method "DrawMultiTrack" contains the algorithm to draw menu items on multiple track. The algorithm is writtern such that, it start with 2 arcs for 1st track and then draws 4 arcs for remaining tracks. For each track the circle radius is calculated using

C#
hyp = 15 * track;

Various angles are determined by the below methods. E.g. Say for 2 menuItems, the required angles to draw ARCs (i.e. 2 Arcs - which required 4 points {0, 178, 180, 358})

Sample Image - maximum width is 600 pixels

Once Angles are determined. Next step is to calculate the arc start point and end point as shown below
C#
double startX = midX + Math.Cos(DegToRad(startAngle)) * hyp;
double startY = midY + Math.Sin(DegToRad(startAngle)) * hyp;
menuItem.StartX = startX;
menuItem.StartY = startY;

double x = midX + Math.Cos(DegToRad(endAngle)) * hyp;
double y = midY + Math.Sin(DegToRad(endAngle)) * hyp;
menuItem.X = x;
menuItem.Y = y;

The method "DrawForSingleTrack" as algorithm to determine various mesaurement for drawing arcs on single track. The circle radius is calulated using the below formula.

C#
int hyp = 15 * (this.InternalChildren.Count / 4 + 1);

End point of Arc is calculated using the below formula.

C#
int angleDisp = 360/this.InternalChildren.Count;
endAngle = angle + angleDisp - 2;

Once we have those values, next step is to find the arcs start point and end point.

The actual drawing is done by class CustomMenuItem.cs. It has method "OnRender". The class is derived from MenuItem.

C#
PathGeometry pathGeom = new PathGeometry();
PathFigure figure = new PathFigure() { IsClosed = false, IsFilled = false };
figure.StartPoint = new Point(StartX, StartY);
ArcSegment segment = new ArcSegment();
segment.IsLargeArc = IsLargeArc;
segment.Size = new Size(Hyp, Hyp);
segment.SweepDirection = SweepDirection.Clockwise;
segment.RotationAngle = 0;
segment.Point = new Point(X, Y);
figure.Segments.Add(segment);
pathGeom.Figures.Add(figure);

if (IsMouseOver)
    drawingContext.DrawGeometry(Brushes.Red, new Pen(Background, 20), pathGeom);
else
    drawingContext.DrawGeometry(Brushes.Red, new Pen(Background, 15), pathGeom);

This method also draws text in order to align with arc and is as below:

C#
if (this.Header == null)
    return;
string text = this.Header.ToString();
this.ToolTip = text; 
if (StartAngle >= 0 && EndAngle <= 180)
    text = new String(text.Reverse().ToArray());
else
    text = this.Header.ToString();

FormattedText formattedText ;
int angle = StartAngle + 10;
int angleSpace =( EndAngle - StartAngle )/ text.Length;
double x;
double y;
int idx = 0;
do
{
    x = MidX  -4  + Math.Cos(DegToRad(angle))*Hyp;
    y = MidY - 7 + Math.Sin(DegToRad(angle))*Hyp;
    formattedText = new FormattedText(
       text[idx++].ToString(),
       CultureInfo.GetCultureInfo("en-us"),
       FlowDirection.LeftToRight,
       new Typeface(this.FontFamily, this.FontStyle, this.FontWeight, this.FontStretch),
       this.FontSize,
       Foreground);
    angle += angleSpace;
     
    drawingContext.DrawText(formattedText, new Point(x, y));

} while (angle < EndAngle && idx < text.Length);

In order to use RoundPanel, we have to retemplate the ContextMenu and will be as shown below

XML
<Style TargetType="{x:Type ContextMenu}">
     <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type ContextMenu}">
                <local:RoundPanel IsItemsHost="True" 
                    KeyboardNavigation.DirectionalNavigation="Cycle" 
                    Background="Transparent"  IsMultiTrack="True" >
                </local:RoundPanel>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Also make sue to set the MenuItem DataContext to null. I did that using the below style.

XML
<Style TargetType="{x:Type local:CustomMenuItem}">
    <Setter Property="DataContext" Value="null" />
</Style>

Let'us rotate this context menu. In RoundPanel I used DispatcherTimer that ticks every millisecond and increment angle by one as shown below

C#
void _timer_Tick(object sender, EventArgs e)
{
    RotateTransform rotateTrans = this.RenderTransform as RotateTransform;
    if (rotateTrans != null)
    {
        rotateTrans.Angle = angle++;
        if (angle > 359)
            angle = 0;
    }
}

I also used event "MouseEvent" in class CustomMenuItem to notify the parent when mouse actions occur on MenuItem. Based on those actions, the timer will be stopped or started.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
I am a Programmer. Currently working with .Net Technolgies WPF/Silverlight/WCF/WEB API/ASP.Net MVC. I mainly use C# language for programming. I also focus on Javascript and JQuery. During free time, I write games for IOS and Andriod platform.

http://ajoshjose.blogspot.com/

Comments and Discussions

 
QuestionCircular Context Menu Pin
Appanda Raj25-Dec-13 19:43
Appanda Raj25-Dec-13 19:43 
Questionchange the size ? Pin
mr.me13-Aug-13 3:29
mr.me13-Aug-13 3:29 
QuestionText orientation Pin
Jaime Olivares23-Jul-12 5:12
Jaime Olivares23-Jul-12 5:12 
GeneralMy vote of 5 Pin
Carsten V2.018-Jul-12 8:42
Carsten V2.018-Jul-12 8:42 
QuestionInteresting, but... Pin
Kevin Marois18-Jul-12 6:29
professionalKevin Marois18-Jul-12 6:29 
AnswerRe: Interesting, but... Pin
prolapse18-Jul-12 11:07
prolapse18-Jul-12 11:07 
Kevin, the answer to your query is right there in your signature "If it's not broken, fix it until it is".
Trying out new ideas is how things progress. This idea may just inspire some great leap forward, nay-saying will not.
Deny everything.

AnswerRe: Interesting, but... Pin
Harley L. Pebley25-Jul-12 11:50
Harley L. Pebley25-Jul-12 11:50 
GeneralRe: Interesting, but... Pin
Kevin Marois25-Jul-12 11:58
professionalKevin Marois25-Jul-12 11:58 
GeneralRe: Interesting, but... Pin
Harley L. Pebley25-Jul-12 12:41
Harley L. Pebley25-Jul-12 12:41 
GeneralRe: Interesting, but... Pin
Ajosh Palis25-Jul-12 15:30
Ajosh Palis25-Jul-12 15:30 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.