Introduction
The circular menu component is a .NETTM user interface component that I developed as part of a usability class project. It is an animated menu popup that displays option icons in a circle around the position where the popup was launched.
Although I designed and wrote the code for the tool, my team members made several contributions to the philosophy behind the layout:
- Eric Engquist
- Joshua Correa
- Zianeh Kemmeh-Gama
Many thanks for their valuable input and suggestions.
Circular menus have a few advantages when compared to traditional popups:
- They look cooler: Users will appreciate the fresh look and feel associated with this type of menu, and it will add extra coolness to your application.
- They're more fun: The animation and new style of circular popups help to create a more "fun" atmosphere for your program. This can help users lower their guard and learn your application with less resistance.
- They can provide better user efficiency: Because menu options are arranged circularly around the click position, each option is equidistant from the user's mouse when the menu is originally shown. Circular menus are iconic, and can be thought of as a temporarily visible toolbar. Because users can find icons more rapidly than they can text, use of the circular popup can be faster than a normal popup, and because the popup appears where the user's focus is, they can even be faster than a normal toolbar.
There are, of course, disadvantages to a circular menu, a few of which are described here:
- Because information is mostly iconic, users will not be able to use a circular menu that does not provide effective visual metaphors. Although the menu supports tool tips, users will not be able to use the menu efficiently if they need to read the text for each item before they can find it. On the other hand, once they've used each option once, they will be able to associate the general meaning with the icon more easily.
- The circular arrangement means that the menu's overall dimensions must increase with additional options: the more options, the bigger the menu. This is especially true when the icons in the menu are larger. This can limit the number of options you would place within a circular menu as opposed to traditional variations.
- The circular menu will only look as good as you make it. Extra attention will need to be paid to the development of the icons to make them look professional and modern, and few software developers are graphic artists on the side. Fortunately, however, there is a wealth of simple icons available on both the web and from MicrosoftTM Visual StudioTM. While the Visual Studio icons are small and won't look as good as professionally designed icons, the addition of menu animations and drop shadows will help ensure that your popup still looks cool.
This document provides a generic overview of the structure of the circular menu component, as well as a tutorial on its use in third-party applications.
License Agreement and Disclaimer of Warranty
THIS SOFTWARE AND THE ACCOMPANYING DOCUMENTATION ARE PROVIDED �AS IS�, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. USE IT AT YOUR OWN RISK. THE AUTHOR ACCEPTS NO LIABILITY FOR ANY DAMAGE TO OR LOSS OF DATA THAT THIS PRODUCT MAY CAUSE.
When modified, this product (source code, binaries, and documentation) may be freely redistributed for both commercial and personal use. Acceptable modifications are defined to be: additions, meaningful changes to the source code, or inclusion of the source code or binaries within a larger software package.
I request, but do not require, credit for my work. This can be placed in your product�s documentation or directly in the software via a typical �Help/About� screen.
Tutorial (Visual Studio .NET)
Adding the circular menu component to the Visual Studio toolbox
Before you can create programs using the circular menu component, you need to add the component to your Visual Studio toolbox panel.
To do this, right-click anywhere on the toolbox and select �Add/Remove Items��.
Selecting this menu option will cause the �Customize Toolbox� dialog to be displayed. From here, you must first click the �Browse�� button, and then navigate to the directory where you installed the circular menu.
Once you have found the installation directory, select the �CircularMenu.dll� file and then select �Open.� This will return you to the �Customize Toolbox� dialog. Click �OK� and you will be returned to Visual Studio. New icons for the circular menu component and the menu option component will have been added to your toolbox.
Adding and configuring a Popup Menu
Create a new Windows application project in the language of your choice. Once the template is ready, you will have a new form in your project named Form1
. Ensure that the circular menu has been added to your toolbox as described earlier. Once you have done so, you can drag and drop it from the toolbox onto your form. Do this now. Notice that Visual Studio has created an icon in your component tray named �circularMenuPopup1
�. You can modify the properties of the circular menu via the Properties window, just as with any other control.
These properties work together to control the appearance and behavior of your circular menu. The OpeningAnimation
and ClosingAnimation
properties define the animations played when the menu opens and closes, respectively. The ToolTip
property controls how and where the menu�s tool tips are displayed.
We�ll accept the default values for now. To add items to the menu, we will need to work with the MenuOptions
property. To do this, select MenuOptions
in the property editor and click on the ��� button that appears to the right.
This summons a �MenuOption Collection Editor� dialog that can be used to add options to the menu and configure them. Click the �Add� button three times to add three new options to the menu.
Notice that you can edit the properties for your menu directly within this dialog. Each option is added to the Visual Studio designer component tray, so they can also be edited outside of this dialog.
Select the first option, �menuOption1
�. We will make this an �Open� button. Follow the steps below to do this:
- Select the �
HoverImage
� property and click on the ��� button to the right. This summons the �Edit Menu Option Image� dialog, which you can use to control the appearance of the option. Select �Browse�� and then navigate to your Visual Studio program directory (typically, �C:\Program Files\Microsoft Visual Studio� or �C:\Program Files\Microsoft Visual Studio .NET 2003�). From here, choose the �Common7� directory and then open �Graphics�. This directory contains a number of generic icons and bitmaps that you can use throughout your applications.
- Select the �bitmaps� directory and then open �Tlbr_W95�. Select and open the �OPEN.BMP� file.
- Set the transparency key to �
ffc0c0c0
� to make the background for this image transparent, and then click �OK�.
- Repeat steps 1 � 3 for the
DisabledImage
, Image
, and PressedImage
properties.
- Finally, set the
ToolTip
property to �Open a file.�
Repeat these steps to make menuOption2
a �Save� button and menuOption3
a �Print� button. When you�re done, click the �OK� button on the �MenuOption Collection Editor� dialog.
Because the icons we�ve used are rather small, we�ll need to decrease the radius for our popup menu. Select �circularMenuPopup1
� from the component tray and then change the Radius
property from 50 to 25.
Let�s also change the animation a bit: select the �OpeningAnimation
� property and click on the ��� button to the right. In the edit dialog that appears, change the layout style to �Spin along perimeter�, change the effect style to �Zoom in�, and then change the number of frames to �30�. Frames are displayed at approximately 30 per second, so this makes our opening animation nearly one second long.
Click the Play button to preview the animation, and then click �OK� to close the dialog. We�re finally ready to enable the popup. When and why a popup displays is up to you, but a very common trigger is right-clicking on the form. To enable this, perform the following steps:
C#
- Select your form and then click on the lightning icon property window to view the events available for the form.
- Find the �
MouseUp
� event and double-click it. This will launch the code editor with a newly added method called �Form1_MouseUp
�.
- Add the following code to the new method:
if( e.Button == MouseButtons.Right )
circularMenuPopup1.Popup( this, e.X, e.Y );
Visual Basic:
- Right-click somewhere on your form and then select �View Code�. This brings up the code editor window.
- Add the following code to the
Form1
class: Private Sub Form1_Click(ByVal sender as Object, ByVal e As MouseEventArgs)_
Handles MyBase.MouseUp
If e.Button = MouseButtons.Right Then
CircularMenuPopup1.Popup(Me, e.X, e.Y)
End If
End Sub
We�re now ready to test the application! Run the application using the �Debug > Start� menu option (F5 shortcut key for Visual Basic users), and right-click anywhere on your form to summon the shortcut menu!
Handling menu events
When the circular menu is shown, it obtains the system�s input focus, and does not release it until the user either cancels the menu or selects an option. These user actions are communicated to your program via a set of events, the most important of which are the MenuOption
class� �Click
� event and the CircularMenuPopup
class� �OptionSelected
� event.
The primary distinction between these events is that the �Click
� event is fired by a specific menu option, whereas the circular menu itself fires the �OptionSelected
� event. This means that when writing methods to handle �Click
�, only code specific to a single menu option needs to go in each method. On the other hand, writing an �OptionSelected
� handler implies including code for each menu option within that menu handler.
These two events are provided primarily to support different styles, but OptionSelected
does offer slightly more information than Click
�specifically, the coordinates where the menu was displayed. If you need to know where the menu was when a user selected an option, you need to use the OptionSelected
event. Please review the code documentation for more details.
In this tutorial, we�ll write some code to handle the Click
event of our Open button. To do so, close any open dialogs and select menuOption1
from the component tray. Double-clicking on menuOption1
will automatically add a new method to your Form1
class that can handle the Click
event for that option. By default, it�s called �menuOption1_Click
.�
For demonstration purposes, we�ll show an open dialog from within this method. The code for this is shown below:
C#:
private void menuOption1_Click(object sender, System.EventArgs e)
{
OpenFileDialog dialog = new OpenFileDialog();
dialog.ShowDialog( this );
}
Visual Basic:
Private Sub MenuOption1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MenuOption1.Click
Dim dialog As New OpenFileDialog
dialog.ShowDialog(Me)
End Sub
Now, when you run your program and click on the Open file button in the menu, the system will show an Open file dialog.
Further exploration
Other events offered by the system allow you to be notified when the user moves the mouse cursor over an option (MenuOption.StartHover
) or away from an option (MenuOption.EndHover
). You can also handle CircularMenuPopup.MenuCanceled
to be notified when a user cancels a menu without selecting anything, and CircularMenuPopup.MenuClosed
to be notified whenever a menu is closed, regardless of the option selected.
Animations within the library are handled by classes that implement IFrameLayoutManager
and IFrameModifier
. These classes are responsible for arranging the options around the clicked position and applying animation effects. Review the documentation and default implementations for more details on writing your own animations and effects. These classes are applied to the FrameImageEffect
and LayoutAnimator
properties of the MenuAnimation
class. Both a menu�s opening and closing animations are instances of the MenuAnimation
class.
Similarly, menu tool-tips are drawn by a class that implements the IToolTipRenderer
interface. You can specify a custom tool-tip renderer via the CircularMenuPopup.ToolTipOverride
property.
Circular Menu Architecture
The code for the circular menu component has been written in the C# language, available from Microsoft. While the .NET SDK and C# compiler are freely available, the circular menu code makes extensive use of the additional features supplied by the Visual Studio development environment, and so this documentation is written from the perspective of a developer using Visual Studio.
As part of the second major release of the .NET platform (2.0), Microsoft plans on providing free versions of Visual Studio targeted at students and hobbyists interested in C# and Visual Basic. Early documentation on these environments indicates that they will have similar functionality to their retail counterpart, but will limit the complexity of the applications written using them.
There is also a freeware designer that can be used to gain access to the Visual Studio features of the circular menu assembly. As of the writing of this article, information on this designer can be found at the following URLs:
The code for the circular menu component has been separated into two separate assemblies, CircularMenu.dll and PixelEffects.dll. The PixelEffects library was written to provide generic special effects for drawing within the CircularMenu library, and will not be discussed within this document. The source code for it, however, is included.
The following diagram shows the basic classes that are a part of the Circular Menu library:
The Circular Menu library can be thought of as being comprised of three subsystems: Top-Level Objects, Menu Options and Rendering, and Menu Animations. The top-level objects serve as controllers for the objects in the other two subsystems.
Top-level objects
In this architecture, the CircularMenuPopup
class represents the top-most entity. In dependency chains beginning here, you will be able to access most of the functionality provided by this library.
The other top-level component, CircularMenuWindow
, is a System.Windows.Forms.Form
subtype that is capable of animating and displaying a circular menu. This is the class responsible for delivering a user�s selection to the requesting application. While the Menu Window is a publicly available and usable class, there are few situations where you would use it directly instead of via a Menu Popup object.
A circular menu popup is composed of four objects: a collection of menu options, an opening animation, a closing animation, and a tool tip renderer. These objects, in turn, are composed of several elements.
Menu options and rendering
The MenuOptionCollection
class is a simplistic implementation of the .NET CollectionBase
class, strongly typed for non-null MenuOption
objects. This class exposes a method that returns a sub-collection of all menu options that are currently visible. This sub-collection is used when a window is being shown or animated.
Instances of the MenuOption
class describe an individual element in the popup menu. These are analogous to the MenuItem
instances in traditional .NET menus. Each option maintains an enabled state, a visible state, and a tool tip, as well as a number of images that control the visual appearance of the option when rendered. These images, which are stored in the form of MenuOptionImage
instances, are Image
, DisabledImage
, HoverImage
, and PressedImage
. These will be described in detail later.
Each MenuOptionImage
object defines an icon and optional operations (color key, transparency, and drop shadow) on the image. When the system needs to draw the image associated with the object, this class performs each of the optional operations on that image one at a time and returns the rendered result. Because this process can be expensive, each MenuOptionImage
stores a cached version of the result. The cached result is cleared (and rendered again when requested) whenever any of the options for that image change.
Finally, the DropShadowOptions
class encapsulates options that define the behavior of the drop shadow algorithm that is applied to an image when requested. Each instance of MenuOptionImage
has a corresponding instance of DropShadowOptions
.
Finally, the IToolTipRenderer
interface describes an object that is capable of drawing the tool tips for the popup menu. CircularMenu.dll provides a default implementation of this interface in StandardToolTipRenderer
. This renderer simply places the tool tip within the center of the pop-up menu. It exposes several options that are configurable via the Visual Studio designer.
An extended version of the tool tip render is described by the IExtraSpaceToolTipRenderer
interface and its default implementation, BelowMenuToolTipRenderer
. This type of tool tip renderer might require extra space on a menu surface that isn�t accounted for by simply the location and sizes of the menu option. For example, the below-menu renderer places the tool tip below all menu options and therefore increases the overall height of the menu. These types of tool-tip renderers need to report their additional requirements to the system so that the menu can be created and positioned properly.
Both StandardToolTipRenderer
and BelowMenuToolTipRenderer
inherit the base class StandardToolTipData
, which provides a common list of designer-aware options, such as foreground color and border thickness.
Menu animations
The MenuAnimation
class represents one of the two menu animations defined for each popup menu. This class defines the number of frames to animate, as well as the layout and modifications for those frames. Because generating animations can be expensive, this class also maintains a cached copy of a built animation, in the form of a FrameCollection
instance. This copy will need to be cleared when setting changes would produce a different animation.
The IFrameLayoutManager
interface describes objects that are capable of defining the layout for each frame in a menu animation. There are a number of built-in implementations of this interface, and customized implementations can be used as well.
Similarly, objects implementing the IFrameModifier
interface are used to provide �special effects� in the menu animations. Unlike layout managers, frame modifiers are not applied to the final frame in an animation. Because of this, they should produce an effect that gradually moves towards the normal image state. As with layout managers, there are a number of default implementations built into the CircularMenu.dll assembly.
Menu animations come in two flavors: forward and reverse. Each type uses a layout manager and a frame modifier, but they render their frames in different orders.
ForwardMenuAnimation
renders frames starting from index zero and advancing towards the highest index, while ReverseMenuAnimation
begins rendering at the highest frame index and finishes at zero. These instances produce a ForwardFrameCollection
and ReverseFrameCollection
, respectively. Typically, a forward animation will be used for the popup opening and a reverse animation will be used for popup closing. To maintain flexibility of the library, this convention is not enforced.
Each FrameCollection
instance is an immutable, fully rendered animation. The cached animations stored in MenuAnimation
instances are objects of one of the subtypes of this class. When creating a frame collection, the new instance is supplied with a set of menu options to render, a menu radius, a layout manager, and a frame modifier. The object�s constructor will then proceed to render the frames, applying the layout and modifications it was supplied with. Note that the FrameCollection
class was not designed to be the superclass for objects outside of the CircularMenu.dll assembly.
Library Documentation
The source files included with this article provide a comprehensive documentation set rendered from the C# XML comments, using nDoc. These describe the architecture and options in much greater detail than this document. The provided installer project will add the documentation set to Visual Studio so that the help is available when authoring applications utilizing the circular menu.
Revision History
2005.01.25:
Rewrote animation thread to use Control.Invoke
instead of directly calling methods on a control from a separate thread. Special thanks to the members who have commented on the article, and especially DrewS, who pointed out the solution. This was a problem that never manifested on any of the machines I was using--please let me know if it didn't work, after all.
Also, added files necessary to build the installer. This made the ZIP file a bit larger, hope nobody minds :)