Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Customizing Menu Shortcuts

4.90/5 (10 votes)
21 Oct 2008CDDL4 min read 1   1.7K  
Sample that allows the user to customize the shortcuts assigned to menu items.

Codeproject.JPG

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.

C#
private List<ToolStripMenuItem> menuItemsList = new List<ToolStripMenuItem>(); 

private void FillListWithMenuEntries(ToolStripItemCollection items)
{
    foreach (ToolStripMenuItem item in items)
    {
        menuItemsList.Add(item);

        // Verify if this item is a parent for other menu-items.
        // If so add these children by recursion:
        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.

C#
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.

C#
/// <summary>
/// Class for use in the List of Keys
/// </summary>
public class Key
{
    /// <summary>
    /// Name of the Key
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// KeyCode of the key. ReadOnly.
    /// </summary>
    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.

C#
/// <summary>
/// Class providing a list with Keys for selection
/// </summary>
public class KeysList
{
    #region Properties
    /// <summary>
    /// ReadOnly by private set (for automatic properties in .net 3.5)
    /// </summary>
    public List<Key> KeyList { get; private set; }
    #endregion
    //---------------------------------------------------------------------
    #region Contructor
    /// <summary>
    /// Initializes the KeyList with the selectable keys for the
    /// shortcuts (A-Z, F1-F11)
    /// </summary>
    public KeysList()
    {
        this.KeyList = new List<Key>();

        // Letters A-Z:
        for (byte b = 65; b <= 90; b++)
        {
            // Convert ASCII to character:
            char c = Convert.ToChar(b);

            // Add to list:
            this.KeyList.Add(new Key { Name = c.ToString() });
        }

        // Add F-keys (F1-F11):
        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.

C#
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.

C#
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.

C#
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.

C#
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:

C#
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.

C#
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    // For saving the shortcut in user.config they have to be
    // written in an ArrayList. Only the shortcuts are saved:
    ArrayList arrList = new ArrayList(menuItemsList.Count);

    foreach (ToolStripMenuItem item in menuItemsList)
        arrList.Add(item.ShortcutKeys);

    Properties.Settings.Default.ShortCuts = arrList;
}

In Program.cs:

C#
Application.Run(new Form1());

// Save the settings:
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.

C#
// Get ShortCuts from the saved user-settings (user.config):
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.

License

This article, along with any associated source code and files, is licensed under The Common Development and Distribution License (CDDL)