Introduction
This sample code allows the user to customize the shortcuts assigned to menu-items. The shortcuts get stored in the user.confg. Therefore, the application can be used in multi-user environments.
When the application gets started, a list with all menu items is created. This list is bound to a listbox where the user can select a menu item. After selecting, the shortcut for this menu item can be assigned or modified. On closing the application, the assigned shortcuts get stored in the user.config. On re-start, these shortcuts from the user.config are read and assigned to the menus.
Background
Shortcuts are a bitwise combination of the keys used for the shortcut. So, it's is possible to combine keys to a shortcut by using OR. To get the keys used for a shortcut, eliminate the known keys, building the shortcut by using XOR.
To save the shortcuts permanently, the VS provided settings are used. For binding the settings (a list of shortcuts), an ArrayList
is used. The settings get saved after the message-loop of the application has finished. This is done because there might be further forms in the application, and therefore the 'in memory' settings have only to be once written to the permanent storage.
List of all menu items
Getting the list of all menu items
A recursive method is used to fill the list with all the menu items of the form. The recursion is done by checking if a menu item has dropdown items, i.e., is a parent to other menu items.
private List<ToolStripMenuItem> menuItemsList = new List<ToolStripMenuItem>();
private void FillListWithMenuEntries(ToolStripItemCollection items)
{
foreach (ToolStripMenuItem item in items)
{
menuItemsList.Add(item);
if (item.HasDropDownItems)
FillListWithMenuEntries(item.DropDownItems);
}
}
Binding the list with menu items to a Listbox
The DataSource
property of the ListBox
is used to bind the list of the menu items. To display the caption of the menu items, the DisplayMember
property is assigned to the name of that property.
lstMenuItems.DataSource = menuItemsList;
lstMenuItems.DisplayMember = "Text";
Class providing a list of keys
The built-in enumeration Keys
isn't in an appropriate order, so I've implemented my own list of keys. This list provides the possible letters A-Z and the F-keys (F1-F11). For converting a key that is given by name into the value of the Keys
enumeration, an instance of the KeysConverter
class is used.
public class Key
{
public string Name { get; set; }
public Keys KeyCode
{
get
{
KeysConverter keyConverter = new KeysConverter();
return (Keys)keyConverter.ConvertFrom(this.Name);
}
}
}
To provide the list, I've implemented another class. This class has the List
as its property. In the constructor, the list is built up.
public class KeysList
{
#region Properties
public List<Key> KeyList { get; private set; }
#endregion
#region Contructor
public KeysList()
{
this.KeyList = new List<Key>();
for (byte b = 65; b <= 90; b++)
{
char c = Convert.ToChar(b);
this.KeyList.Add(new Key { Name = c.ToString() });
}
for (byte b = 1; b <= 11; b++)
this.KeyList.Add(new Key { Name = "F" + b.ToString() });
}
#endregion
}
The letters A-Z are represented by ASCII-code 65-90. Via Convert.ToChar(ASCII code)
, the letter is obtained.
This list is bound to a combobox for user's selection.
cmbKeys.DataSource = new KeysList().KeyList;
cmbKeys.DisplayMember = "Name";
cmbKeys.ValueMember = "KeyCode";
Combine a shortcut
As stated above: shortcuts are a bitwise combination of modifiers and keys. The modifiers are selected by checking (or unchecking) a checkbox. The combination is done by OR-operations.
Keys shortCut = (Keys)cmbKeys.SelectedValue;
if (chkCtrl.Checked) shortCut |= Keys.Control;
if (chkAlt.Checked) shortCut |= Keys.Alt;
if (chkShift.Checked) shortCut |= Keys.Shift;
Following the rules of the boolean algebra, the order of combination doesn't matter.
For querying whether this selected/combined shortcut is already assigned, I've used a LINQ-query to the list of all menu items.
var query = menuItemsList.FirstOrDefault(s => s.ShortcutKeys == shortCut);
By using FirstOrDefault
, the result is null
when no element is obtained by the query - that means the shortcut is "free" for use.
Retrieving the key and the modifiers
The reverse way: get the key and the modifiers that are combined to a shortcut.
For getting the modifiers, we can bit-mask the shortcut and check if the result is equal to a possible modifier. Bit-masking is done by AND. In this operation, the values of the checkboxes are set.
Keys shortCut = selectedItem.ShortcutKeys;
chkCtrl.Checked = (shortCut & Keys.Control) == Keys.Control;
chkAlt.Checked = (shortCut & Keys.Alt) == Keys.Alt;
chkShift.Checked = (shortCut & Keys.Shift) == Keys.Shift;
To get the letter or the F-key, we have to eliminate the modifiers. This is done by combining all the set modifiers and then by removing them from the shortcut with XOR.
Example (values used are just dummies to show how it works):
Ctrl = 00100000
Alt = 01000000 OR
-------------------
modifyer = 01100000
shortcut = 01100010
modifyer = 01100000 XOR
-------------------
Key = 00000010
Code:
Keys modifiers = Keys.None;
if (chkCtrl.Checked) modifiers |= Keys.Control;
if (chkAlt.Checked) modifiers |= Keys.Alt;
if (chkShift.Checked) modifiers |= Keys.Shift;
Keys buchstabe = shortCut ^ modifiers;
Saving the shortcuts
This is done by using the VS built-in Settings-designer. From the possible types, the designer provides an array list that is chosen. On FormClosing
, the value for this array list has to be assigned. Only the value of the shortcut gets assigned - the matching menu item is held by the list of the menu items. The settings are written to permanent storage after the message loop from the WinForms application has finished, because there might be other forms and this operation has to executed only once.
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
ArrayList arrList = new ArrayList(menuItemsList.Count);
foreach (ToolStripMenuItem item in menuItemsList)
arrList.Add(item.ShortcutKeys);
Properties.Settings.Default.ShortCuts = arrList;
}
In Program.cs:
Application.Run(new Form1());
Properties.Settings.Default.Save();
On startup
By saving the settings, the values are written to the user.config file. On startup, these values have to be assigned to the menu items. This done by creating a list of the menu items and then by assigning the values of the shortcuts.
ArrayList arrList = Properties.Settings.Default.ShortCuts;
if (arrList != null)
for (int i = 0; i < menuItemsList.Count; i++)
menuItemsList[i].ShortcutKeys = (Keys)arrList[i];
Generally: ToolStripMenuItem
is a reference-type. Therefore, changing a property in any variable holding a 'reference' changes the property in the 'basic' ToolStripMenuItem
.
Using the code
The sample is a full working project. When used in your application, you have to adjust the code to your user interface. See the comments in the code for details.
Points of interest
Combining and retrieving the keys used for a shortcut by using bit-manipulation with OR and XOR.
See also
Component for customizing menu shortcuts describes a component that allows the customization of menu shortcuts. This can be useful for barrier free aspplications.
History
This is the first release. Inspired by request in a forum. The code is made up form scratch. There maybe some improvements in future.