Introduction
This is my first article, in which I try to present a basic way to make a fish eye menu control, in which the item the mouse is over is enlarged, and the surrounding items increase and decrease their size when you move the mouse.
Background
There was a post on the forums that linked to a Web site with several alternative menus that are coming in the future. I wanted to know how hard it would be to implement something like the fish eye menu, and although the code is very basic, it serves as a starting point for improving and making it better.
Using the Code
As a first version of the code, stuff is very messy. First of all, there are class and attribute names in both Spanish and English, as I wasn't thinking of posting this article. In the course of this week, I'll correct it and upload the new version.
There is a class called Item
, which represents each of the different menu items. This class has very basic attributes, such as:
private string _valor;
private float _inicio;
private float _fin;
private bool _over;
_valor
is the value of the item, which in this case is just a string
, but it can be any supported type. _inicio
and _fin
are the y
axis limits in which the item is displayed. _over
indicates if the mouse is currently over the specified item.
The control, which in this case is the form itself, has three other attributes:
private int? _indexOver;
private Item _itemOver;
private List<Item> _items;
_indexOver
is the variable that holds the index of the item the mouse is over. Since it can be null
, the variable is nullable. _itemOver
corresponds to the specified item, and it can also be null
. _items
contains the item list, which in this case is loaded in the Form_Load
event.
Constructor
In order to reduce the flickering and enhance the visual experience and responsiveness, there are some calls to the SetStyle
method of the control in its constructor:
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(ControlStyles.CacheText, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
this.SetStyle(ControlStyles.ResizeRedraw, true);
this.SetStyle(ControlStyles.UserPaint, true);
What this basically does is configure the control so that I (the developer) am responsible for its drawing, and sets it to use double buffer and to cache text so it renders faster.
In the Form_Load
method I just use a for
to fill the _items
collection, but this could be done by getting info from a datasource, receiving it as a parameter, or even setting it as a property from an external object.
The two main methods are Form1_MouseMove
and OnPaint
(which is overridden in this class). I will not copy the code, as it is available in the source code, but instead I will give you some insight into what it does.
OnPaint
This method is in charge of drawing the menu. It is called every time the form has to be redrawn, and it overrides the method of the base class (but it doesn't call it, so the whole painting is done in this method).
First of all, this method calculates the height
of the regular items (where the mouse is not over nor near), using a basic (and not accurate) calculation of the form height over item count, just to get an average height (this calculation will be corrected in the second version of the article).
Then, what it basically does is go through the list of items in the _items
collection, and check the item over property. If the property is true
, then it draws the item with bigger letters and in another color. If not, it checks the index of the current item to that of the item the mouse is over at. If the index
is +/- 1, or +/- 2 then it draws the item
in color and gives it a bigger size. If not, it draws the item in black and with the regular size. Every time an item is drawn, the current y
position (currently that variable is named x
but it will be corrected) is used as the starting point of the next item. So, if the item's height is greater because the mouse is over it or it's over an item close
to it, then the next item will be redrawn correctly.
Form1_MouseMove
This method controls the mouse movement. First of all, it clears the _itemOver
and _indexOver
class attributes. Then it goes through the _items
collection and for each item it calls the Esta(float y)
method. This public
method of the Item
class receives a y
position and returns whether it is between the item's _inicio
and _fin
attributes (beginning and end). If it's true
, then the mouse is over the item, so it sets the item's Over
property to true
and updates the _itemOver
and _indexOver
class attributes. If it's false
, then it sets the Over
property to false
. After going through the collection, if the mouse is over an item (checking either the _itemOver
or the _indexOver
for null
s), it sets the mouse cursor to Cursors.Hand
. Otherwise, it sets it to Cursors.Default
.
Finally, it calls the this.Invalidate()
method that forces a redraw, and with it, a call to the OnPaint
method, which redraws the menu.
Form1_MouseClick
This last method only shows the current item, if any is selected. It could be used to call a delegate, to return a value, or anything anyone would want from a menu.
Points of Interest
What I learnt from doing this is that it's really easy to create your own controls, with your own functionality and extend the .NET Framework functionality.
What's Next?
In the next update, which I'll plan to do by the end of this week, I will:
- Update the code to add comments
- Rewrite the code in a better class structure
- Change all the Spanish names for easier, English names
- Make it a user control instead of a form
- Read your suggestions to try and improve this menu
History
- 03/12/2007 - First version