Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Mapping Keys


If you haven’t tried out the customize key mappings feature in Visual Studio, it offers the ability to map (or remap) keystrokes including Ctrl, Shift, and Alt combinations to specific features in Visual Studio. This can be really handy if you use a particular feature of Visual Studio very often in your development activities. For myself, I’ve mapped several things I do a great deal of during the day to keystrokes that make sense to me. However, the idea of mapping keys to features can be really useful for other types of applications also, and this is the topic for this issue.

The most likely user interface approach for specifying what keys to use for a mapping is an edit control that displays the results of a set of keystrokes as they occur to provide intuitive feedback to the user. The trick is in displaying special keys like Ctrl, Shift, and Alt when appropriate.

Let’s take a look at some code that demonstrates how to do this. We’ll use MFC to keep things simple.

First, let's derive a new CEdit-type class named CKeyEdit that will subclass the edit control and override handling for the keys. It would look like this:

class CKeyEdit : public CEdit
{
// Construction
public:
	CKeyEdit();

// Attributes
public:
	WORD GetVirtualKey() { return m_uVKey; }
	WORD GetKeyType() { return m_uKeyType; }

// Operations
public:

// Overrides
	// ClassWizard generated virtual function overrides
	//{{AFX_VIRTUAL(CKeyEdit)
	//}}AFX_VIRTUAL

// Implementation
public:
	virtual ~CKeyEdit();

	// Generated message map functions
protected:
	//{{AFX_MSG(CKeyEdit)
	afx_msg void OnSysKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
	afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
	afx_msg void OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags);
	afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);
	afx_msg void OnSysKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags);
	//}}AFX_MSG

	DECLARE_MESSAGE_MAP()

private:
	WORD ConvertSpecialKeysToAttrib();
	CString ConvertKeyToString( UINT nChar,UINT nFlags );

private:
	UINT m_uVKey;
	UINT m_uKeyType;
};

For now, ignore the private members. We’ll get to them in a bit. Notice that we create handlers for several Windows messages that deal with keys. If you’ve ever done much work with keystroke handling, you know that it is often necessary to trap multiple messages since they are at a fairly fine level of granularity. In this case, we trap OnSysKeyDown/OnSysKeyUp to handle the special keys like Ctrl, Alt, and Shift, OnKeyDown/OnKeyUp to handle the nonspecial keys and OnChar to override the display of the character that was pressed, and allow us to display it whatever way we wish. The latter override is how we actually handle displaying the keystroke combination itself instead of the keystroke characters that would normally be shown by Windows.

Now, let’s look at the implementation:

// KeyEdit.cpp : implementation file

#include "stdafx.h"
#include "KeyEdit.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

After the typical stuff at the top of the source file, we define an enumeration to handle the attributes of the keystroke in case it included some special characters.

enum KeyAttribType {
	KEY_ATTRIB_NONE=0x0,
	KEY_ATTRIB_CTRL=0x1,
	KEY_ATTRIB_ALT=0x2,
	KEY_ATTRIB_SHIFT=0x4
};

CKeyEdit::CKeyEdit()
{
	m_uVKey = 0;
	m_uKeyType = KEY_ATTRIB_NONE;
}

CKeyEdit::~CKeyEdit()
{
}

BEGIN_MESSAGE_MAP(CKeyEdit, CEdit)
	//{{AFX_MSG_MAP(CKeyEdit)
	ON_WM_SYSKEYDOWN()
	ON_WM_KEYDOWN()
	ON_WM_KEYUP()
	ON_WM_CHAR()
	ON_WM_SYSKEYUP()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

Next, we define the handler for special keys (both up and down). For the down keystroke, we filter on the Ctrl, Shift, and Alt keys. If we find that one of these keys is pressed, the key is converted to a textual representation and the edit control is updated with this text. We also store off the actual key information in some private members (m_uVKey and m_uKeyType) so they can be retrieved by external code.

void CKeyEdit::OnSysKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	if ( nChar != VK_CONTROL && nChar != VK_SHIFT && nChar != VK_MENU )
	{
		m_uVKey = nChar;
		m_uKeyType = ConvertSpecialKeysToAttrib();
		SetWindowText( ConvertKeyToString(nChar,nFlags) );
	}
}

void CKeyEdit::OnSysKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
}  

Then, we define the handler for nonspecial (normal) keys such as letters, numbers, and the like. Notice that we filter out alphanumeric keys that are pressed by themselves-we only care about combinations that occur with one of the special keys. You could obviously change this logic for your own circumstances. Also, note that the backspace (VK_BACK) removes the entire entry in the control instead of a single character, since this is more logical for this type of custom control.

void CKeyEdit::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	if ( (nChar >= 'A' && nChar <= 'Z') ||
		 (nChar >= '0' && nChar <= '9') )
	{
		// make sure that at least the Ctrl or Alt key is
// also pressed. Otherwise ignore this keystroke.
		DWORD w = ConvertSpecialKeysToAttrib();
		if ( w != KEY_ATTRIB_NONE && w != KEY_ATTRIB_SHIFT )
		{
			m_uVKey = nChar;
			m_uKeyType = ConvertSpecialKeysToAttrib();
			SetWindowText( ConvertKeyToString(nChar,nFlags) );
		}
	}
	else
	if ( nChar >= VK_F1 && nChar <= VK_F12 )
	{
		m_uVKey = nChar;
		m_uKeyType = ConvertSpecialKeysToAttrib();
		SetWindowText( ConvertKeyToString(nChar,nFlags) );
	}
	else
	if ( nChar == VK_BACK )
	{
		m_uVKey = 0;
		m_uKeyType = KEY_ATTRIB_NONE;
		SetWindowText( "" );
	}
}

void CKeyEdit::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
}

The WM_CHAR handler OnChar is overridden with no implementation to keep the default behavior in the edit control from displaying the character that was pressed. Basically, we just eat the keystroke.

void CKeyEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
}

Now, we finally get to the private members that assist the Windows message handlers in converting a key to a textual representation. The comments in the code below should guide you on what is happening, but you may want to fire up

Microsoft Developer Network if you need more information on particular Win32 API calls.

CString CKeyEdit::ConvertKeyToString( UINT nChar,UINT nFlags )
{
	// convert the key and its flags to a character string.
	LONG uExt = nFlags >> 7;
	LONG uScan = nFlags & 127;
	LONG lKey = (uExt << 25) | (uScan << 16);
	TCHAR buf[100];
	GetKeyNameText( lKey,buf,sizeof(buf));

	// if 1 or more special keys were struck, prepend strings.
	WORD wAttrib = ConvertSpecialKeysToAttrib();
	CString strKey;
	if ( wAttrib & KEY_ATTRIB_CTRL )
		strKey = "Ctrl";
	if ( wAttrib & KEY_ATTRIB_ALT )
	{
		if ( strKey.GetLength() )
			strKey += "+";
		strKey += "Alt";
	}
	if ( wAttrib & KEY_ATTRIB_SHIFT )
	{
		if ( strKey.GetLength() )
			strKey += "+";
		strKey += "Shift";
	}
	if ( strKey.GetLength() )
		strKey += "+";
	strKey += buf;

	return strKey;
}

WORD CKeyEdit::ConvertSpecialKeysToAttrib()
{
	WORD wAttrib = KEY_ATTRIB_NONE;
	if ( GetKeyState(VK_CONTROL) < 0 )
		wAttrib |= KEY_ATTRIB_CTRL;
	if ( GetKeyState(VK_SHIFT) < 0 )
		wAttrib |= KEY_ATTRIB_SHIFT;
	if ( GetKeyState(VK_MENU) < 0 )
		wAttrib |= KEY_ATTRIB_ALT;
	return wAttrib;
}

Ok, so we're now ready to try this custom code out in a real application. I went ahead and created a simple MFC AppWizard-based dialog application with a single edit control to illustrate the behavior of KeyEdit. After running the application, here is what appears:

Pretty exciting. Next, I'll enter a key combination. I'll try Ctrl-S. Here is what appears:

Nice. Let’s now try something that include multiple special keys such as Ctrl, Shift, and Alt.

At this point, you should get the idea of what is going on and how it appears to the user. You could obviously adapt this code to subclass something other than an edit control, but the logic should be similar. The trick here is to know to trap and eat all relevant messages when dealing with keystrokes, not just WM_CHAR or WM_KEYDOWN.


Mark M. Baker is the Chief of Research & Development at BNA Software located in Washington, D.C.
Do you have a Windows development question? Send it to [email protected].


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.