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.
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.
_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
_items contains the item list, which in this case is loaded in the
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:
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.
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
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.
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.
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 _
_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 _
nulls), it sets the mouse cursor to
Cursors.Hand. Otherwise, it sets it to
Finally, it calls the
this.Invalidate() method that forces a redraw, and with it, a call to the
OnPaint method, which redraws the menu.
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.
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
- 03/12/2007 - First version