想让程序模拟一波,用户击键消息,应该怎么做呢?
有两种方法可以很方便地实现模拟击键消息。一是使用
SendMessage / PostMessage函数发送WM_KEYDOWN等消息。SendMessage和PostMessage函数的用法相同,不同的是PostMessage函数将消息发送到线程的消息队列并立即返回,不会等待窗口过程把消息处理完毕。我们已经用过SendMessage函数,接下来练习一下PostMessage函数。
二是使用keybd_event模拟击键事件。keybd_event函数的原型定义如下∶
voID WINAPl keybd_event(
_In_ BYTE bVk, //虚拟键码
_In_ BYTE bScan, //按键的扫描码,可以设置为0
_In_ DWORD dwFlags,//标志位
_In_ ULONG_PTR dwExtraInfo);//与击键相关的附加的32位值,可以设置为0
keybd_event函数合成一次击键事件,并不关心由谁来处理它。系统捕捉到击键事件后会转换为键盘消息WM_KEYDOWN或WM_KEYUP分发给当前系统中拥有键盘焦点的应用程序。
比如哦说,我的笔记本只有84个键盘,没有Home和End按键。我想使用数字按键1代替Home按键的效果,数字按键2代替End按键的效果。打开Chapter4\SystemMetrics4项目,添加对WM_CHAR消息的处理∶
#include <Windows.h>
#include <tchar.h>
#include "Metrics.h"
const int NUMLINES = sizeof(METRICS) / sizeof(METRICS[0]);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wndclass; // RegisterClassEx函数用的WNDCLASSEX结构
TCHAR szClassName[] = TEXT("MyWindow"); // RegisterClassEx函数注册的窗口类的名称
TCHAR szAppName[] = TEXT("GetSystemMetrics"); // 窗口标题
HWND hwnd; // CreateWindowEx函数创建的窗口的句柄
MSG msg; // 消息循环所用的消息结构体
wndclass.cbSize = sizeof(WNDCLASSEX);
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WindowProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = szClassName;
wndclass.hIconSm = NULL;
RegisterClassEx(&wndclass);
hwnd = CreateWindowEx(0, szClassName, szAppName, WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&msg, NULL, 0, 0) != 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
TEXTMETRIC tm;
SCROLLINFO si;
HFONT hFont, hFontOld;
static int s_iCol1, s_iCol2, s_iCol3, s_iHeight;// 第1~3列字符串的最大宽度,字符串高度
static int s_cxClient, s_cyClient; // 客户区宽度、高度
static int s_cxChar; // 平均字符宽度,用于水平滚动条滚动单位
int iVertPos, iHorzPos; // 垂直、水平滚动条的当前位置
SIZE size = { 0 };
int x, y;
TCHAR szBuf[10];
int nPaintBeg, nPaintEnd; // 无效区域
RECT rect;
switch (uMsg)
{
case WM_CHAR:
switch (wParam)
{
case '1':
PostMessage(hwnd, WM_KEYDOWN, VK_HOME, 0);
break;
case '2':
keybd_event(VK_END,0,0,0);
keybd_event(VK_END,0,KEYEVENTF_KEYUP,0);
default:
break;
}
case WM_CREATE:
hdc = GetDC(hwnd);
hFont = CreateFont(12, 0, 0, 0, 0, 0, 0, 0, GB2312_CHARSET, 0, 0, 0, 0, TEXT("宋体"));
hFontOld = (HFONT)SelectObject(hdc, hFont);
for (int i = 0; i < NUMLINES; i++)
{
GetTextExtentPoint32(hdc, METRICS[i].m_pLabel, _tcslen(METRICS[i].m_pLabel), &size);
if (size.cx > s_iCol1)
s_iCol1 = size.cx;
GetTextExtentPoint32(hdc, METRICS[i].m_pDesc, _tcslen(METRICS[i].m_pDesc), &size);
if (size.cx > s_iCol2)
s_iCol2 = size.cx;
GetTextExtentPoint32(hdc, szBuf,
wsprintf(szBuf, TEXT("%d"), GetSystemMetrics(METRICS[i].m_nIndex)), &size);
if (size.cx > s_iCol3)
s_iCol3 = size.cx;
}
s_iHeight = size.cy + 2;
GetTextMetrics(hdc, &tm);
s_cxChar = tm.tmAveCharWidth;
//让窗口大小和渲染文件内容匹配
GetClientRect(hwnd, &rect);
rect.right = s_iCol1 + s_iCol2 + s_iCol3 + GetSystemMetrics(SM_CXVSCROLL);
AdjustWindowRectEx(&rect, GetWindowLongPtr(hwnd, GWL_STYLE),
GetMenu(hwnd) != NULL, GetWindowLongPtr(hwnd, GWL_EXSTYLE));
MoveWindow(hwnd, 0, 0, rect.right - rect.left, rect.bottom - rect.top, TRUE);
SetWindowPos(hwnd, NULL, 0, 0, rect.right - rect.left, rect.bottom - rect.top,
SWP_NOZORDER | SWP_NOMOVE);
SelectObject(hdc, hFontOld);
DeleteObject(hFont);
ReleaseDC(hwnd, hdc);
return 0;
case WM_SIZE:
// 客户区宽度、高度
s_cxClient = LOWORD(lParam);
s_cyClient = HIWORD(lParam);
// 设置垂直滚动条的范围和页面大小
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_RANGE | SIF_PAGE;
si.nMin = 0;
si.nMax = NUMLINES - 1;
si.nPage = s_cyClient / s_iHeight;
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
// 设置水平滚动条的范围和页面大小
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_RANGE | SIF_PAGE;
si.nMin = 0;
si.nMax = (s_iCol1 + s_iCol2 + s_iCol3) / s_cxChar - 1;
si.nPage = s_cxClient / s_cxChar;
SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);
return 0;
case WM_VSCROLL:
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_ALL;
GetScrollInfo(hwnd, SB_VERT, &si);
iVertPos = si.nPos;
switch (LOWORD(wParam))
{
case SB_LINEUP:
si.nPos -= 1;
break;
case SB_LINEDOWN:
si.nPos += 1;
break;
case SB_PAGEUP:
si.nPos -= si.nPage;
break;
case SB_PAGEDOWN:
si.nPos += si.nPage;
break;
case SB_THUMBTRACK:
si.nPos = si.nTrackPos;
break;
case SB_TOP:
si.nPos = 0;
break;
case SB_BOTTOM:
si.nPos = NUMLINES - 1;
break;
}
// 设置位置,然后获取位置,如果si.nPos越界,Windows不会设置
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_POS;
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
GetScrollInfo(hwnd, SB_VERT, &si);
// 如果Windows更新了滚动条位置,我们更新客户区
if (iVertPos != si.nPos)
{
ScrollWindow(hwnd, 0, s_iHeight * (iVertPos - si.nPos), NULL, NULL);
UpdateWindow(hwnd);
}
return 0;
case WM_HSCROLL:
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_ALL;
GetScrollInfo(hwnd, SB_HORZ, &si);
iHorzPos = si.nPos;
switch (LOWORD(wParam))
{
case SB_LINELEFT:
si.nPos -= 1;
break;
case SB_LINERIGHT:
si.nPos += 1;
break;
case SB_PAGELEFT:
si.nPos -= si.nPage;
break;
case SB_PAGERIGHT:
si.nPos += si.nPage;
break;
case SB_THUMBTRACK:
si.nPos = si.nTrackPos;
break;
}
// 设置位置,然后获取位置,如果si.nPos越界,Windows不会设置
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_POS;
SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);
GetScrollInfo(hwnd, SB_HORZ, &si);
// 如果Windows更新了滚动条位置,我们更新客户区
if (iHorzPos != si.nPos)
{
ScrollWindow(hwnd, s_cxChar * (iHorzPos - si.nPos), 0, NULL, NULL);
UpdateWindow(hwnd);
}
return 0;
case WM_KEYDOWN:
switch (wParam)
{
case VK_UP: // 向上箭头键
SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0);
break;
case VK_DOWN: // 向下箭头键
SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0);
break;
case VK_PRIOR: // PageUp键
SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0);
break;
case VK_NEXT: // PageDown键
SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
break;
case VK_HOME: // Home键(或者Fn + PageUp键)
SendMessage(hwnd, WM_VSCROLL, SB_TOP, 0);
break;
case VK_END: // End键(或者Fn + PageDown键)
SendMessage(hwnd, WM_VSCROLL, SB_BOTTOM, 0);
break;
case VK_LEFT: // 左箭头键
SendMessage(hwnd, WM_HSCROLL, SB_LINELEFT, 0);
break;
case VK_RIGHT: // 右箭头键
SendMessage(hwnd, WM_HSCROLL, SB_LINERIGHT, 0);
break;
}
return 0;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
// 获取垂直滚动条、水平滚动条位置
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_POS | SIF_PAGE;
GetScrollInfo(hwnd, SB_VERT, &si);
iVertPos = si.nPos;
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_POS;
GetScrollInfo(hwnd, SB_HORZ, &si);
iHorzPos = si.nPos;
SetBkMode(hdc, TRANSPARENT);
hFont = CreateFont(12, 0, 0, 0, 0, 0, 0, 0, GB2312_CHARSET, 0, 0, 0, 0, TEXT("宋体"));
hFontOld = (HFONT)SelectObject(hdc, hFont);
// 获取无效区域
nPaintBeg = max(0, iVertPos + ps.rcPaint.top / s_iHeight);
nPaintEnd = min(NUMLINES - 1, iVertPos + ps.rcPaint.bottom / s_iHeight);
for (int i = nPaintBeg; i <= nPaintEnd; i++)
{
x = s_cxChar * (-iHorzPos);
y = s_iHeight * (i - iVertPos);
TextOut(hdc, x, y, METRICS[i].m_pLabel, _tcslen(METRICS[i].m_pLabel));
TextOut(hdc, x + s_iCol1, y, METRICS[i].m_pDesc, _tcslen(METRICS[i].m_pDesc));
TextOut(hdc, x + s_iCol1 + s_iCol2, y, szBuf,
wsprintf(szBuf, TEXT("%d"), GetSystemMetrics(METRICS[i].m_nIndex)));
}
SelectObject(hdc, hFontOld);
DeleteObject(hFont);
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
使用keybd_event模拟击键消息,需要注意的是对于每一个按键必须成对使用,第一次表示按下,第二次带有KEYEVENTF_KEYUP标志表示释放按键。例如,如果需要模拟Win +R打开系统的"运行"程序,可以按如下方式使用keybd_event函数︰
keybd_event(VK_LWIN,0,0,0);
keybd_event('R', 0, 0, 0);
keybd_event('R',0, KEYEVENTF_KEYUP,0);
keybd_event(VK_LWIN,0, KEYEVENTF_KEYUP,0);
有时候,在按键按下和抬起之间可能需要暂停一会,例如模拟Alt + Tab组合键︰
keybd_event(VK_MENU,0,0,0);
keybd_event(VK_TAB,0,0,0);
Sleep(3000);
keybd_event(VK_TAB,0,KEYEVENTF_KEYUP,0);
keybd_event(VK_MENU,0, KEYEVENTF_KEYUP,0);
Sleep函数可以暂停程序的执行︰
voID WINAPI Sleep(_In_DWORD dwMilliseconds); //暂停执行的时间间隔,以毫秒为单位
微软建议使用SendInput函数代替keybd_event函数。SendInput函数既可以模拟键盘输入也可以模拟鼠标输入
UINT WINAPl SendInput(
_In_ UINT nInputs, //plnputs数组中的结构数
_In_ LPINPUT plnputs,// INPUT结构的数组,每个结构表示一个键盘或鼠标输入事件
_In_ int cbSize); // INPUT结构的大小,以字节为单位
参数pInputs是一个INPUT结构的数组,每个结构表示一个键盘或鼠标输入事件。INPUT结构在WinUser.h头文件中定义如下∶
typedef struct tagINPUT {
DWORD type; //输入事件的类型
union
{
MOUSEINPUT mi; // MOUSEINPUT结构
KEYBDINPUT ki; // KEYBDINPUT结构
HARDWAREINPUT hi; // HARDWAREINPUT结构
};
}INPUT,*PINPUT,FAR*LPINPUT;
- 字段type指定输入事件的类型,该字段可以是下表所示的值之一。
宏常量 | 含义 |
---|---|
INPUT_MOUSE | 该事件是鼠标事件,使用mi结构 |
INPUT_KEYBOARD | 该事件是键盘事件,使用ki结构 |
INPUT_HARDWARE | 该事件是硬件事件,使用hi结构 |
- mi ki hi都是结构体,分别指定鼠标、键盘和硬件事件的具体信息。硬件事件指的是键盘或鼠标以外的输入设备生成的消息。这几个结构比较简单,如果需要使用SendInput函数请自行查阅MSDN