Implementing a Keyboard Interface using .NET Controls for a WinForm Client

Introduction

The .NET Framework Windows Form client is a great platform for offering a rich, interactive and responsive user interface. The rich interface of a WinForm client, properly constructed, can offer productivity benefits to the information worker far and above other standard architectures. There are many reasons for increased productivity but one reason comes from how easy it is to leverage the keyboard in a WinForm client.

The Microsoft .NET Framework comes packaged with all the building blocks to implement a keyboard interface in a WinForm application. What this article will illustrate and walk through is how to extend those building blocks with basic .NET controls, inheritance and custom events to create a consistent keyboard interface. The approaches covered do not encompass all possible keyboard interface solutions but aim to offer an idea and direction on how a consistent and efficient keyboard interface can be implemented.

Keep in mind that consistency is crucial when implementing any interface, especially a keyboard interface. Just as easy as it is to increase productivity with the keyboard, it is even easier to decrease productivity by implementing a poorly thought out and inconsistent keyboard interface. For example, if the F1 key pulls up a search box in one form of the application, and deletes records in another form it can be detrimental to the application as a whole. This is why the approaches discussed in this article will focus on keeping the keyboard interface consistent while at the same time make the task of implementing the interface painless and efficient for the .NET developer.

The Basics of Keyboard Interfaces

At the basic level capturing and taking action off of a keyboard command is simple and straight forward. By handling one of a few keyboard events (KeyDown, KeyUp, or KeyPress) on virtually any WinForm .NET Control, keystrokes can be captured and appropriate action taken. The serious problem with this is that the keyboard event would have to be handled individually for every control on the form to make sure that keyboard commands were always captured. Meaning that if a form had 5 buttons, 5 events would have to be handled and coded. This is neither practical nor efficient for the .NET Developer and therefore causes the need to handle the keyboard events at the form level, regardless of what control has focus. The following walks through how to implement an event handler on the form that captures keystrokes.

(All code examples are done using Microsoft Visual Studio 2008 SP1, .NET Framework 3.5 SP1, and running Windows 7 Ultimate 64-bit)

Open Microsoft Visual Studio and create a new Windows Form Application. Place 2 textbox controls and 2 button controls on the form. Open up the properties panel for the default form and rename the form to "MainForm". There is then a property on the form to set to true that is by default, false. The 'KeyPreview' property on the form causes keyboard events fired on controls on the form to be registered with the form first. By handling the events at the form level it eliminates the need to handle them individually at the control level. Once the 'KeyPreview' property is set to true the next step is to create the event handler on MainForm to handle the KeyDown event. Double-clicking the KeyDown event in the Properties window of MainForm will generate the event handler in code. Use the code block below to populate the KeyDown method.

private void MainForm_KeyDown(object sender, KeyEventArgs e)

{

if (e.KeyCode == Keys.Escape)

MessageBox.Show("Escape Key", "Keyboard Command");

else if (e.KeyCode == Keys.F1)

MessageBox.Show("F1 Key", "Keyboard Command");

else if (e.KeyCode == Keys.Enter)

MessageBox.Show("Enter Key", "Keyboard Command");

}

The above code displays a message box with a message when the Escape, F1 or Enter key is pressed. Run the application and notice that no matter what control has focus on the form the KeyDown event on the form handles the keyboard command.


Figure 1

The KeyEventArgs used in the KeyDown event can also be used detect when modifiers are used (a combination of keys, such as 'Ctrl+Alt') and even control if the keyboard command is passed down to the underlying control.

The problem with using this approach on each individual form is efficiency for the .NET Developer. Using this approach in larger applications with many forms is not only inefficient but prone to introduce consistency issues. An application with dozens of forms will require the .NET Developer to individually setup and configure the keyboard events at a measurable impact to time and cost. This is where implementing an approach to extend .NET Controls and customizing events comes in. By combining customized .NET Controls and inheritance the required work on the Developer can be reduced while increasing the consistency in the way the keyboard interface is implemented. The rest of this article will explore 2 different approaches to implementing a consistent and efficient interface to a .NET Windows Form client.

NOTE: There is another approach to capturing all keystrokes at the main form level. It involves implementing an IMessageFilter interface on the form level. While this is more effective in some cases to capture all keystrokes it does have the potential of degrading performance since message filters are being added to the message pump for the application.

Implementing a Keyboard Interface using .NET Controls for a WinForm Client

Approach #1: Extending .NET Controls

In this first approach the .NET Framework Button control will be extended to include a new property. This new property will contain the keyboard command that should trigger the same action as if the button was clicked. In addition to extending the Button control, a .NET Framework Form will be extended to process keyboard commands, via the KeyDown event, in a uniform fashion. All forms in the application will then inherit from this new base form and anytime a Button control is used, the new extended Button control will be used. The Base Form will then capture each keystroke and determine if there is an action to take by determining if a Button control on the form has a matching key code.

To get started add a new User Control to the project in Microsoft Visual Studio by right-clicking on the project in the Solution Explorer, selecting Add, then User Control. Name the User Control "myButton.cs" and click Add. In the code view of the User Control change what myButton inherits from (UserControl) to Button. Then add a new property with Keys as the type, named "KeyCode". When finished, the myButton.cs code view should look similar to the below.

public partial class myButton : Button

{

public Keys KeyCode { get; set; }

 

public myButton()

{

InitializeComponent();

}

}

Next add a new Form to the project by right-clicking on the project in the Solution Explorer, selecting "Add", then Windows Form. Name the Form "BaseForm.cs" and click "Add". Open the code view of the Form and override the OnKeyDown method. Within the OnKeyDown method all the myButton controls on the form will be scanned to determine if the keystroke captured matches the button's KeyCode property. If a match is found then the button's click event is fired. When finished the BaseForm.cs code view should look similar to the below.

public partial class BaseForm : Form

{

public BaseForm()

{

InitializeComponent();

}

 

protected override void OnKeyDown(KeyEventArgs e)

{

foreach (Control control in this.Controls)

{

if (control is myButton)

{

if ((control as myButton).KeyCode == e.KeyCode)

{

(control as myButton).PerformClick();

}

}

}

 

base.OnKeyDown(e);

}

}

The extended button and base form are now ready to be implemented on the existing form in the project, MainForm.cs. In the designer view of MainForm remove the existing buttons and replace them with the new myButton controls. In the Properties for the myButton controls set the first button's KeyCode property to "F1" and the second button's KeyCode property to "F2". Once the KeyCode properties are set, create the buttons' click event handlers. The handler for MainForm's KeyDown event can also be removed as it is no longer needed.

[image2_vsdesigner.png]
Figure 2

Switch to the code view of MainForm.cs and change what the form inherits from (Form) to "BaseForm". Finally, in the first button's click event add a message box that displays "Button One Clicked" and "Button Two Clicked" in the second button's click event.

public partial class MainForm : BaseForm

{

public MainForm()

{

InitializeComponent();

}

 

private void cmdButtonOne_Click(object sender, EventArgs e)

{

MessageBox.Show("Button One Clicked");

}

 

private void cmdButtonTwo_Click(object sender, EventArgs e)

{

MessageBox.Show("Button Two Clicked");

}

}

Run the application in "Debug" mode and click on the first button, then the second button. Then using the keyboard click the F1 and F2 keys individually, acknowledging the message box between clicks.

[image3_approach1.png]
Figure 3

While this is a simple approach, it allows a Developer to focus on functionality without concerning themselves with implementing the keyboard interface on each individual form. For each form in the application the developer only has to inherit from "BaseForm" and use the myButton control instead of the standard Button control. Then for those features / actions initiated by a button but also require a keyboard command it is as simple as setting a property in the designer.

There are limitations to this approach. In the implementation above only myButton controls directly on the form will be found in the override method of OnKeyDown. If there is a myButton control nested in a panel or other control it will not be found. The approach also only extends the Button control with the KeyCode property. While additional controls could be extended to also include the KeyCode property it will not handle cases where a keyboard command is not associated with a control on the form. The next approach is more robust in terms of what is possible but comes with more complexities.

Implementing a Keyboard Interface using .NET Controls for a WinForm Client

Approach #2: Custom Classes and Dynamic Controls

In this approach, custom classes, events and dynamic creation of controls will be used to give more robust flexibility to each form while maintaining a consistent feel for the user. This approach uses the concept that a form can only be in one "mode" ( Edit, Add, Delete, etc...) at a time. For this concept to work we need to create a class to hold information about the mode. This information will determine how the mode is entered (button, keyboard command, both, etc...) and what other modes are visible while the form is in a particular mode. As the basis for this approach the form mode class will have to be created first.

Right-click on the project in the Solution Explorer, select Add, then Class. Name the class "FormMode.cs" and click Add. Create a public enumeration that will allow consistent naming and use of form modes across all forms. Call the enumeration "Mode" and populate it with 'Default', 'Add', 'Edit', 'Delete', and 'Exit'. The FormMode properties and constructor need to be added next. See the below code extract to get the full setup of what the FormMode class should look like when fully created.

public class FormMode

{

public enum Mode

{

Default = 1,

Add = 10,

Edit = 20,

Delete = 30,

Exit = 40

}

 

public Mode SelectedMode { get; set; }

public bool ShowButton { get; set; }

public Keys ModeHotKey { get; set; }

public string ButtonText { get; set; }

public List ModesToHide { get; set; }

public bool IsModeEnabled { get; set; }

 

public FormMode(Mode selectedMode, bool showButton, Keys modeHotKey, string buttonText,List modesToHide, bool isModeEnabled)

{

SelectedMode = selectedMode;

ShowButton = showButton;

ModeHotKey = modeHotKey;

ButtonText = buttonText;

ModesToHide = modesToHide;

IsModeEnabled = isModeEnabled;

}

}

Now that the FormMode class has been created, the base form, or the form that all other forms will inherit from, can be created. Reusing the BaseForm.cs form the first approach remove the added code to the override of the OnKeyDown method, but leave the override as it will still be used. In the designer add a FlowLayoutPanel control and dock it to the right side the form. Name the control "flpButtons". This is all that needs to be added to the base form from the designer so switch into code view.

The base form will need a consistent way to communicate that the form's mode is changing and to do that a custom event will be used. In the code view of BaseForm.cs add the following code to create the event that will be fired each time the form's mode changes.

public delegate void FormModeChangedEventHandler(FormModeChangedEventArgs e);

public event FormModeChangedEventHandler FormModeChanged;

public class FormModeChangedEventArgs : EventArgs

{

public FormModeChangedEventArgs(FormMode.Mode oldFormMode, FormMode.Mode

newFormMode)

{

OldFormMode = oldFormMode;

NewFormMode = newFormMode;

}

 

public FormMode.Mode OldFormMode { get; set; }

public FormMode.Mode NewFormMode { get; set; }

}

Next create one property and two collections on the base form. The property will hold the current form mode and the first collection will contain all available modes the form can possibly have, while the second collection will contain a more dynamic list of the modes that are currently available to the user.

private FormMode.Mode currentFormMode = FormMode.Mode.Default;

public FormMode.Mode CurrentFormMode

{

get

{

return currentFormMode;

}

set

{

FormMode.Mode oldFormMode = currentFormMode;

currentFormMode = value;

this.FormModeChanged(new FormModeChangedEventArgs(oldFormMode,

currentFormMode));

InitializeButtons();

}

}

 

protected List formModeCollection = new List();

public List FormModeCollection

{

get

{

return formModeCollection;

}

set

{

formModeCollection = value;

}

}

 

private List formModeActiveCollection = new List();

The base form now has all the core elements to drive mode-based behavior consistently on all inherited forms within the application. What is missing is the helper method to control the buttons on the form based on the current mode, the OnKeyDown method override and button click events. The helper method InitializeButtons will be used to determine what modes are available based on the current mode, then determine if those available modes have a button to display. The below code snippet is what the InitializeButtons method should look like when finished.

protected void InitializeButtons()

{

formModeActiveCollection = new List();

 

// Find the current form mode object to determine

// what other modes should not be displayed

FormMode fmCurrent = null;

foreach (FormMode curMode in FormModeCollection)

{

if (curMode.SelectedMode == CurrentFormMode)

{

fmCurrent = curMode;

break;

}

}

if (fmCurrent == null)

throw new Exception("Current FormMode not found!");

 

 

foreach (FormMode formmode in FormModeCollection)

{

bool matchFound = false;

foreach (FormMode.Mode mode in fmCurrent.ModesToHide)

{

if (formmode.SelectedMode == mode)

{

matchFound = true;

break;

}

}

// If a match was found the mode of that form mode

// shouldn't be enabled for action, either through

// button interface or keyboard

if (matchFound)

{

formmode.IsModeEnabled = false;

continue;

}

else

{

formmode.IsModeEnabled = true;

 

formModeActiveCollection.Add(formmode);

}

}

 

this.flpButtons.Controls.Clear();

 

// Using the collection of modes that are active build

// the list of buttons to display in the Flow Layout Panel

foreach (FormMode fmButton in formModeActiveCollection)

{

Button buttonShell = new Button();

 

buttonShell.Size = new System.Drawing.Size(150, 23);

buttonShell.TabStop = false;

buttonShell.Text = fmButton.ButtonText;

buttonShell.Tag = fmButton;

 

// The ShowButton allows a form mode to still be

// enabled, but not show a button

buttonShell.Visible = fmButton.ShowButton;

buttonShell.Click += new EventHandler(buttonShell_Click);

 

this.flpButtons.Controls.Add(buttonShell);

}

}

The click event for the dynamically created buttons should change the form's mode to the mode of the button. The below code uses the FormMode object placed in the Tab property of the button to change the from's mode to that of the button's mode, via the FormMode object. When finished the buttonShell_Click event handler should have the following code.

private void buttonShell_Click(object sender, EventArgs e)

{

if (((sender as Button).Tag is FormMode))

{

FormMode formMode = ((sender as Button).Tag as FormMode);

CurrentFormMode = formMode.SelectedMode;

}

}

The last thing to code on the base form is the override method for the OnKeyDown method. 

protected override void OnKeyDown(KeyEventArgs e)

{

// Handles form action key codes

foreach (FormMode formmode in formModeActiveCollection)

{

if (formmode.ModeHotKey == e.KeyCode && formmode.IsModeEnabled)

{

CurrentFormMode = formmode.SelectedMode;

e.Handled = true;

}

}

 

base.OnKeyDown(e);

}

The base form is now complete and ready to be used by the application's forms through inheritance. Open the designer for MainForm.cs in Microsoft Visual Studio and remove the two existing buttons along with their handler methods. Since the buttons on the form will be dynamically driven there is no need to keep the static buttons. With the form selected open up the events filter of the Properties window and find the new FormModeChanged event. Double-click the event to create the handler method for it in the code.

In the code view, MainForm is already inheriting from BaseForm from the first approach. If it were not already inheriting from BaseForm the FormModeChanged event would not have been available in the designer's Properties windows. Since MainForm inherits from BaseForm all the behind the scenes wiring is already in place to handle form modes and fire the FormModeChanged event each time the mode changes. What needs to be done now, and on each form of the application is tell the form what modes are available. The below code block from MainForm's constructor shows MainForm being configured to allow the 'Default', 'Add', 'Edit' and 'Exit' modes.

// Default Mode

FormModeCollection.Add(new FormMode(FormMode.Mode.Default, false, Keys.KeyCode, string.Empty, new List(), false));

 

// Add Mode

List hideListAdd = new List();

hideListAdd.Add(FormMode.Mode.Edit);

FormModeCollection.Add(new FormMode(FormMode.Mode.Add, true, Keys.F1, "F1 - Add", hideListAdd, true));

 

// Edit Mode

List hideListEdit = new List();

hideListEdit.Add(FormMode.Mode.Add);

FormModeCollection.Add(new FormMode(FormMode.Mode.Edit, true, Keys.F2, "F2 - Edit", hideListEdit, true));

 

// Exit Mode

FormModeCollection.Add(new FormMode(FormMode.Mode.Exit, true, Keys.Escape, "Esc - Exit", new List(), true));

If the application is run at this point nothing shows on the form because the InitializeButtons method is not being called. Since the InitializeButtons method cannot be invoked until there are FormMode objects in the collection the call to the method needs to be placed in the Load event of MainForm. Once the InitializeButtons method is in the Load event of MainForm, run the application to view the three buttons, per what was specified in the setup of MainForm's modes. Notice a button does not show up for the 'Default' mode due to the settings of the mode. The buttons availability will dynamically be updated based on the click events or keyboard commands received. Try clicking on the "Add" button or pressing 'F1' and see how the "Edit" button disappears and the application no longer recognizes the 'F2' keystroke.

[image3_approach2.png]
Figure 4

The last remaining part is to handle the form mode changes. Since each form usually has a different purpose and function this implementation is form specific. An example implementation is shown in the code block below.

private void MainForm_FormModeChanged(FormModeChangedEventArgs e)

{

switch (e.NewFormMode)

{

case FormMode.Mode.Default:

break;

case FormMode.Mode.Add:

// Perform Add action...

break;

case FormMode.Mode.Edit:

// Perform Edit action...

break;

case FormMode.Mode.Exit:

// Exit the form, prompt to save changes, etc...

break;

default:

break;

}

}

This second approach gives the user a consistent feel all the while providing the efficiencies that allow the Developer to focus on functionality and worry less about how to implement both a UI and keyboard interface for the application.

One nice feature about this approach is that it gives a Developer nearly limitless options in terms of the different modes a form can have. All it takes is adding more modes to the enumeration and then using those modes in the inherited forms.

Conclusion

You have just walked through creating a keyboard interface for a Microsoft .NET Windows Form client using a few different approaches. The approaches, from the most basic and simplest, to the more robust and complicated ones, demonstrated how to use .NET controls, inheritance and events to achieve a consistent and efficient keyboard interface for the end user.



About the Author

Matt Goebel

Matt Goebel is the Founder and President of AP Innovation, Inc. in Indianapolis, Indiana. He can be reached at 859-802-7591 or matt.goebel@apinnovation.com.

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Cisco and Intel have harnessed flash memory technology and truly innovative system software to blast through the boundaries of today's I/O-bound server/storage architectures. See how they are bringing real-time responsiveness to data-intensive applications—for unmatched business advantage. Sponsored by Cisco and Intel® Partnering in Innovation

  • Live Event Date: August 14, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT Data protection has long been considered "overhead" by many organizations in the past, many chalking it up to an insurance policy or an extended warranty you may never use. The realities of today makes data protection a must-have, as we live in a data-driven society -- the digital assets we create, share, and collaborate with others on must be managed and protected for many purposes. Check out this upcoming eSeminar and join Seagate Cloud …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds