第四讲键盘事件.docx
《第四讲键盘事件.docx》由会员分享,可在线阅读,更多相关《第四讲键盘事件.docx(11页珍藏版)》请在冰豆网上搜索。
第四讲键盘事件
第四讲:
键盘事件
【事件驱动】
所谓的事件驱动其实就是当满足什么样的条件下发生另外一件事情。
比如当你按下按钮时就会执行相应的代码一样。
由于控制台程序在同一个时刻只能够执行一个任务,这就造成了在处理事件的效率上太过于低下,通常现代的程序都是面向多任务的,也就是说同一个时间段可以完成好几个功能,那么如何才能够做到这一点呢?
就依赖于事件驱动机制。
理解事件驱动就必须要先理解事件,所谓的事件(Event)其实就是触发某样事情发生的条件,例如键盘按下、单击按钮、双击图标等等。
这些称之为事件。
而当这些事件发生之后,就会触发另外一件事情。
例如当我们点击“XX搜索”按钮时就会跳转到搜索的结果界面一样。
当然,采用事件驱动的直接原因就是最大化利用CPU,由于控制台程序的限制,我们不能够完成多任务的操作(早期的DOS操作系统是单用户单任务的,即同一个时刻只能够完成一个任务,效率很慢)故此我们需要事件驱动这样的机制为我们提高效率,为我们最大化利用CPU资源。
简而言之,事件驱动就是一个用于提高程序效率的机制。
如果上述还没有让你明白过来,你可以看看XX百科上的解释,如下:
所谓事件驱动,简单地说就是你点什么按钮(即产生什么事件),电脑执行什么操作(即调用什么函数).当然事件不仅限于用户的操作.事件驱动的核心自然是事件。
从事件角度说,事件驱动程序的基本结构是由一个事件收集器、一个事件发送器和一个事件处理器组成。
事件收集器专门负责收集所有事件,包括来自用户的(如鼠标、键盘事件等)、来自硬件的(如时钟事件等)和来自软件的(如操作系统、应用程序本身等)。
事件发送器负责将收集器收集到的事件分发到目标对象中。
事件处理器做具体的事件响应工作,它往往要到实现阶段才完全确定,因而需要运用虚函数机制(函数名往往取为类似于HandleMsg的一个名字)。
对于框架的使用者来说,他们唯一能够看到的是事件处理器。
这也是他们所关心的内容。
【程序查询方式】
通常检测事件有两种方法,一种称为程序查询法,一种称之为程序中断法。
由于在控制台中我们无法直接使用系统中断(系统中断只有汇编语言可以直接使用),因此我们无法在高级语言中使用程序中断法检测指令。
故此只能够使用程序查询方式进行事件的检测。
所谓的程序查询方式实际上是利用一个死循环不停地对事件进行查询,如果事件发生则执行相应的代码,当代码执行结束之后继续开始下一轮的查询,由于CPU的计算速度相当快,你不用担心会有延时的问题。
这样一轮一轮查询的方式,我们称之为程序查询方式,也叫轮询法。
程序查询法的流程图如下:
【键盘按下事件】
所谓的键盘事件,是指无论在何时只要键盘按键被按下,即可被程序所截获,并执行相应的功能代码。
故此负责监听键盘事件的API函数会一直处于工作状态,在MFC中我们可以通过死循环进行操作,当然也可以通过我们即将接触到的多线程编程进行操作。
在这里我们介绍如何利用死循环监听键盘。
【DWORD类型】
在MFC中,通常会见到DWORD类型,所谓的DWORD类型就是DoubleWord类型,即双字节类型,MFC中将unsignedlong类型使用typedef关键字重新制定为DWROD类型。
MFC框架中的声明如下:
typedefunsignedlongDWORD;
故此,DWROD类型的本质其实是一个无符号的长整型数。
在MFC中我们可以使用DWORD类型表示任何双字节的数据。
这里笔者将不再重复如何使用DWORD类型的变量的方法。
【INPUT_RECORD结构体】
INPUT_RECORD结构体是用于描述输入设备事件的结构体,声明如下:
typedefstruct_INPUT_RECORD{
WORDEventType;//事件类型
union{
KEY_EVENT_RECORDKeyEvent;//键盘事件
MOUSE_EVENT_RECORDMouseEvent;//鼠标事件
WINDOW_BUFFER_SIZE_RECORDWindowBufferSizeEvent;//窗口缓冲事件
MENU_EVENT_RECORDMenuEvent;//菜单事件
FOCUS_EVENT_RECORDFocusEvent;//摄像头事件
}Event;
}INPUT_RECORD;
当然上述的结构体不像一般的结构体,它有一些复杂,但是也不影响我们的使用,在此要多说一句的是union一词,union也是C/C++语言中的关键字,用于描述“共用体”类型,所谓的共用体实际上是指在进行某些算法编程的时候,需要使几种不同类型的变量存放到同一段内存单元中。
也就是使用覆盖技术,几个变量互相覆盖。
这种几个不同的变量共同占用一段内存的结构,在C语言中,被称作“共用体”类型结构,简称共用体。
也被称之为“联合体”。
它是早期的C语言过渡到C++中的产物。
当然这里我们只需要了解一下就行,并不需要做太多的深究。
【事件类型】
INPUT_RECORD结构体中的EnventType成员用于说明事件的类型,通常取值如下:
事件取值
相关描述
FOCUS_EVENT
摄像头事件,控制对摄像头的操作
KEY_EVENT
键盘事件,控制对键盘的操作
MENU_EVENT
菜单事件,控制对UI中菜单的操作
MOUSE_EVENT
鼠标事件,控制对鼠标的操作
WINDOW_BUFFER_SIZE_EVENT
窗体缓冲事件
小提示:
EventType有5种事件,对应五个RECORD。
所以当要使用的是键盘事件时,应该先判断EventType是否为KeyEvent,然后使用KEY_EVENT_RECORD,判断现在的键盘是什么情况。
其他事件也是一样的(一般只使用键盘和鼠标事件)。
键盘事件通常有字符事件和按键事件。
之所以称为事件,当它们被按下时,事件被激发。
【KEY_EVENT_RECORD结构体】
KEY_EVENT_RECORD用于记录键盘事件的情况,当有键盘事件发生时,我们就可以使用该结构获取此时键盘的情况。
KEY_EVENT_RECORD结构体声明如下;
typedefstruct_KEY_EVENT_RECORD
{
BOOLbKeyDown;//TRUE表示键按下,FALSE表示键释放
WORDwRepeatCount;//按键次数
WORDwVirtualKeyCode;//虚拟键代码
WORDwVirtualScanCode;//虚拟键扫描码
union{
WCHARUnicodeChar;//宽字符
CHARAsciiChar;//ASCII字符
}uChar;//字符
DWORDdwControlKeyState;//控制键状态
}
KEY_EVENT_RECORD;
【虚拟键码】
接下来为大家介绍一下虚拟键码的概念,由于键盘上的一些特殊键,例如ctrl、alt、shift、enter等键不能够直接被控制台程序所识别,故此在MFC中引入了虚拟键码的概念。
虚拟指的是假定存在于思想中而不是现实世界中的一些事物,也只有熟练使用DOS组合语言编写应用程式的程式写作者才有可能指出,为什么对Windows键盘处理如此基本的键码是虚拟的而不是真实的。
对於早期的程序员来说,真实的键码由实际键盘硬体产生。
在Windows文件中将这些键码称为「扫描码(scancodes)」。
在IBM相容机种上,扫描码16是Q键,17是W键,18是E、19是R,20是T,21是Y等等。
这时您会发现,扫描码是依据键盘的实际布局的。
Windows开发者认为这些代码过於与设备相关了,於是他们试图通过定义所谓的虚拟键码,以便经由与装置无关的方式处理键盘。
其中一些虚拟键码不能在IBM相容机种上产生,但可能会在其他制造商生产的键盘中找到,或者在未来的键盘上找到。
提出虚拟键盘码的好处就在于可以兼容不同布局的键盘。
(不要忘记即使是笔记本电脑也有小键盘和大键盘之分的哦!
)
常用的虚拟键盘码如下:
十进制
十六进制
WINUSER.H识别字
IBM相容键盘
8
8
VK_BACK
Backspace
9
9
VK_TAB
Tab
12
0C
VK_CLEAR
Lock关闭时的数字键盘5
13
0D
VK_RETURN
Enter
16
10
VK_SHIFT
Shift
17
11
VK_CONTROL
Ctrl
18
12
VK_MENU
Alt
19
13
VK_PAUSE
Pause
20
14
VK_CAPITAL
CapsLock
27
1B
VK_ESCAPE
Esc
32
20
VK_SPACE
Spacebar
33
21
VK_PRIOR
PageUp
34
22
VK_NEXT
PageDown
35
23
VK_END
End
36
24
VK_HOME
Home
37
25
VK_LEFT
左箭头
38
26
VK_UP
上箭头
39
27
VK_RIGHT
右箭头
40
28
VK_DOWN
下箭头
41
29
VK_SELECT
42
2A
VK_PRINT
43
2B
VK_EXECUTE
44
2C
VK_SNAPSHOT
PrintScreen
45
2D
VK_INSERT
Insert
46
2E
VK_DELETE
Delete
47
2F
VK_HELP
91
5B
VK_LWIN
左Windows键
92
5C
VK_RWIN
右Windows键
106
6A
VK_MULTIPLY
数字键盘上的*
107
6B
VK_ADD
数字键盘上的+
108
6C
VK_SEPARATOR
109
6D
VK_SUBTRACT
数字键盘上的-
110
6E
VK_DECIMAL
数字键盘上的.
111
6F
VK_DIVIDE
数字键盘上的/
上述是在MFC中常用的一些虚拟键码,更多的大家可以到网上进行查询。
使用虚拟键码读者需要注意以下的几个要点:
1、Windows下的程序通常不需要监视shift、ctrl或alt键的状态
2、键盘上的字母和数字使用其ASCII码直接表示,而不采用虚拟键码
3、Windows操作系统中使用VK_LWIN以及VK_RWIN两个键打开“开始菜单”或者是启动“工作管理员模式”,故此需要谨慎使用这两个虚拟键码。
4、对于键盘上的功能键(F1–F12)一般不进行处理,当然虚拟键码表中提供了24个功能键的编码。
【键盘事件的操作步骤】
键盘事件的操作步骤如下:
while
(1)
{
监听键盘;
if(有键盘事件发生)
{
if(键盘上有按键被按下)
{
执行被触发的功能代码;
}
}
}
【监听键盘事件】
在MFC框架中通常使用ReadConsoleInput函数对键盘事件进行侦听,并通过形参将侦听到的结果保存在INPUT_RECORD结构体中。
函数声明如下:
BOOLReadConsoleInput(
HANDLEhConsoleInput,//输入设备句柄
PINPUT_RECORDlpBuffer,//返回数据记录
DWORDnLength,//要读取的记录数
LPDWORDlpNumberOfEventsRead//返回已读取的记录数
);
【判断是否有键盘事件发生】
判断是否有键盘事件发生通常使用INPUT_RECORD结构体中的EnventType进行判断,如果该成员的值与KEY_ENVENT(键盘事件)等价时,则证明发生了键盘事件,即:
if(rec.EventType=KEY_EVENT)//如果捕捉到了键盘事件
{
cout<<"发生了键盘事件!
"<}
【判断是否有键被按下】
那么如何判断键盘上是否有键被按下呢?
这很简单,我们需要通过判断INPUT_RECORD结构体中的成员Event中的KeyEvent中的成员bKeyDown(即BoolKeyDown)是否为true即可。
即:
if(rec.Event.KeyEvent.bKeyDown==true)//如果此时有键按下
{
cout<<”有键被按下!
”<}
【判断键盘按下的键值】
那么我们怎么才能够知道到底是哪个键被按下呢?
此时我们可以通过KeyEvent成员中的uChar成员获取该键的ASCII码或者是Unicode编码,进行判断。
获取该键的ASCII码的代码为:
charc=rec.Event.KeyEvent.uChar.AsciiChar;//获取该键的ASCII码
获取该键的Unicode编码的代码为:
WCHARch=rec.Event.KeyEvent.uChar.UnicodeChar;
通常我们使用ASCII码判断该键。
[例]键盘事件监听小例子
#include
#include
usingnamespacestd;
voidmain()
{
HANDLEin=GetStdHandle(STD_INPUT_HANDLE);//获取键盘句柄
INPUT_RECORDrec;//保存键盘事件结果的结构体
DWORDrecCount;//已读取的记录数
while
(1)//始终循环监听
{
ReadConsoleInput(in,&rec,1,&recCount);//侦听键盘事件
if(rec.EventType=KEY_EVENT)//如果捕捉到了键盘事件
{
if(rec.Event.KeyEvent.bKeyDown==true)//如果此时有键按下
{
charc=rec.Event.KeyEvent.uChar.AsciiChar;//获取该键的ASCII码
putchar(c);//打印该键
}
}
}
}
运行效果如下:
当然,光看图是不行的,还需要读者自己去运行代码体会代码中的含义。
【键盘事件综合案例】
此处笔者提供一个综合案例供大家练习和参考!
该案例是一个简单的文本编辑器,可以模拟打字、按backspace退格删除、空格、按esc退出等功能。
#include
#include
#include
usingnamespacestd;
voidmain()
{
HANDLEout=GetStdHandle(STD_OUTPUT_HANDLE);//获取控制台输出句柄
HANDLEin=GetStdHandle(STD_INPUT_HANDLE);//获取控制台输出句柄
DWORDdwRes,dwState=0;
COORDcrHome={0,0},crPos;
INPUT_RECORDkeyRec;
CONSOLE_SCREEN_BUFFER_INFObInfo;
cout<<"欢迎您使用基于控制台的文本编辑器!
"<cout<<"请按任意键继续!
"<getch();
system("cls");
while
(1)
{
ReadConsoleInput(in,&keyRec,1,&dwRes);//读取屏幕输入
if(keyRec.EventType==KEY_EVENT)//若存在键盘事件
{
if(keyRec.Event.KeyEvent.bKeyDown)//当有按键按下时
{
dwState=keyRec.Event.KeyEvent.dwControlKeyState;//获取键盘状态
GetConsoleScreenBufferInfo(out,&bInfo);//获取控制台信息
SetConsoleCursorPosition(out,crHome);//设置控制台
SetConsoleCursorPosition(out,bInfo.dwCursorPosition);//还原光标位置
switch(keyRec.Event.KeyEvent.wVirtualKeyCode)//判断按下的键值
{
caseVK_SPACE:
cout<<"";break;
caseVK_RETURN:
cout<<"\n";break;
caseVK_BACK:
{
GetConsoleScreenBufferInfo(out,&bInfo);
crPos=bInfo.dwCursorPosition;
if(crPos.X!
=0)
{
crPos.X-=1;
}
else
{
crPos.Y-=1;
crPos.X=60;
}
SetConsoleCursorPosition(out,crPos);
cout<<"";
SetConsoleCursorPosition(out,crPos);
}break;
caseVK_ESCAPE:
CloseHandle(out);//关闭标准输出设备句柄
CloseHandle(in);//关闭标准输入设备句柄
return;
default:
break;
}
charch=keyRec.Event.KeyEvent.uChar.AsciiChar;
if(ch>0x20&&ch<0x7e)
{
putchar(ch);
}
}
}
}
}