Why Another Mouse Control?
There are plenty of ways to get your user to tell you what he needs, but you can always find newer and better ones. Still, for no other reason than "I just felt like making one", I wrote the one described here. This user control is intended to be used with the right-mouse-click and appears as a menu under your mouse wherever it is on the screen at the time and can be expanded to any number of levels.
See the screen capture below taken near the right edge of the screen.
You can see in the image above that the user's options are submenus of submenus. You can capture the results when your user clicks on any one of the options or moves the mouse away from it and it disappears.
Using the Control
To use this control is quite simple. You create an instance of it, set up the menus in the form of a string and then give it an event-handler for when it is disposed.
void formRightClickMenu_Example_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Right)
{
string[] strButtons = { "file(save+load+exit)",
"Level(one+two+three+four)",
"options(color(background+foreground)+
shadow+stoptime(on+off))"};
formRightClick frmRightClick = new formRightClick(strButtons);
frmRightClick.bolPasteToClipBoard = true; frmRightClick.Owner = this;
frmRightClick.frmMain = this;
frmRightClick.TopMost = true;
frmRightClick.Show();
frmRightClick.Disposed += new EventHandler(frmRightClick_Disposed);
}
}
Then to find out what the user has selected, you go to the frmRightClick_Disposed that you just added above and read the string there.
void frmRightClick_Disposed(object sender, EventArgs e)
{
formRightClick frmRightClick = (formRightClick)sender;
string strClicked = frmRightClick.strClick.ToUpper();
switch (strClicked)
{
case "FILE+SAVE":
MessageBox.Show("save the file");
break;
default:
if (strClicked.Length>0)
MessageBox.Show("result :" + strClicked);
break;
}
}
Optionally, for debugging purposes, if you're not sure about your typing, you can place a break at the switch in the event-handler and then paste the results of what you selected directly from the clipboard. to enable this set:
frmRightClick.bolPasteToClipBoard = true;
which you'll probably want to set to false(default setting) before you compile.
Setting Up the Menus
You may have noticed above that the menus have to be set up as an array of strings. Each string is intended to be a menu option in the right-click form being created. and each of these menu options can have as manu submenu options as you want. To define each submenu, you encapsulate the submenus between two round-brackets and then insert a '+' character between each submenu. for sub-sub-menus(and so on) you just do the same. You can see this in the example above where the options menu has three submenus (color, shadow & stop-time) and both the color and stop-time submenus have submenus of their own which are also encapsulated between two rounded brackets.
string[] strButtons = { "do a cartwheel(on the(roof+table(beside the(vestibule+
treasure chest+wall)+against the(wall+door+cabinet)+in the
(recepticle+bedroom+roadhouse))+floor)+in the(office+hallway+stairwell))",
"smile(like (a(retard+moron+movie star)+an(idol+idiot))+
so that(you(look(good+bad))+they(cry+cheer)))",
"quit"};
The image shown at the top was taken using the button definition shown here.
To keep from getting confused when created a complicated set of sub-sub and then sub menus, I suggest you add the open and close brackets at every level before adding each of the menu-options that appear between them.
Or else you'll be counting brackets, and that's a bad way to be.
How It Works
When you call the form with the first set of menus, each menu-option is stripped of its submenus and these are tagged to it as a string. The labels themselves are of type LabelItem which have only one string variable that holds its submenu string and a pointer to its parent form. These inherit another type of control that I wrote and included here called the LabelButton. The LabelButtons are handy because they have both dull & highlight appearances, a boolean flag along with set() and reset() functions which are used by this control to highlight the menu options that lead up to the one currently being selected. Each of these LabelButtons gets highlighted when the mouse enters and then are set() only if the mouse leaves by entering into its submenus. As the mouse enters one of these labelbuttons, the submenus are read off them and then converted into an array of strings which are sent to the sub-menu form.
The submenu form is just another instance of the same control with the menu options string array pulled LabelItem's string var strSubMenu and a running strClick is kept to remember all the steps we've taken to get where we are. Since each instance of the formRightClick is stored into a static array, we can test if the mouse is under any one of these at the MouseLeave event handler. We do this by cycling through each instance in this static array from the most recent to the earliest so that if the cascade of forms "bounced" off the edge of the screen and made its way backwards(and appears over top of an earlier one) we're always looking at the deepest instance the mouse is over rather than an earlier one which allows us to go as deep into menu-sub-menu and then sub-more menus (forgive my bad spelling, I have a cold) ad infinitum.
Have a look below :
void lbtn_MouseEnter(object sender, EventArgs e)
{
LabelItem lbtn = (LabelItem)sender;
if (lbtn.frmSubMenu != null)
return;
if (frmMyOffspring != null && frmMyOffspring != lbtn.frmSubMenu)
{
frmMyOffspring.Dispose();
}
lbtn.set();
string[] strArray = new string[0];
if (lbtn.strSubMenu.Length == 0)
return;
string strLbtnSubMenu = lbtn.strSubMenu;
while (strLbtnSubMenu.Length > 0)
{
string strNextItem = getNextSubMenu(strLbtnSubMenu);
strLbtnSubMenu = strLbtnSubMenu.Length > strNextItem.Length ?
strLbtnSubMenu.Substring(strNextItem.Length + 1) : "";
Array.Resize<string />(ref strArray, strArray.Length + 1);
strArray[strArray.Length - 1] = strNextItem;
}
frmMyOffspring
= lbtn.frmSubMenu
= new formRightClick(strArray);
frmMyOffspring.bolPasteToClipBoard = bolPasteToClipBoard;
lbtn.frmSubMenu.bolPlaceUnderMouse = false;
lbtn.frmSubMenu.Owner = this;
lbtn.frmSubMenu.frmMain = frmMain;
lbtn.frmSubMenu.strParent = strParent + lbtn.Text.Replace
(strSubMenuArrow, "").Trim() + "+";
lbtn.frmSubMenu.Show();
strClick = strParent + frmMyOffspring.strClick;
if (frmMyOffspring.strClick.Length > 0)
Dispose();
}
We peel off the submenus in the function getNextSubMenu() shown below by tracing through the string looking at the '(' ')' and '+' characters.
string getNextSubMenu(string strSubMenu)
{
int intCountOpenBrackets = 0;
for (int intCharCounter = 0; intCharCounter < strSubMenu.Length; intCharCounter++)
{
char chrThis = strSubMenu[intCharCounter];
if (chrThis == ')')
intCountOpenBrackets --;
if (chrThis == '(')
intCountOpenBrackets++;
if (chrThis == '+' && intCountOpenBrackets == 0)
return strSubMenu.Substring(0, intCharCounter);
}
return strSubMenu;
}
This is just a matter of keeping track of the brackets and only calling it quits when we find a '+' char and the count of open brackets is zero.
History
- 29th November, 2010: Initial post