相关函数预览:
SHORT WINAPI GetKeyState(_In_intnVirtKey);
SHORT WINAPI GetAsyncKeyState( _In_int vKey);
LRESULT WINAPI SendMessageW( _In_ HWND hWnd, _In_ UINT Msg, _Pre_maybenull_ _Post_valid_ WPARAM wParam, _Pre_maybenull_ _Post_valid_LPARAM lParam);
#define SendMessage SendMessageW
BOOL WINAPI CreateCaret(_In_HWND hWnd,_In_opt_HBITMAP hBitmap, _In_ int nWidth,_In_int nHeight);
BOOL WINAPI SetCaretPos(_In_int X, _In_int Y);
BOOL WINAPI ShowCaret( _In_opt_HWND hWnd);
BOOL WINAPI HideCaret(_In_opt_HWND hWnd);
BOOL WINAPI DestroyCaret( VOID);
Windows提供了八种消息来表示键盘事件,其中击键消息有4种,字符消息有4种。窗口过程通过捕获WM_SETFOCUS和WM_KILLFOCUS消息来确定自己的窗口是否具有输入点。
Windows击键消息:
键按下 | 键释放 | |
非系统击键 | WM_KEYDOWN | WM_KEYUP |
系统击键 | WM_SYSKEYDOWN | WM_SYSKEYUP |
对于这四类击键消息,wParam是虚拟键代码,lParam则被分为六个字段。虚拟键代码再WINUSER.H头文件中,第一个为VK_LBUTTON,0x41~0x5A为A~Z, 0x30~0x39为主键盘上的0~9.
32位的lParam的15~00位为16位的重复计数,即当程序无法及时处理某一个键被持续按下的情况时,会将重复次数保存在这16位。
23~16位为8位的OEM扫描码
24位为扩展键标记,当击键来自于IBM加强型键盘的附加键时,该位为1。
29位为内容代码,如果击键的同时按下了alt键,则该位为1,收到WM_SYSKEYUP和WM_SYSKEYDOWN消息时,此位始终为1,收到WM_KEYUP和WM_KEYDOWN消息时,此位始终为0。有特殊情况,即当窗口最小化时,所有的击键都将产生WM_SYSKEYUP和WM_SYSKEYDOWN消息。
30位为键的先前状态,如果键先前处于释放状态,则该位为0,否则为1.WM_KEYUP和WM_SYSKEYUP的消息此字段总为1,而WM_SYSKEYDOWN和WM_KEYDOWN消息可能为0或1。
31位为转换状态,如果键正在按下,则此位为0,否则为1。
31 | 30 | 29 | 28~25 | 24 | 23~16 | 15~00 |
查询键的状态:
这里有两个函数
iState = GetKeyState(VK_SHIFT);//查询Shift键的状态
iState = GetAsyncKeyState(VK_SHIFT);
两个函数的区别在于前者是重消息队列中得到键盘消息,后者是直接侦测键盘的硬件中断(此处参考点击打开链接)。
如果我们用键盘的上下左右方向键来控制滚动条,可以在WM_KEYDOWN消息中使用SendMessage函数向窗口发送WM_VSCROLL消息或者WM_HSCROLL消息,然后交给滚动条的相关处理方法处理。通常情况下我们只需要处理WM_KEYDOWN消息,因为系统击键往往会由 DefWindowProc(hWnd, message, wParam, lParam);来处理。
Windows还有四种字符消息:
字符 | 死字符 | |
非系统字符 | WM_CHAR | WM_DEADCHAR |
系统字符 | WM_SYSCHAR | WM_SYSDEADCHAR |
字符消息是由TranslateMessage函数捕获到WM_KEYDOW消息后,发送WM_CHAR到消息队列后,由窗口过程捕获并处理。当我们按下A键然后释放时,窗口过程会依次收到WM_KEYDOWN, WM_CHAR, WM_KEYUP三条消息。如果我们一直按下A键然后再释放时,窗口过程会依次收到WM_KEYDOWN,WM_CHAR, WM_KEYDOWN,WM_CHAR, WM_KEYDOWN,WM_CHAR, WM_KEYDOWN,WM_CHAR,WM_KEYUP消息。
许多击键消息我们可以当作字符消息处理,特别是在文本编辑器中,这种方式可以让程序更清晰,这些击键可以由ctrl组合键的方式输入。
击键 | 字符码 | 产生方法 | ANSIC转义码 |
空格 | 0x08 | ctrl+H | \b |
Tab | 0x09 | ctrl+I | \t |
crtl+回车 | 0x0a | ctrl+J | \n |
回车 | 0x0d | ctrl+M | \r |
ESC | 0x1b | ctrl+[ |
插入符号:
插入符号就是我们在输入文本时闪烁的 | 。由以下五个函数来管理插入符号
CreateCatet(hWnd, nullptr, x, y);//创建插入符
SetCaretPos(x, y);//设置插入符的位置
ShowCaret(hWnd);//显示插入符
HideCaret(hWnd);//隐藏插入符
DestroyCaret();//销毁插入符
当键盘布局发生改变时,窗口过程将收到WM_INPUTLANGCHANGE消息,其wParam为字符集代码,即CreateFont函数的第9个参数,所以在该消息中应当及时改变字体的设置,详见参考程序。
注:参考程序是在滚动条基础上做了一些改动,添加了WM_INPUTLANGCHANGE, WM_SETFOCUS, WM_KILLFOCUS, 重写了WM_KEYDOWN, WM_CHAR消息,改动了WM_CREATE, WM_SIZE, WM_PAINT消息的代码。
参考程序:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static int cxChar, cyChar, cxCaps, cxClient, cyClient, iMaxWidth, iMaxLine;
//以下内容为在滚动条基础上增添的内容
static int cxBuffer, cyBuffer, xCaret, yCaret;
static TCHAR* pBuffer = nullptr;
static DWORD dwCharSet = DEFAULT_CHARSET;
//END
HDC hdc;
int i, x, y, iVertpos, iPaintBeg, iPaintEnd;
SCROLLINFO si;
TCHAR szBuffer[10];
TEXTMETRIC tm;
switch (message)
{
case WM_INPUTLANGCHANGE:
dwCharSet = wParam;
break;
case WM_CREATE://较滚动条有所变化
{
hdc = GetDC(hWnd);
SelectObject(hdc, CreateFont(0, 0, 0, 0, 0, 0, 0, 0, dwCharSet, 0, 0, 0, FIXED_PITCH, nullptr));
GetTextMetrics(hdc, &tm);
cxChar = tm.tmAveCharWidth;
cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2;//如果为等宽字体,则低位为0,如果为变宽字体,地位为1,大写字母的宽度为小写字母宽度的3/2
cyChar = tm.tmHeight + tm.tmExternalLeading;
DeleteObject(SelectObject(hdc, GetStockObject(SYSTEM_FONT)));
ReleaseDC(hWnd, hdc);
}
break;
case WM_SIZE:
{
cxClient = LOWORD(lParam);//获取客户区尺寸
cyClient = HIWORD(lParam);
iMaxLine = cyClient / cyChar;
si.cbSize = sizeof(si);
si.fMask = SIF_RANGE | SIF_PAGE;
si.nMin = 0;
si.nMax = iMaxLine;
si.nPage = iMaxLine;
SetScrollInfo(hWnd, SB_VERT, &si, true);
iMaxWidth = cxClient / cxCaps;//每行输出的字数
//以下内容为在滚动条基础上新增内容
cxBuffer = max(1, iMaxWidth);
cyBuffer = max(1, iMaxLine);
if (pBuffer)
free(pBuffer);
pBuffer = (TCHAR*)malloc(cxBuffer * cyBuffer * sizeof(TCHAR));
for(y = 0 ; y < cyBuffer ; ++y)
for (x = 0; x < cxBuffer; ++x)
{
pBuffer[x + y * cxBuffer] = ' ';
}
xCaret = yCaret = 0;
if (hWnd == GetFocus())
{
SetCaretPos(xCaret * cxChar, yCaret * cyChar);
}
InvalidateRect(hWnd, nullptr, true);
//END
}
break;
case WM_SETFOCUS:
CreateCaret(hWnd, nullptr, 1, cyChar);
SetCaretPos(xCaret * cxChar, yCaret * cyChar);
ShowCaret(hWnd);
break;
case WM_KILLFOCUS:
HideCaret(hWnd);
DestroyCaret();
break;
case WM_VSCROLL:
{
si.cbSize = sizeof(si);
si.fMask = SIF_ALL;
GetScrollInfo(hWnd, SB_VERT, &si);
iVertpos = si.nPos;
switch (wParam & 0xffff)
{
case SB_TOP:
si.nPos = si.nMin;
break;
case SB_BOTTOM:
si.nPos = si.nMax;
break;
case SB_LINEUP:
si.nPos -= 1;
break;
case SB_LINEDOWN:
si.nPos += 1;
break;
case SB_PAGEUP:
si.nPos -= si.nPage;
si.nPos = (si.nPos < 0 ? 0 : si.nPos);
break;
case SB_PAGEDOWN:
si.nPos += si.nPage;
si.nPos = (si.nPos > si.nMax ? si.nMax : si.nPos);
break;
case SB_THUMBTRACK:
si.nPos = si.nTrackPos;
break;
default:
break;
}
si.fMask = SIF_POS;
SetScrollInfo(hWnd, SB_VERT, &si, true);
GetScrollInfo(hWnd, SB_VERT, &si);
if (si.nPos != iVertpos)
{
ScrollWindow(hWnd, 0, cyChar * (iVertpos - si.nPos), nullptr, nullptr);
UpdateWindow(hWnd);
}
}
break;
case WM_CHAR:
{
for (i = 0; i < (int)LOWORD(lParam); ++i)
{
switch (wParam)
{
case '\b':
if (xCaret > 0)
{
xCaret--;
SendMessage(hWnd, WM_KEYDOWN, VK_DELETE, 1);
}
break;
case '\t':
do
{
SendMessage(hWnd, WM_CHAR, ' ', 1);
} while (xCaret % 8 != 0);
break;
case '\n':
if (++yCaret == cyBuffer)
yCaret = 0;
break;
case '\r':
xCaret = 0;
if (++yCaret == cyBuffer)
yCaret = 0;
break;
default:
pBuffer[xCaret + yCaret * cxBuffer] = (TCHAR)wParam;
HideCaret(hWnd);
hdc = GetDC(hWnd);
SelectObject(hdc, CreateFont(0, 0, 0, 0, 0, 0, 0, 0, dwCharSet, 0, 0, 0, FIXED_PITCH, nullptr));
TextOut(hdc, xCaret * cxChar, yCaret * cyChar, &pBuffer[xCaret + yCaret * cxBuffer], 1);
DeleteObject(SelectObject(hdc, GetStockObject(SYSTEM_FONT)));
ReleaseDC(hWnd, hdc);
ShowCaret(hWnd);
if (++xCaret == cxBuffer)
{
xCaret = 0;
if (++yCaret == cyBuffer)
yCaret = 0;
}
break;
}
}
}
SetCaretPos(xCaret * cxChar, yCaret * cyChar);
break;
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
// 分析菜单选择:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_KEYDOWN:
{
switch (wParam)
{
case VK_LEFT:
xCaret = max(xCaret - 1, 0);
break;
case VK_RIGHT:
xCaret = min(xCaret + 1, cxBuffer - 1);
break;
case VK_UP:
yCaret = max(yCaret - 1, 0);
break;
case VK_DOWN:
yCaret = min(yCaret + 1, cyBuffer - 1);
break;
case VK_DELETE:
for (x = xCaret; x < cxBuffer - 1; ++x)
pBuffer[x + yCaret * cxBuffer] = pBuffer[x + yCaret * cxBuffer + 1];
pBuffer[(yCaret + 1) * cxBuffer - 1] = ' ';
HideCaret(hWnd);
hdc = GetDC(hWnd);
SelectObject(hdc, CreateFont(0, 0, 0, 0, 0, 0, 0, 0, dwCharSet, 0, 0, 0, FIXED_PITCH, nullptr));
TextOut(hdc, xCaret * cxChar, yCaret * cyChar, &pBuffer[xCaret + yCaret * cxBuffer], cxBuffer - xCaret);
DeleteObject(SelectObject(hdc, GetStockObject(SYSTEM_FONT)));
ReleaseDC(hWnd, hdc);
ShowCaret(hWnd);
break;
}
SetCaretPos(xCaret * cxChar, yCaret * cyChar);
break;
}
case WM_PAINT:
{
PAINTSTRUCT ps;
hdc = BeginPaint(hWnd, &ps);
// TODO: 在此处添加使用 hdc 的任何绘图代码...
si.cbSize = sizeof(si);
si.fMask = SIF_POS;
GetScrollInfo(hWnd, SB_VERT, &si);
iVertpos = si.nPos;
SelectObject(hdc, CreateFont(0, 0, 0, 0, 0, 0, 0, 0, dwCharSet, 0, 0, 0, FIXED_PITCH, nullptr));
for(y = 0 ; y < cyBuffer ; ++y)
TextOut(hdc, 0, y * cyChar, &pBuffer[y * cxBuffer], cxBuffer);
DeleteObject(SelectObject(hdc, GetStockObject(SYSTEM_FONT)));
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
参考文献:《Windows程序设计(第5版 珍藏版)》