键盘消息分为击键消息和字符消息。
具体的细节可以参看微软的官方文档说明:键盘输入概述
一、击键消息:指的是当前按下的是哪一个键。一般有以下4个击键消息WM_KEYDOWN、WM_SYSKEYDOWN、WM_KEYUP、WM_SYSKEYUP。其中又分为系统击键消息和非系统击键消息。但是我们常用的是非系统击键消息。当我们按下键盘上的一个键时就会产生一个WM_KEYDOWN消息,但是我们不知道当前按下的是哪一个按键,我们可以通过该消息的wParam参数知道当前按下的是哪一个按键。wParam参数所附带的信息一般是以VK_开头的键码值。
2.键码值:存储在击键消息WM_KEYDOWN、WM_SYSKEYDOWN、WM_KEYUP、WM_SYSKEYUP的wParam参数中,大多数都是以VK_开头的比如:VK_F1…
#define VK_F1 0x70
#define VK_F2 0x71
#define VK_F3 0x72
他们都定义在WINUSER.h头文件中。消息的另一个参数lParam包含了该消息的其他信息。此信息包括 重复计数、 扫描代码、 扩展键标志、 上下文代码、 以前的键状态标志和 转换状态标志。
比如我向某个窗口发送一个回车键消息,使用如下代码,表示我按下的是回车键。
SendMessage(hwnd,WM_KEYDOWN,VK_RETURN,0);
还有一个比较重要的就是转移状态了,它可以判断是否按下了转义键(Shift键,Ctrl键,Alt键或者切换键Caps Lock、Num Lock键、Scroll Lock键)。可以使用GetKeyState()函数检测这些键是否被按下,GetKeyState()函数的原型如下:
WINUSERAPI
SHORT
WINAPI
GetKeyState(
_In_ int nVirtKey);
比如可以这样使用该函数:
if (GetKeyState(VK_MENU) & 0x8000))
{
// ALT key is down.
}
说明:值0x8000包含用于测试键当前是否按下的位标志。
更多的说明,可以参考微软官方文档的描述键盘输入
虚拟键的键码值在WINUSER.h中定义,以下是一些常用的:
/*
* Virtual Keys, Standard Set
*/
#define VK_LBUTTON 0x01
#define VK_RBUTTON 0x02
#define VK_CANCEL 0x03
#define VK_MBUTTON 0x04 /* NOT contiguous with L & RBUTTON */
#if(_WIN32_WINNT >= 0x0500)
#define VK_XBUTTON1 0x05 /* NOT contiguous with L & RBUTTON */
#define VK_XBUTTON2 0x06 /* NOT contiguous with L & RBUTTON */
#endif /* _WIN32_WINNT >= 0x0500 */
/*
* 0x07 : reserved
*/
#define VK_BACK 0x08
#define VK_TAB 0x09
/*
* 0x0A - 0x0B : reserved
*/
#define VK_CLEAR 0x0C
#define VK_RETURN 0x0D
/*
* 0x0E - 0x0F : unassigned
*/
#define VK_SHIFT 0x10
#define VK_CONTROL 0x11
#define VK_MENU 0x12
#define VK_PAUSE 0x13
#define VK_CAPITAL 0x14
#define VK_KANA 0x15
#define VK_HANGEUL 0x15 /* old name - should be here for compatibility */
#define VK_HANGUL 0x15
/*
* 0x16 : unassigned
*/
#define VK_JUNJA 0x17
#define VK_FINAL 0x18
#define VK_HANJA 0x19
#define VK_KANJI 0x19
/*
* 0x1A : unassigned
*/
#define VK_ESCAPE 0x1B
#define VK_CONVERT 0x1C
#define VK_NONCONVERT 0x1D
#define VK_ACCEPT 0x1E
#define VK_MODECHANGE 0x1F
#define VK_SPACE 0x20
#define VK_PRIOR 0x21
#define VK_NEXT 0x22
#define VK_END 0x23
#define VK_HOME 0x24
#define VK_LEFT 0x25
#define VK_UP 0x26
#define VK_RIGHT 0x27
#define VK_DOWN 0x28
#define VK_SELECT 0x29
#define VK_PRINT 0x2A
#define VK_EXECUTE 0x2B
#define VK_SNAPSHOT 0x2C
#define VK_INSERT 0x2D
#define VK_DELETE 0x2E
#define VK_HELP 0x2F
/*
* VK_0 - VK_9 are the same as ASCII '0' - '9' (0x30 - 0x39)
* 0x3A - 0x40 : unassigned
* VK_A - VK_Z are the same as ASCII 'A' - 'Z' (0x41 - 0x5A)
*/
#define VK_LWIN 0x5B
#define VK_RWIN 0x5C
#define VK_APPS 0x5D
/*
* 0x5E : reserved
*/
#define VK_SLEEP 0x5F
#define VK_NUMPAD0 0x60
#define VK_NUMPAD1 0x61
#define VK_NUMPAD2 0x62
#define VK_NUMPAD3 0x63
#define VK_NUMPAD4 0x64
#define VK_NUMPAD5 0x65
#define VK_NUMPAD6 0x66
#define VK_NUMPAD7 0x67
#define VK_NUMPAD8 0x68
#define VK_NUMPAD9 0x69
#define VK_MULTIPLY 0x6A
#define VK_ADD 0x6B
#define VK_SEPARATOR 0x6C
#define VK_SUBTRACT 0x6D
#define VK_DECIMAL 0x6E
#define VK_DIVIDE 0x6F
#define VK_F1 0x70
#define VK_F2 0x71
#define VK_F3 0x72
#define VK_F4 0x73
#define VK_F5 0x74
#define VK_F6 0x75
#define VK_F7 0x76
#define VK_F8 0x77
#define VK_F9 0x78
#define VK_F10 0x79
#define VK_F11 0x7A
#define VK_F12 0x7B
#define VK_F13 0x7C
#define VK_F14 0x7D
#define VK_F15 0x7E
#define VK_F16 0x7F
#define VK_F17 0x80
#define VK_F18 0x81
#define VK_F19 0x82
#define VK_F20 0x83
#define VK_F21 0x84
#define VK_F22 0x85
#define VK_F23 0x86
#define VK_F24 0x87
更多的定义可以直接参考WINUSER.h。
二、字符消息:击键消息提供了大量的信息但是不包括字符消息。如果需要获取字符消息,则需要在线程的消息循环中调用TranslateMessage
如下所示:
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
MSG msg;
if (!InitApplication(hinstance))
return FALSE;
if (!InitInstance(hinstance, nCmdShow))
return FALSE;
BOOL fGotMessage;
while ((fGotMessage = GetMessage(&msg, (HWND)NULL, 0, 0)) != 0 && fGotMessage != -1)
{
// 将虚拟密钥消息转换为字符消息。
// 字符消息将发布到调用线程的消息队列,下次线程调用 GetMessage 或 PeekMessage 函数时要读取。
TranslateMessage(&msg);
// 将消息调度到窗口过程。它通常用于调度 GetMessage 函数检索的消息。
DispatchMessage(&msg);
}
return msg.wParam;
UNREFERENCED_PARAMETER(lpCmdLine);
}
该函数的作用是把击键消息转化为字符消息。
字符消息也分为以下几类:
WM_CHAR、WM_DEADCHAR、WM_SYSCHAR、WM_SYSDEADCHAR、WM_UNICHAR。TranslateMessage()函数会将WM_KEYDOWN或者WM_SYSDOWN消息翻译成WM_CHAR或者WM_SYSCHAR消息。但是处理键盘输入的应用程序通常会忽略WM_CHAR、WM_UNICHAR消息以外的消息。WM_CHAR使用(16 位 Unicode 转换格式) 或 ANSI 字符集,而WM_UNICHAR使用UTF-32 (32 位 Unicode 转换格式) 。这与RegisterClass函数使用的版本有关。
2.典型的窗口过程函数会忽略除WM_CHAR以外的所有字符消息。当用户按下以下键时TranslateMessage()函数生成WM_CHAR消息。
①任意字符键
②Backspace(退格键)
③Enter(回车键)
④ESC键
⑤Shift+Enter(换行符)
⑥Tab键
当生成WM_CHAR消息时,通过wParam参数我们可以知道字符代码是什么。
以下是对于WM_CHAR消息的典型处理:
case WM_CHAR:
switch (wParam)
{
case 0x08:
// Process a backspace.
break;
case 0x0A:
// Process a linefeed.
break;
case 0x1B:
// Process an escape.
break;
case 0x09:
// Process a tab.
break;
case 0x0D:
// Process a carriage return.
break;
default:
// Process displayable characters.
break;
}
下面给出具体的实例:
1.新建控制台应用程序:
我新建的工程名字是UseKeyboardInput.代码如下所示:
#include <iostream>
#include <afxwin.h>
#include <windows.h>
#include <strsafe.h>
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int);
BOOL InitApplication(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);
HINSTANCE hinst;
LRESULT CALLBACK WndProc(HWND hWnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
HDC hDC;
CRect rcClient;
POINT ptClientUL;
POINT ptClientLR;
static POINTS ptBegin;
static POINTS ptEnd;
static POINTS ptPrevEnd;
static BOOL bPrevLine = FALSE;
switch (message)
{
case WM_KEYDOWN:
{
switch (wParam)
{
case VK_LEFT:
MessageBox(NULL, _T("左方向键按下"), _T("提示"), MB_OK);
break;
case VK_RIGHT:
MessageBox(NULL, _T("右方向键按下"), _T("提示"), MB_OK);
break;
case VK_UP:
MessageBox(NULL, _T("Up 键按下"), _T("提示"), MB_OK);
break;
case VK_DOWN:
MessageBox(NULL, _T("Down 键按下"), _T("提示"), MB_OK);
break;
case VK_HOME:
MessageBox(NULL, _T("Home 键按下"), _T("提示"), MB_OK);
break;
case VK_END:
MessageBox(NULL, _T("End 键按下"), _T("提示"), MB_OK);
break;
case VK_INSERT:
MessageBox(NULL, _T("Insert 键按下"), _T("提示"), MB_OK);
break;
case VK_DELETE:
MessageBox(NULL, _T("Delete 键按下"), _T("提示"), MB_OK);
break;
case VK_F2:
MessageBox(NULL, _T("F2 键按下"), _T("提示"), MB_OK);
break;
}
}
case WM_CHAR:
{
switch (wParam)
{
case 0x08:
MessageBox(NULL, _T("退格键按下"), _T("提示"), MB_OK);
break;
case 0x0A:
MessageBox(NULL, _T("换行符(SHIFT + ENTER)被按下"), _T("提示"), MB_OK);
break;
case 0x1B:
MessageBox(NULL, _T("ESC键按下"), _T("提示"), MB_OK);
break;
case 0x09:
MessageBox(NULL, _T("Tab键按下"), _T("提示"), MB_OK);
break;
case 0x0D:
MessageBox(NULL, _T("ENTER键(回车键)按下"), _T("提示"), MB_OK);
break;
default:
break;
}
break;
}
case WM_SETFOCUS:
{
HWND hwnd; // window handle
int x= 50 ; // horizontal coordinate of cursor
int y= 50 ; // vertical coordinate of cursor
int nWidth = 10; // width of cursor
int nHeight =10 ; // height of cursor
char* lpszChar; // pointer to character
// Create a solid black caret.
CreateCaret(hWnd, (HBITMAP)NULL, nWidth, nHeight);
// Adjust the caret position, in client coordinates.
SetCaretPos(x, y);
// Display the caret.
ShowCaret(hWnd);
break;
}
case WM_KILLFOCUS:
{
int x = 50; // horizontal coordinate of cursor
int y = 50; // vertical coordinate of cursor
wchar_t* lpszChar = NULL ;
// Hide the caret.
HideCaret(NULL);
// Draw the character on the screen.
HDC hDC;
hDC = GetDC(hWnd);
SelectObject(hDC,
GetStockObject(SYSTEM_FIXED_FONT));
TextOut(hDC, x, y,lpszChar, 1);
ReleaseDC(hWnd, hDC);
// Display the caret.
ShowCaret(hWnd);
break;
}
default:
return DefWindowProc(hWnd, message, wParam, lParam);
break;
}
return 0;
}
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
MSG msg;
if (!InitApplication(hinstance))
return FALSE;
if (!InitInstance(hinstance, nCmdShow))
return FALSE;
BOOL fGotMessage;
while ((fGotMessage = GetMessage(&msg, (HWND)NULL, 0, 0)) != 0 && fGotMessage != -1)
{
// 将虚拟密钥消息转换为字符消息。
// 字符消息将发布到调用线程的消息队列,下次线程调用 GetMessage 或 PeekMessage 函数时要读取。
TranslateMessage(&msg);
// 将消息调度到窗口过程。它通常用于调度 GetMessage 函数检索的消息。
DispatchMessage(&msg);
}
return msg.wParam;
UNREFERENCED_PARAMETER(lpCmdLine);
}
BOOL InitApplication(HINSTANCE hinstance)
{
WNDCLASSEX wcx;
// Fill in the window class structure with parameters
// that describe the main window.
wcx.cbSize = sizeof(wcx); // size of structure
wcx.style = CS_HREDRAW |
CS_VREDRAW; // redraw if size changes
wcx.lpfnWndProc = (WNDPROC)WndProc; // points to window procedure
wcx.cbClsExtra = 0; // no extra class memory
wcx.cbWndExtra = 0; // no extra window memory
wcx.hInstance = hinstance; // handle to instance
wcx.hIcon = LoadIcon(NULL,
IDI_APPLICATION); // predefined app. icon
wcx.hCursor = LoadCursor(NULL,
IDC_ARROW); // predefined arrow
wcx.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); // white background brush
wcx.lpszMenuName = (LPCWSTR)"MainMenu"; // name of menu resource
wcx.lpszClassName = (LPCWSTR)"MainWClass"; // name of window class
wcx.hIconSm = (HICON)LoadImage(hinstance, // small class icon
MAKEINTRESOURCE(5),
IMAGE_ICON,
GetSystemMetrics(SM_CXSMICON),
GetSystemMetrics(SM_CYSMICON),
LR_DEFAULTCOLOR);
// Register the window class.
return RegisterClassEx(&wcx);
}
BOOL InitInstance(HINSTANCE hinstance, int nCmdShow)
{
HWND hwnd;
// Save the application-instance handle.
hinst = hinstance;
// Create the main window.
hwnd = CreateWindow(
(LPCWSTR)"MainWClass", // name of window class
(LPCWSTR)"Sample", // title-bar string
WS_OVERLAPPEDWINDOW, // top-level window
CW_USEDEFAULT, // default horizontal position
CW_USEDEFAULT, // default vertical position
CW_USEDEFAULT, // default width
CW_USEDEFAULT, // default height
(HWND)NULL, // no owner window
(HMENU)NULL, // use class menu
hinstance, // handle to application instance
(LPVOID)NULL); // no window-creation data
if (!hwnd)
return FALSE;
// Show the window and send a WM_PAINT message to the window
// procedure.
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
return TRUE;
}
该章节的详细细节,大家可以参考微软的官方文档键盘和鼠标输入
最终效果如下图所示:
欢迎大家一起交流。
参考:Windows程序设计–击键消息和字符消息
微软官方文档:键盘和鼠标输入