键盘对于每个操作电脑的人员来说是最熟悉不过的了。键盘上的按键可分为两类: 按下后会在电脑的输入窗口上出现对应字符的按键,如字母键和数字键等,我们称之为字符键;按下后虽然看不到字符但会产生控制作用的按键,如回车键、光标键等,我们称之为控制键。
对于vc+程序员来说,键盘上的每个按键都一样,无非是不同按键产生的键盘扫描码不同。在不同的操作系统下,键盘扫描码常常被转换为不同的编码以方便应用程序调用,比如在Windows系统下的ASCII码,在Windows系统下的虚拟键盘码等等。
有时我们希望能以程序的方式模拟键盘按键,以达到自动输入文字或者控制操作的目的。在DOS系统下通常使用中断调用,产生键盘的扫描码的方法来实现。在Windows 系统下,由于Windows本身的一些限制和特点,一般不直接使用中断调用。
了解一点Windows编程的朋友应该知道, Windows系统是通过消息的传递(或称事件的发生)来控制各个应用程序的执行和数据通信的。例如:应用程序打开和关闭会产生相应的窗口消息,鼠标的移动、点击动作会产生相应的鼠标消息,同样键盘的按下、弹起也会产生相应的键盘消息。那么如果用程序产生键盘消息,也就达到了模拟键盘按键的目的。
有了这样的思路,我们现在就来实验一下。
首先要知道在Windows系统中与键盘按键相关的消息有:WM_KEYDOWN、WM_KEYUP、 WM_SYSKEYDOWN、WM_SYSKEYUP、WM_CHAR等。其中,WM_KEYDOWN为键按下,WM_KEYUP为键弹起,WM_SYSKEYDOWN为系统键按下,WM_SYSKEYUP为系统键弹起,WM_CHAR为按键对应的字符。
要模拟键盘产生键盘消息,我们就发送一条键盘消息给指定窗口。比如要模拟一个字母键“A”,可以这样:PostMessage(hWnd, WM_CHAR, ‘A‘, 0); 模拟按一个回车:PostMessage(hWnd, WM_KEYDOWN, VK_RETURN, 0)。这里的关键问题是要确定窗口句柄(hWnd),使用GetFocus()函数可以得到键盘光标所在窗口句柄,但该函数只能得到当前进程内的窗口句柄。
如果要得到其他应用程序的键盘光标所在窗口句柄,需要调用 AttachThreadInput()函数。该函数的作用就是将其他窗口线程的输入附加到本窗口线程的输入操作中,这样就可以调用GetFocus()函数得到其他窗口的句柄了。
AttachThreadInput()函数的原形如下:
BOOL AttachThreadInput(
DWORD idAttach, // 需要附加的线程ID
DWORD idAttachTo, // 附加到的线程ID
BOOL fAttach // true 附加 false 取消
);
函数使用的过程大致如下:
HWND hWnd;
hWnd = GetForegroundWindow(); // 得到当前窗口
if (hWnd == Form1->Handle) return; // 排除程序本身的窗口
DWORD FormThreadID = GetCurrentThreadId(); // 本程序的线程ID
// 当前窗口的线程ID
DWORD CWndThreadID = GetWindowThreadProcessId(hWnd, NULL);
// 附加输入线程
AttachThreadInput(CWndThreadID, FormThreadID, true);
// 得到当前键盘光标所在的窗口
hWnd = GetFocus();
// 取消附加的输入线程
AttachThreadInput(CWndThreadID, FormThreadID, false);
hWnd就是当前键盘光标所在的窗口句柄。另外,经过测试发现,在Windows2000系统下发送字符消息(WM_CHAR)时,如果字符是一个汉字,则该字符对应的虚拟键盘码高位不为0,这样得到的字符就不正确。解决办法是做一个“与”运算: ch & 0xFF就可以了。
下面又到了给出例程的时间了。例程“刷刷刷”能够在键盘光标所在的文本输入框中自动输入文字(中文、英文、数字),程序使用C++ Builder 5开发。首先运行C++ Builder并新建工程。接着,将窗体Form1的边框样式(BorderStyle)改为对话框(bsDialog),并放置相应控件如图所示,其中SS_Text是一个用于输入文本的TComboBox控件,当然,你可以在设计阶段预先向控件中输入一些常用文本,以便程序运行后可以直接选用; txtTimes和txtDelay为TEdit控件,分别用于控制发送文本的次数和间隔时间;chkAutoWrap和chkAutoNumber为TCheckBox控件,决定是否在每一行发送文本后面自动回车或自动加记数编号; 以上控件包含在Panel1(TPanel控件)中; Timer1用于控制循环发送和时间间隔。
下面是程序清单:
- //--------------------------------------------
- #include
- #pragma hdrstop
- #include "Unit1.h"
- //--------------------------------------------
- #pragma package(smart_init)
- #pragma resource "*.dfm"
- int nTotalTimes, // 发送本文的总次数
- nTimes; // 已经发送的次数
- TForm1 *Form1;
- //--------------------------------------------
- __fastcall TForm1::TForm1(TComponent* Owner)
- : TForm(Owner)
- {
- }
- //--------------------------------------------
- void __fastcall TForm1::btnStartClick(TObject *Sender) // 开始刷屏
- { if (SS_Text->Text.IsEmpty())
- {
- // 文本不能为空
- ShowMessage("请输入刷刷文本!");
- SS_Text->SetFocus();
- return;
- }
- __try
- {
- // Timer1->Interval取值为n秒(最小为50毫秒)
- int Interval = StrToInt(txtDelay->Text);
- Timer1->Interval = (Interval > 0) ? Interval * 1000 : 50;
- // nTotalTimes取值为n次(最小为0次)
- nTotalTimes = StrToInt(txtTimes->Text);
- if (nTotalTimes < 0)
- nTotalTimes = 0;
- nTimes = 0;
- Timer1->Enabled = true;
- }
- __except(EXCEPTION_EXECUTE_HANDLER)
- {
- ShowMessage("请输入数值类型数据!");
- return;
- }
- btnStart->Enabled = false;
- btnStop->Enabled = true;
- Panel1->Enabled = false;
- Application->Minimize(); // 最小化刷刷窗口
- }
- //--------------------------------------------
- void __fastcall TForm1::btnStopClick(TObject *Sender)// 停止刷屏
- {
- Timer1->Enabled = false;
- btnStart->Enabled = true;
- btnStop->Enabled = false;
- Panel1->Enabled = true;
- }
- //--------------------------------------------
- void __fastcall TForm1::Timer1Timer(TObject *Sender)
- {
- // 现刷屏nTimes次,到nTotalTimes次后完成。
- if (nTimes == nTotalTimes)
- {
- btnStopClick(Sender);
- return;
- }
- HWND hWnd;
- hWnd = GetForegroundWindow(); // 得到当前窗口
- if (hWnd == Form1->Handle) return; // 不需要程序本身的窗口
- DWORD FormThreadID = GetCurrentThreadId();
- DWORD CWndThreadID = GetWindowThreadProcessId(hWnd, NULL);
- // 附加输入线程
- AttachThreadInput(CWndThreadID, FormThreadID, true);
- hWnd = GetFocus(); // 得到当前键盘光标所在的窗口
- AttachThreadInput(CWndThreadID, FormThreadID, false); // 取消
- if (hWnd == NULL) return;
- nTimes++;
- for (int i = 1; i <= SS_Text->Text.Length(); i++)
- { // 模拟键盘按键输入文本
- PostMessage(hWnd, WM_CHAR, (WPARAM)(SS_Text->Text[i] & 0xFF), 0);
- }
- if (chkAutoNumber->Checked)
- { // 自动编号
- AnsiString Lines = IntToStr(nTimes);
- for (int j = 1; j <= Lines.Length(); j++)
- PostMessage(hWnd, WM_CHAR, (WPARAM)(Lines[j]), 0);
- }
- if (chkAutoWrap->Checked) // 自动回车
- PostMessage(hWnd, WM_KEYDOWN, VK_RETURN, 0);
- }
- //-----------
- end