使用键盘快捷键
本部分介绍与键盘快捷键关联的任务。
使用快捷键表资源
向应用程序添加加速器支持的最常用方法是将加速器表资源包含在应用程序的可执行文件中,然后在运行时加载资源。
此部分涵盖以下主题。
创建加速器表资源
使用应用程序的资源定义文件中的 ACCELERATORS 语句创建加速器表资源。 必须向加速器表分配名称或资源标识符,最好不同于任何其他资源的名称或资源标识符。 系统使用此标识符在运行时加载资源。
定义的每个加速器都需要快捷键表中的单独条目。 在每个条目中,定义击键 (生成加速器和加速器标识符的 ASCII 字符代码或虚拟键代码) 。 还必须指定击键是否必须与 Alt、SHIFT 或 CTRL 键结合使用。 有关虚拟键的详细信息,请参阅 键盘输入。
通过将 ASCII 字符括在双引号中或将该字符的整数值与 ASCII 标志结合使用来指定 ASCII 键击。 以下示例演示如何定义 ASCII 加速器。
"A", ID_ACCEL1 ; SHIFT+A
65, ID_ACCEL2, ASCII ; SHIFT+A
虚拟键代码击键的指定方式不同,具体取决于击键是字母数字键还是非字母数字键。 对于字母数字键,键的字母或数字(括在双引号中)与 VIRTKEY 标志组合在一起。 对于非字母数字键,特定键的虚拟键代码与 VIRTKEY 标志合并。 以下示例演示如何定义虚拟键代码加速器。
"a", ID_ACCEL3, VIRTKEY ; A (caps-lock on) or a
VK_INSERT, ID_ACCEL4, VIRTKEY ; INSERT key
以下示例演示了定义文件操作加速器的加速器表资源。 资源的名称为 FileAccel。
FileAccel ACCELERATORS
BEGIN
VK_F12, IDM_OPEN, CONTROL, VIRTKEY ; CTRL+F12
VK_F4, IDM_CLOSE, ALT, VIRTKEY ; ALT+F4
VK_F12, IDM_SAVE, SHIFT, VIRTKEY ; SHIFT+F12
VK_F12, IDM_SAVEAS, VIRTKEY ; F12
END
如果希望用户结合快捷键击按 Alt、SHIFT 或 CTRL 键,请在快捷键的定义中指定 ALT、SHIFT 和 CONTROL 标志。 下面是一些示例。
"B", ID_ACCEL5, ALT ; ALT_SHIFT+B
"I", ID_ACCEL6, CONTROL, VIRTKEY ; CTRL+I
VK_F5, ID_ACCEL7, CONTROL, ALT, VIRTKEY ; CTRL+ALT+F5
默认情况下,当快捷键对应于菜单项时,系统会突出显示菜单项。 可以使用 NOINVERT 标志来防止单个加速器的突出显示。 以下示例演示如何使用 NOINVERT 标志:
VK_DELETE, ID_ACCEL8, VIRTKEY, SHIFT, NOINVERT ; SHIFT+DELETE
若要定义与应用程序中的菜单项对应的加速器,请在菜单项的文本中包含快捷键。 以下示例演示如何在资源定义文件的菜单项文本中包含快捷键。
FilePopup MENU
BEGIN
POPUP "&File"
BEGIN
MENUITEM "&New..", IDM_NEW
MENUITEM "&Open\tCtrl+F12", IDM_OPEN
MENUITEM "&Close\tAlt+F4" IDM_CLOSE
MENUITEM "&Save\tShift+F12", IDM_SAVE
MENUITEM "Save &As...\tF12", IDM_SAVEAS
END
END
加载加速器表资源
应用程序通过调用 LoadAccelerators 函数并指定其可执行文件包含资源以及资源的名称或标识符的应用程序的实例句柄来加载加速器表资源。 LoadAccelerators 将指定的加速器表加载到内存中,并将句柄返回到加速器表。
应用程序可以随时加载加速器表资源。 通常,单线程应用程序在进入其main消息循环之前加载其加速器表。 使用多个线程的应用程序通常会在进入线程的消息循环之前加载线程的加速器表资源。 应用程序或线程还可能使用多个快捷键表,每个表都与应用程序中的特定窗口相关联。 每次用户激活窗口时,此类应用程序都会加载窗口的快捷键表。 有关线程的详细信息,请参阅 进程和线程。
调用 Translate Accelerator 函数
若要处理加速器,应用程序的 (或线程的) 消息循环必须包含对 TranslateAccelerator 函数的调用。 TranslateAccelerator 将击键与快捷键表进行比较,如果找到匹配项,则会将击键转换为 WM_COMMAND (或 WM_SYSCOMMAND) 消息。 然后,该函数将消息发送到窗口过程。 TranslateAccelerator 函数的参数包括用于接收WM_COMMAND消息的窗口句柄、用于转换加速器的快捷键表的句柄,以及指向包含队列消息的 MSG 结构的指针。 以下示例演示如何从消息循环中调用 TranslateAccelerator 。
MSG msg;
BOOL bRet;
while ( (bRet = GetMessage(&msg, (HWND) NULL, 0, 0)) != 0)
{
if (bRet == -1)
{
// handle the error and possibly exit
}
else
{
// Check for accelerator keystrokes.
if (!TranslateAccelerator(
hwndMain, // handle to receiving window
haccel, // handle to active accelerator table
&msg)) // message data
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
处理WM_COMMAND消息
使用加速器时, TranslateAccelerator 函数中指定的窗口将接收 WM_COMMAND 或 WM_SYSCOMMAND 消息。 wParam 参数的低序字包含加速器的标识符。 窗口过程检查标识符以确定 WM_COMMAND 消息的源并相应地处理消息。
通常,如果加速键对应于应用程序中的菜单项,则为加速键和菜单项分配相同的标识符。 如果需要知道 WM_COMMAND 消息是由加速键还是由菜单项生成的,可以检查 wParam 参数的高序字。 如果加速键生成了消息,则高序字为 1;如果菜单项生成了消息,则高序字为 0。
销毁加速器表资源
系统会自动销毁 LoadAccelerators 函数加载的加速器表资源,在应用程序关闭后从内存中删除资源。
为字体属性创建快捷键
本部分中的示例演示如何执行以下任务:
- 创建加速器表资源。
- 在运行时加载加速器表。
- 在消息循环中转换加速器。
- 处理 WM_COMMAND 加速器生成的消息。
这些任务在包含 “字符 ”菜单和允许用户选择当前字体属性的相应快捷键的应用程序上下文中演示。
资源定义文件的以下部分定义 “字符 ”菜单和关联的快捷键表。 请注意,菜单项显示快捷键击,并且每个快捷键与其关联的菜单项具有相同的标识符。
#include <windows.h>
#include "acc.h"
MainMenu MENU
{
POPUP "&Character"
{
MENUITEM "&Regular\tF5", IDM_REGULAR
MENUITEM "&Bold\tCtrl+B", IDM_BOLD
MENUITEM "&Italic\tCtrl+I", IDM_ITALIC
MENUITEM "&Underline\tCtrl+U", IDM_ULINE
}
}
FontAccel ACCELERATORS
{
VK_F5, IDM_REGULAR, VIRTKEY
"B", IDM_BOLD, CONTROL, VIRTKEY
"I", IDM_ITALIC, CONTROL, VIRTKEY
"U", IDM_ULINE, CONTROL, VIRTKEY
}
应用程序的源文件中的以下部分演示如何实现加速器。
HWND hwndMain; // handle to main window
HANDLE hinstAcc; // handle to application instance
int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpCmdLine, int nCmdShow)
{
MSG msg; // application messages
BOOL bRet; // for return value of GetMessage
HACCEL haccel; // handle to accelerator table
// Perform the initialization procedure.
// Create a main window for this application instance.
hwndMain = CreateWindowEx(0L, "MainWindowClass",
"Sample Application", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL,
hinst, NULL );
// If a window cannot be created, return "failure."
if (!hwndMain)
return FALSE;
// Make the window visible and update its client area.
ShowWindow(hwndMain, nCmdShow);
UpdateWindow(hwndMain);
// Load the accelerator table.
haccel = LoadAccelerators(hinstAcc, "FontAccel");
if (haccel == NULL)
HandleAccelErr(ERR_LOADING); // application defined
// Get and dispatch messages until a WM_QUIT message is
// received.
while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0)
{
if (bRet == -1)
{
// handle the error and possibly exit
}
else
{
// Check for accelerator keystrokes.
if (!TranslateAccelerator(
hwndMain, // handle to receiving window
haccel, // handle to active accelerator table
&msg)) // message data
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
return msg.wParam;
}
LRESULT APIENTRY MainWndProc(HWND hwndMain, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
BYTE fbFontAttrib; // array of font-attribute flags
static HMENU hmenu; // handle to main menu
switch (uMsg)
{
case WM_CREATE:
// Add a check mark to the Regular menu item to
// indicate that it is the default.
hmenu = GetMenu(hwndMain);
CheckMenuItem(hmenu, IDM_REGULAR, MF_BYCOMMAND |
MF_CHECKED);
return 0;
case WM_COMMAND:
switch (LOWORD(wParam))
{
// Process the accelerator and menu commands.
case IDM_REGULAR:
case IDM_BOLD:
case IDM_ITALIC:
case IDM_ULINE:
// GetFontAttributes is an application-defined
// function that sets the menu-item check marks
// and returns the user-selected font attributes.
fbFontAttrib = GetFontAttributes(
(BYTE) LOWORD(wParam), hmenu);
// SetFontAttributes is an application-defined
// function that creates a font with the
// user-specified attributes the font with
// the main window's device context.
SetFontAttributes(fbFontAttrib);
break;
default:
break;
}
break;
// Process other messages.
default:
return DefWindowProc(hwndMain, uMsg, wParam, lParam);
}
return NULL;
}
使用在运行时创建的加速器表
本主题讨论如何使用在运行时创建的加速器表。
创建Run-Time加速器表
在运行时创建加速器表的第一步是填充 ACCEL 结构的数组。 数组中的每个结构在 表中定义一个加速器。 加速器的定义包括其标志、键和标识符。 ACCEL 结构具有以下形式。
typedef struct tagACCEL { // accl
BYTE fVirt;
WORD key;
WORD cmd;
} ACCEL;
通过在 ACCEL 结构的键成员中指定 ASCII 字符代码或虚拟键代码,可以定义加速器的击键。 如果指定虚拟密钥代码,则必须先在 fVirt 成员中包含 FVIRTKEY 标志;否则,系统会将代码解释为 ASCII 字符代码。 可以包含 FCONTROL、 FALT 或 FSHIFT 标志,或全部三个标志,以将 CTRL、ALT 或 SHIFT 键与击键组合在一起。
若要创建快捷键表,请将指向 ACCEL 结构数组的指针传递给 CreateAcceleratorTable 函数。 CreateAcceleratorTable 创建快捷键表,并返回表的句柄。
处理加速器
加载和调用运行时创建的加速器表提供的加速器的过程与处理加速器表资源提供的加速器的过程相同。 有关详细信息,请参阅通过处理WM_COMMAND消息加载加速器表资源。
销毁Run-Time加速器表
系统会自动销毁在运行时创建的加速器表,并在应用程序关闭后从内存中删除资源。 可以通过将表的句柄传递给 DestroyAcceleratorTable 函数来销毁快捷键表并将其从内存中删除。
创建用户可编辑加速器
此示例演示如何构造允许用户更改与菜单项关联的加速键的对话框。 该对话框包含一个包含菜单项的组合框、一个包含键名称的组合框,以及用于选择 Ctrl、Alt 和 SHIFT 键的检查框。 下图显示了对话框。
以下示例演示如何在资源定义文件中定义对话框。
EdAccelBox DIALOG 5, 17, 193, 114
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION
CAPTION "Edit Accelerators"
BEGIN
COMBOBOX IDD_MENUITEMS, 10, 22, 52, 53,
CBS_SIMPLE | CBS_SORT | WS_VSCROLL |
WS_TABSTOP
CONTROL "Control", IDD_CNTRL, "Button",
BS_AUTOCHECKBOX | WS_TABSTOP,
76, 35, 40, 10
CONTROL "Alt", IDD_ALT, "Button",
BS_AUTOCHECKBOX | WS_TABSTOP,
76, 48, 40, 10
CONTROL "Shift", IDD_SHIFT, "Button",
BS_AUTOCHECKBOX | WS_TABSTOP,
76, 61, 40, 10
COMBOBOX IDD_KEYSTROKES, 124, 22, 58, 58,
CBS_SIMPLE | CBS_SORT | WS_VSCROLL |
WS_TABSTOP
PUSHBUTTON "Ok", IDOK, 43, 92, 40, 14
PUSHBUTTON "Cancel", IDCANCEL, 103, 92, 40, 14
LTEXT "Select Item:", 101, 10, 12, 43, 8
LTEXT "Select Keystroke:", 102, 123, 12, 60, 8
END
应用程序的菜单栏包含 一个 Character 子菜单,其项具有与之关联的快捷键。
MainMenu MENU
{
POPUP "&Character"
{
MENUITEM "&Regular\tF5", IDM_REGULAR
MENUITEM "&Bold\tCtrl+B", IDM_BOLD
MENUITEM "&Italic\tCtrl+I", IDM_ITALIC
MENUITEM "&Underline\tCtrl+U", IDM_ULINE
}
}
FontAccel ACCELERATORS
{
VK_F5, IDM_REGULAR, VIRTKEY
"B", IDM_BOLD, CONTROL, VIRTKEY
"I", IDM_ITALIC, CONTROL, VIRTKEY
"U", IDM_ULINE, CONTROL, VIRTKEY
}
菜单模板的菜单项值是在应用程序的头文件中定义如下的常量。
#define IDM_REGULAR 1100
#define IDM_BOLD 1200
#define IDM_ITALIC 1300
#define IDM_ULINE 1400
对话框使用应用程序定义的 VKEY 结构的数组,每个结构都包含一个击键文本字符串和一个快捷键文本字符串。 创建对话框后,它会分析数组,并将每个击键文本字符串添加到 “选择击键 ”组合框中。 当用户单击“ 确定 ”按钮时,对话框将查找所选的击键文本字符串并检索相应的快捷键文本字符串。 该对话框将快捷键文本字符串追加到用户选择的菜单项的文本。 以下示例演示 VKEY 结构的数组:
// VKey Lookup Support
#define MAXKEYS 25
typedef struct _VKEYS {
char *pKeyName;
char *pKeyString;
} VKEYS;
VKEYS vkeys[MAXKEYS] = {
"BkSp", "Back Space",
"PgUp", "Page Up",
"PgDn", "Page Down",
"End", "End",
"Home", "Home",
"Lft", "Left",
"Up", "Up",
"Rgt", "Right",
"Dn", "Down",
"Ins", "Insert",
"Del", "Delete",
"Mult", "Multiply",
"Add", "Add",
"Sub", "Subtract",
"DecPt", "Decimal Point",
"Div", "Divide",
"F2", "F2",
"F3", "F3",
"F5", "F5",
"F6", "F6",
"F7", "F7",
"F8", "F8",
"F9", "F9",
"F11", "F11",
"F12", "F12"
};
对话框的初始化过程将填充 “选择项” 和 “选择击键 ”组合框。 用户选择菜单项和关联的快捷键后,对话框将检查对话框中的控件以获取用户的选择,更新菜单项的文本,然后创建包含用户定义的新快捷键的新快捷键表。 以下示例演示对话框过程。 请注意,必须在窗口过程中进行初始化。
// Global variables
HWND hwndMain; // handle to main window
HACCEL haccel; // handle to accelerator table
// Dialog-box procedure
BOOL CALLBACK EdAccelProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
int nCurSel; // index of list box item
UINT idItem; // menu-item identifier
UINT uItemPos; // menu-item position
UINT i, j = 0; // loop counters
static UINT cItems; // count of items in menu
char szTemp[32]; // temporary buffer
char szAccelText[32]; // buffer for accelerator text
char szKeyStroke[16]; // buffer for keystroke text
static char szItem[32]; // buffer for menu-item text
HWND hwndCtl; // handle to control window
static HMENU hmenu; // handle to "Character" menu
PCHAR pch, pch2; // pointers for string copying
WORD wVKCode; // accelerator virtual-key code
BYTE fAccelFlags; // fVirt flags for ACCEL structure
LPACCEL lpaccelNew; // pointer to new accelerator table
HACCEL haccelOld; // handle to old accelerator table
int cAccelerators; // number of accelerators in table
static BOOL fItemSelected = FALSE; // item selection flag
static BOOL fKeySelected = FALSE; // key selection flag
HRESULT hr;
INT numTCHAR; // TCHARs in listbox text
switch (uMsg)
{
case WM_INITDIALOG:
// Get the handle to the menu-item combo box.
hwndCtl = GetDlgItem(hwndDlg, IDD_MENUITEMS);
// Get the handle to the Character submenu and
// count the number of items it has. In this example,
// the menu has position 0. You must alter this value
// if you add additional menus.
hmenu = GetSubMenu(GetMenu(hwndMain), 0);
cItems = GetMenuItemCount(hmenu);
// Get the text of each item, strip out the '&' and
// the accelerator text, and add the text to the
// menu-item combo box.
for (i = 0; i < cItems; i++)
{
if (!(GetMenuString(hmenu, i, szTemp,
sizeof(szTemp)/sizeof(TCHAR), MF_BYPOSITION)))
continue;
for (pch = szTemp, pch2 = szItem; *pch != '\0'; )
{
if (*pch != '&')
{
if (*pch == '\t')
{
*pch = '\0';
*pch2 = '\0';
}
else *pch2++ = *pch++;
}
else pch++;
}
SendMessage(hwndCtl, CB_ADDSTRING, 0,
(LONG) (LPSTR) szItem);
}
// Now fill the keystroke combo box with the list of
// keystrokes that will be allowed for accelerators.
// The list of keystrokes is in the application-defined
// structure called "vkeys".
hwndCtl = GetDlgItem(hwndDlg, IDD_KEYSTROKES);
for (i = 0; i < MAXKEYS; i++)
{
SendMessage(hwndCtl, CB_ADDSTRING, 0,
(LONG) (LPSTR) vkeys[i].pKeyString);
}
return TRUE;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDD_MENUITEMS:
// The user must select an item from the combo
// box. This flag is checked during IDOK
// processing to be sure a selection was made.
fItemSelected = TRUE;
return 0;
case IDD_KEYSTROKES:
// The user must select an item from the combo
// box. This flag is checked during IDOK
// processing to be sure a selection was made.
fKeySelected = TRUE;
return 0;
case IDOK:
// If the user has not selected a menu item
// and a keystroke, display a reminder in a
// message box.
if (!fItemSelected || !fKeySelected)
{
MessageBox(hwndDlg,
"Item or key not selected.", NULL,
MB_OK);
return 0;
}
// Determine whether the CTRL, ALT, and SHIFT
// keys are selected. Concatenate the
// appropriate strings to the accelerator-
// text buffer, and set the appropriate
// accelerator flags.
szAccelText[0] = '\0';
hwndCtl = GetDlgItem(hwndDlg, IDD_CNTRL);
if (SendMessage(hwndCtl, BM_GETCHECK, 0, 0) == 1)
{
hr = StringCchCat(szAccelText, 32, "Ctl+");
if (FAILED(hr))
{
// TODO: write error handler
}
fAccelFlags |= FCONTROL;
}
hwndCtl = GetDlgItem(hwndDlg, IDD_ALT);
if (SendMessage(hwndCtl, BM_GETCHECK, 0, 0) == 1)
{
hr = StringCchCat(szAccelText, 32, "Alt+");
if (FAILED(hr))
{
// TODO: write error handler
}
fAccelFlags |= FALT;
}
hwndCtl = GetDlgItem(hwndDlg, IDD_SHIFT);
if (SendMessage(hwndCtl, BM_GETCHECK, 0, 0) == 1)
{
hr = StringCchCat(szAccelText, 32, "Shft+");
if (FAILED(hr))
{
// TODO: write error handler
}
fAccelFlags |= FSHIFT;
}
// Get the selected keystroke, and look up the
// accelerator text and the virtual-key code
// for the keystroke in the vkeys structure.
hwndCtl = GetDlgItem(hwndDlg, IDD_KEYSTROKES);
nCurSel = (int) SendMessage(hwndCtl,
CB_GETCURSEL, 0, 0);
numTCHAR = SendMessage(hwndCtl, CB_GETLBTEXTLEN,
nCursel, 0);
if (numTCHAR <= 15)
{
SendMessage(hwndCtl, CB_GETLBTEXT,
nCurSel, (LONG) (LPSTR) szKeyStroke);
}
else
{
// TODO: writer error handler
}
for (i = 0; i < MAXKEYS; i++)
{
//
// lstrcmp requires that both parameters are
// null-terminated.
//
if(lstrcmp(vkeys[i].pKeyString, szKeyStroke)
== 0)
{
hr = StringCchCopy(szKeyStroke, 16, vkeys[i].pKeyName);
if (FAILED(hr))
{
// TODO: write error handler
}
break;
}
}
// Concatenate the keystroke text to the
// "Ctl+","Alt+", or "Shft+" string.
hr = StringCchCat(szAccelText, 32, szKeyStroke);
if (FAILED(hr))
{
// TODO: write error handler
}
// Determine the position in the menu of the
// selected menu item. Menu items in the
// "Character" menu have positions 0,2,3, and 4.
// Note: the lstrcmp parameters must be
// null-terminated.
if (lstrcmp(szItem, "Regular") == 0)
uItemPos = 0;
else if (lstrcmp(szItem, "Bold") == 0)
uItemPos = 2;
else if (lstrcmp(szItem, "Italic") == 0)
uItemPos = 3;
else if (lstrcmp(szItem, "Underline") == 0)
uItemPos = 4;
// Get the string that corresponds to the
// selected item.
GetMenuString(hmenu, uItemPos, szItem,
sizeof(szItem)/sizeof(TCHAR), MF_BYPOSITION);
// Append the new accelerator text to the
// menu-item text.
for (pch = szItem; *pch != '\t'; pch++);
++pch;
for (pch2 = szAccelText; *pch2 != '\0'; pch2++)
*pch++ = *pch2;
*pch = '\0';
// Modify the menu item to reflect the new
// accelerator text.
idItem = GetMenuItemID(hmenu, uItemPos);
ModifyMenu(hmenu, idItem, MF_BYCOMMAND |
MF_STRING, idItem, szItem);
// Reset the selection flags.
fItemSelected = FALSE;
fKeySelected = FALSE;
// Save the current accelerator table.
haccelOld = haccel;
// Count the number of entries in the current
// table, allocate a buffer for the table, and
// then copy the table into the buffer.
cAccelerators = CopyAcceleratorTable(
haccelOld, NULL, 0);
lpaccelNew = (LPACCEL) LocalAlloc(LPTR,
cAccelerators * sizeof(ACCEL));
if (lpaccelNew != NULL)
{
CopyAcceleratorTable(haccel, lpaccelNew,
cAccelerators);
}
// Find the accelerator that the user modified
// and change its flags and virtual-key code
// as appropriate.
for (i = 0; i < (UINT) cAccelerators; i++)
{
if (lpaccelNew[i].cmd == (WORD) idItem)
{
lpaccelNew[i].fVirt = fAccelFlags;
lpaccelNew[i].key = wVKCode;
}
}
// Create the new accelerator table, and
// destroy the old one.
DestroyAcceleratorTable(haccelOld);
haccel = CreateAcceleratorTable(lpaccelNew,
cAccelerators);
// Destroy the dialog box.
EndDialog(hwndDlg, TRUE);
return 0;
case IDCANCEL:
EndDialog(hwndDlg, TRUE);
return TRUE;
default:
break;
}
default:
break;
}
return FALSE;
}