A simple approach for handling shortcuts in an application

The other day I was messing with making an application be configurable to have shortcuts for specific functions. For instance, in a data-entry form that has the typical functionality such as "new", "load" (or "open"), "save", etc., which are triggered by a click on a button somewhere (right on a form or on a Toolbox or Ribbon control), the users also expect to be able to use keyboard shortcuts (such as Ctrl+N for "new" and Ctrl+S for "save") so to trigger those common features.

I knew that it could be done by handling events such as KeyPress or KeyDown directly on a form, but I wanted to write a more generic and reusable solution (which is part of another thing that I’m working on, but I’ll leave that for another post).

In this post I’m going to present a simplified version of the design and implementation for the solution I’m going with (I’m stripping out some details and making a few changes to make it simpler, since eventually I’m going to come back to this and more more details).

What is the requirement?

When a data-entry form is active, the user wants to press Ctrl+N to create "new" data, Ctrl+O to "load" existing data, and Ctrl+S to save data. Since the purpose of this post is not to show how to right data access layers, business objects, etc., those shortcuts will simply trigger the display of a MessageBox stating what operation should be executed at that point.

Another requirement is that those shortcuts should be enabled or disabled depending on some logic; for instance, "save" should be disabled if no data has changed, and maybe "new" should be disabled if the user doesn’t have rights to create new data on that form. For this post, I just show how the options can be enabled/disabled (the actual logic to decide when to enable/disable a feature is beyond the scope of this post).

The usage

As I mentioned, I wanted to encapsulate that functionality somehow so that it can be easily maintained and reused. I have then created a "SimpleDataInterfaceShortcutHandler" class for that (from here on I’ll refer to this class as the handler class for the sake of brevity). The way to use it (at least for the scope of this post) is to instantiate the class somewhere in a form (such as in its constructor), passing the a reference to the form into the class (since it uses the form’s capabilities to detect the key that was pressed), and then setting properties to determine what shortcuts should be enabled. Something like this:

SimpleDataInterfaceShortcutHandler shortcutHandler = 
new SimpleDataInterfaceShortcutHandler(this); shortcutHandler.LoadEnabled = true; shortcutHandler.NewEnabled = true; shortcutHandler.SaveEnabled = true;

The shortcut key combinations

There’s a Keys enum within the System.Windows.Forms namespace. This enum lists values for all the keys in a keyboard. I’ve added three constants to my handler class so to store the values for my shortcut key combinations: one constant for each shortcut that I want to support:

/// <summary>
/// The shortcut that triggers the "NEW" functionality.
/// </summary>
private const Keys NEW_Shortcut = Keys.Control | Keys.N;

/// <summary>
/// The shortcut that triggers the "LOAD" functionality.
/// </summary>
private const Keys LOAD_Shortcut = Keys.Control | Keys.O;

/// <summary>
/// The shortcut that triggers the "SAVE" functionality.
/// </summary>
private const Keys SAVE_Shortcut = Keys.Control | Keys.S;

Executing actions associated with the shortcuts, and the Command pattern

In order to execute actions associated with a given shortcut, I decided to use a simplified version of the Command pattern, by using a delegate that I named ExecuteShortcut:

/// <summary>
/// Represents the method to be executed when a shortcut is triggered.
/// </summary>
private delegate void ExecuteShortcut();

Storing the shortcut bindings and the actions that they execute

In order to set the shortcut bindings and the actions that they’re supposed to execute, I use a Dictionary<Key, Value> collection. For the key I use the keyboard combination (which I’ve defined as a set of constants), and for the value I use an instance of the ExecuteShortcut delegate, which holds a reference to the command to be executed when the shortcut is triggered. This is the definition of my field that stores such collection:

/// <summary>
/// Dictionary of shortcuts controller by the handler. 
/// The key for the dictionary is the "key" combination that the shortcut is mapped to,
/// whereas the value is a delegate that points to the action to be executed when the 
/// shortcut has been triggered.
/// </summary>
private Dictionary<Keys, ExecuteShortcut> m_Shortcuts = 
new Dictionary<Keys, ExecuteShortcut>();

Registering the shortcuts and actions

In order to register the shortcuts and their actions, I’ve decided to provide special methods for that, as opposed to going straight to the m_Shortcuts dictionary. That way, if I ever decide to store the bindings in a different way, I can do that easily without breaking anything. The RegisterShortcut and UnregisterShortcut methods look like the following:

/// <summary>
/// Registers a shortcut and it's related action with this handler.
/// </summary>
/// <param name="shortcut">The shortcut.</param>
/// <param name="shortcutAction">The shortcut action.</param>
private void RegisterShortcut(Keys shortcut, ExecuteShortcut shortcutAction)
{
    if (!m_Shortcuts.ContainsKey(shortcut))
    {
        m_Shortcuts.Add(shortcut, shortcutAction);
    }
}

/// <summary>
/// Unregisters the shortcut from this handler.
/// </summary>
/// <param name="shortcut">The shortcut.</param>
private void UnregisterShortcut(Keys shortcut)
{
    m_Shortcuts.Remove(shortcut);
}

Nothing special there. The only thing is that the RegisterShortcut only adds a new binding to the dictionary in case it does not already exist there.

Enabling and Disabling shortcuts

Enabling and disabling shortcuts is a matter of registering and unregistering the specific bindings. We do that in the setter method of each "Enabled" property, like so:

/// <summary>
/// Gets or sets a value indicating whether the New functionality is enabled.
/// </summary>
/// <value><c>true</c> if [new enabled]; otherwise, <c>false</c>.</value>
public bool NewEnabled
{
    get
    {
        return this.m_Shortcuts.ContainsKey(NEW_Shortcut);
    }
    set
    {
        if (value == true)
        {
            this.RegisterShortcut(NEW_Shortcut,
                delegate()
                {
                    MessageBox.Show("Should be creating a new something...");
                });
        }
        else
        {
            this.UnregisterShortcut(NEW_Shortcut);
        }
    }
}

/// <summary>
/// Gets or sets a value indicating whether the Load functionality is enabled.
/// </summary>
/// <value><c>true</c> if [load enabled]; otherwise, <c>false</c>.</value>
public bool LoadEnabled
{
    get
    {
        return this.m_Shortcuts.ContainsKey(LOAD_Shortcut);
    }
    set
    {
        if (value == true)
        {
            this.RegisterShortcut(LOAD_Shortcut,
                delegate()
                {
                    MessageBox.Show("Should be loading something...");
                });
        }
        else
        {
            this.UnregisterShortcut(LOAD_Shortcut);
        }
    }
}

/// <summary>
/// Gets or sets a value indicating whether the Save functionality is enabled.
/// </summary>
/// <value><c>true</c> if [save enabled]; otherwise, <c>false</c>.</value>
public bool SaveEnabled
{
    get
    {
        return this.m_Shortcuts.ContainsKey(SAVE_Shortcut);
    }
    set
    {
        if (value == true)
        {
            this.RegisterShortcut(SAVE_Shortcut,
                delegate()
                {
                    MessageBox.Show("Should be saving something...");
                });
        }
        else
        {
            this.UnregisterShortcut(SAVE_Shortcut);
        }
    }
}

The two important pieces are the constant that we pass to the RegisterShortcut method, indicating which shortcut we’re registering, and also the delegate with the command to be executed whenever the shortcut is triggered. This is where the Command pattern is being used: the action is not being executed just yet; we’re only registering the command to be executed when necessary. Also, in this sample, I’m using an anonymous method, since the code to be executed is just a one-liner. I figured the code is clean enough if written like that, since it’s easy to see what shortcut is being registered, and what action is going to be triggered by it.

Hooking up the handler and the form

As I mentioned before, I use the form’s capabilities to trap what keys have been pressed. When the handler gets instantiated, we hook things up in the class’s constructor, like so:

/// <summary>
/// Initializes a new instance of the <see cref="DataInterfaceKeyboardHandler"/> class.
/// </summary>
/// <param name="form">The form.</param>
public SimpleDataInterfaceShortcutHandler(Form form)
{
// Causes the form to receive the key stroke
// even when a control is currently selected on it.
form.KeyPreview = true;


// Only KeyUp seems to trap all keys, including function keys (F1, F2, etc.)
form.KeyUp += new KeyEventHandler(form_KeyUp); }

As the inline comments say, the form’s KeyPreview is property is set to true so to cause the form to receive a key even if the focus is on a control contained in the form (such as a TextBox). Also, KeyUp is the event that I subscribe to in the form. That seems to be the only event that receives all the keys and combinations that I’m interested on. For some reason, the other events such as KeyPress and KeyDown don’t receive keys such as F1, F2, F3, etc, and some combinations such as Ctrl+Key.

Finally, this is the event handler for the KeyUp event:

/// <summary>
/// Handles the KeyUp event of the form control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.Windows.Forms.KeyEventArgs"/> 
/// instance containing the event data.</param>
void form_KeyUp(object sender, KeyEventArgs e) { if (this.m_Shortcuts.ContainsKey(e.Modifiers | e.KeyCode)) { ExecuteShortcut action = this.m_Shortcuts[e.Modifiers | e.KeyCode]; action(); } }

This is how that method works:

  1. We first check to see whether the key combination (Modifier, such as "control" or "shift", plus the KeyCode, such as "N", or "S") is registered as a shortcut. In such case we know we have an action to execute;
  2. We retrieve the command object (that is, the delegate associated with the shortcut) from the dictionary;
  3. We execute the delegate.

Summing up

That’s it. This simple class can now be used wherever needed, but most importantly, it’s easy to refactor it into something that can be a lot more powerful.

For instance, it could have a different way to hook up to the "keystroke trapping" (maybe there’s a better way then how I did it here, and maybe there’s a different way of doing it in another scenario, such as in WPF); if that’s the case, it’d be easy invert things here and remove the small dependency that the handler has on the Form class.

Also, in this simple example we have the handler having knowledge of what actions to execute; on the actual scenario where I’m implementing this class, instead of the handler itself having the action to be executed, it actually delegates the execution to a controller class, which can be swapped on the fly depending on the context where the shortcut was fired.

Finally, another thing that I like about this solution is that we don’t have just a single event handler on the KeyUp event, with a big and nasty switch statement on the key combinations and the actions all implemented mixed up into a single method.

Download source code

Advertisements
  1. #1 by Kamran on December 19, 2007 - 1:29 am

    Can there be any such thing for web forms.[I am asking about Asp.net ]

  2. #2 by Claudio on December 19, 2007 - 10:00 am

    Hi Hamran,
     
    you know, I kinda of saw that question coming.  🙂
    I thought about it too, but I\’m not sure how that could be done (I\’m kind of rusty on web development these days). For one thing, I don\’t remember seeing websites that make use of keyboard shortcuts. One of the reasons may be because the shortcuts may not map well cross-platform (does a Ctrl+something or F1, F2, etc, work in a Mac the same way they do in Windows? I don\’t know, since I\’m not a Mac guy).
     
    I\’m certainly interested to hear whether anybody knows the answer to those questions…

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: