输入设备键盘鼠标你得会
键盘和鼠标是个人计算机中常用的输入设备。通过键盘可以将字母、数字、标点符号等输入计算机中,从而向计算机发出命今;鼠标可以对屏幕上的光标进行定位,并通过鼠标按钮和滚轮对光标所处位置的屏幕元素进行操作。一个应用程序应该响应用户的键盘和鼠标输入事件。
键盘
键盘上的每一个键相当于一个开关,键盘中有一个芯片对键盘上每一个键的开关状态进行扫描。按下一个键时,开关接通,该芯片会产生一个扫描码。扫描码说明了按下的键在键盘上的位置。松开按下的键会产生一个扫描码,这说明了松开的键在键盘上的位置。扫描码与具体键盘设备相关。键盘设备驱动程序解释扫描码并将其转换(映射)为虚拟键码,虚拟键码是系统定义的一个与设备无关的值。转换扫描码以后,将创建一条消息,其中包含虚拟键码和有关按键的其他信息,然后将该消息放入系统的消息队列,系统将这个键盘消息发送到相应线程的消息队列中,最后,线程的消息循环获取该消息并将其分发送给相应的窗口过程进行处理。
活动窗口与键盘焦点
活动窗口(Active Window)是用户当前使用的顶级窗口,同一时刻只能有一个程序窗口是活动窗口。系统将其放置在Z顺序的顶部,并突出显示其标题栏和边框。用户可以通过单击顶级窗口,或使用
Alt +Tab / Alt + Esc
组合键来激活顶级窗口使其成为活动窗口,可以通过调用GetActiveWindow()
函数获取活动窗口的句柄。还可以通过调用SetActiveWindow()
函数来激活顶级窗口,类似的函数还有
BringWindowToTop()
SwitchToThisWindow()
和SetForegroundWindow()
等,但是在某些系统中,这些函数可能达不到预期的效果。当用户正在使用一个窗口时,Windows不会强制另一个窗口到达前台,仅闪烁窗口的任务栏程序图标以通知用户。
桌面上的所有窗口共享键盘,只有活动窗口或活动窗口的子窗口才可以接收键盘输入。具有键盘焦点的窗口接收键盘消息,直到键盘焦点变为其他窗口。
当一个窗口被激活时,系统会发送WM_ACTIVATE消息。默认窗口过程DefWindowProc()
会处理这个消息,并将键盘焦点设置为这个活动窗口。
程序可以通过SetFocus()
函数为自己的窗口或子窗口设置键盘焦点︰
HWND WINAPI SetFocus(_In_opt_ HWND hWnd);
函数执行成功,返回值是以前具有键盘焦点的窗口的句柄。
当键盘焦点从一个窗口更改为另一个窗口时,系统会向失去焦点的窗口发送WM_KILLFOCUS
消息,然后将WM_SETFOCUS
消息发送到已获得焦点的窗口。
程序可以通过调用BlockInput()
函数阻止键盘和鼠标输入∶
BOOL WINAPI BlockInput(_ln_BOOL fBlocklt);
fBlocklt
参数设置为TRUE表示阻止键盘和鼠标输入事件,设置为FALSE表示取消阻止。测试这个函数的时候请注意,因为编译运行程序会使本程序成为活动窗口,键盘和鼠标输入会失效,导致无法关闭本程序,也无法切换到其他程序,这时只能按Ctrl + Alt +Del组合键打开任务管理器来结束本进程。
系统击键消息和非系统击键消息
按下一个键将产生WM_KEYDOWN或WM_SYSKEYDOWN消息,释放一个键将产生WM_KEYUP或WM_SYSKEYUP消息。按键按下和按键抬起消息通常是成对出现的,如果用户按住一个键不放,则系统会生成一系列WM_KEYDOWN或WM_SYSKEYDOWN消息,直到用户释放按键时,再生成一条WM_KEYUP或WM_SYSKEYUP消息。
系统区分系统击键和非系统击键,系统击键生成系统击键消息,WM_SYSKEYDOWN
和WM_SYSKEYUP
,非系统击键生成非系统击键消息WM_KEYDOWN
和WM_KEYUP
。当用户按下F10键(激活菜单栏),或键入与Alt键组合的键时,将生成系统击键消息,例如Alt +F4组合键用于关闭一个程序。系统击键主要用于访问系统菜单,系统击键消息由默认窗口过程DefWindowProc
进行处理。程序的窗口过程通常不应该处理系统击键消息。
DefWindowProc
不会处理非系统击键消息,因此程序窗口过程应该处理感兴趣的非系统击键消息。对于WM_KEYDOWN和WM_KEYUP消息,通常只需要处理一个即可,通常都是处理WM_KEYDOWN消息,并且通常只处理那些包含方向键、上档键Shift和功能键(F1~F12)等的虚拟键码的消息,不处理来自可显示字符键的击键消息。还记得在讲解消息循环时说过,TranslateMessage()
函数可以把
WM_KEYDOWN消息转换为字符消息WM_CHAR,这样一来我们就可以在窗口过程中处理字符消息WM_CHAR
来判断用户是按下了哪个字符按键,即有了TranslateMessage函数的帮助,对于字符按键我们不需要处理WM_KEYDOWN和WM_KEYUP消息,只需要处理字符消息WM_CHAR
对于系统击键消息WM_SYSKEYDOWN
和WM_SYSKEYUP
,非系统击键消息WM_KEYDOWN
和WM_KEYUP
,wParam参数包含虚拟键码,用于确定哪个键被按下或释放。
lParam参数
是击键消息的一些附加信息,包含消息的重复计数、扫描码、扩展键标志、状态描述码、先前键状态标志、转换状态标志等,程序通常不需要关心这些附加信息。
位含义
- 0~15当前消息的重复计数,也就是由于用户按住键不放而自动重复击键的次数。对于
WM_KEYUP
和WM_SYSKEYUP
消息,重复计数始终为1 - 16~23扫描码。扫描码是与具体键盘设备相关的,其值取决于具体的键盘设备,因此程序通常会忽略扫描码,而使用与设备无关的虚拟键码
- 24扩展键标志,如果是扩展键,则值为1,否则为0。对于扩展的101键和102键键盘,扩展键是键盘右侧的Alt和Ctrl,InsDel HomeEnd- PageUp- PageDown和数字小键盘中的箭头键,NumLock键,Break(Ctrl + Pause)键,PrintScrn键,以及数字键盘中的/和Enter键。程序通常忽略扩展键标志
- 25~28保留
- 29状态描述码。状态描述码表示在生成击键消息时Alt键是否已按下。如果Alt键已按下,则该位为1;如果Alt键已释放,则该位为0。对于WM_KEYDOWN和WM_KEYUP消息该位始终为0,对于
WM_SYSKEYDOWN和WM_SYSKEYUP消息该位始终为1 - 30键先前状态标志。如果在发送消息之前键是按下的,则值为1;如果键是抬起的,则值为0。对于WM_KEYUP和WM_SYSKEYUP消息该位始终为1,对于WM_KEYDOWN和WM_SYSKEYDOWN消息该位始终为0
- 31转换状态标志。转换状态标志表示是因为按下按键还是释放按键而产生的击键消息,对于WM_KEYDOWN和WM_SYSKEYDOWN消息该位始终为0,对于WM_KEYUP和WM_SYSKEYUP消息该位始终为1
虚拟键码
虚拟键码在WinUser.h头文件中定义。下面列出常见按键的虚拟键码,虽然虚拟键码比较多,但是在击键消息中常用的并不多。
有关鼠标按钮的虚拟键码如下表所示,但是请注意,击键消息中不会有鼠标按钮的虚拟键码,鼠标按钮的虚拟键码在鼠标消息
中。
- VK_LBUTTON 0x01 鼠标左键
- VK_RBUTTON 0x02 鼠标右键
- VK_MBUTTON 0x04 鼠标中键
程序可能需要处理下表中的一些键。但是像BackSpace
键、Tab
键、Enter
键、Esc
键和空格
键,通常是在字符消息
(而不是击键消息)中处理。
实际上开发,比较常用的是下表列举的虚拟键码。
数字键和字母键的虚拟键码就是其ASCII码
,不过通常是在字符消息(而不是击键消息)中处理,如下表所示:
虚拟键码数值含义
- 0x30~0x390 0~9 键
- 0x41~0x5A A~Z 键
左Windows键和右Windows键用于打开Windows开始菜单,
Application键位于右Ctrl键的左侧,它的作用相当于鼠标右键,用来激活Windows或程序中的右键菜单,如下表所示。
虚拟键码数值含义
-
VK_LWINOx5B 左Windows键
-
VK_RWINOx5C 右Windows键
-
VK_APPS0x5D Application键
与数字小键盘中的键相对应的虚拟键码如表所示。
- VK_NUMPADO~VK_NUMPAD9 0x60~Ox69 数字小键盘0~9键
- VK_MULTIPLY 0x6A 数字小键盘的
*键
- VK_ADD Ox6B 数字小键盘的
+键
- VK_SUBTRACT 0x6D 数字小键盘的
- 键
- VK_DECIMAL 0x6E 数字小键盘的
. 键
- VK_DIVIDE Ox6F 数字小键盘的
/键
大部分键盘只有12个功能键,不过Windows提供了24个功能键的虚拟键码,程序通常把功能键用作键盘快捷键。
虚拟键码数值含义
- VK_F1~VK_F24 0x70~0x87 F1~F24键
- VK_NUMLOCK 0x90 Num Lock键
- VK_SCROLL 0x91 Scroll Lock键
我们来看WM_KEYDOWN
消息,窗口过程如何结合下面的案例来使用。实现,滚动条拖动,不用鼠标操作,通过键盘按钮来完成。
Metrics.h
#pragma once
struct
{
int m_nIndex;
PTSTR m_pLabel;
PTSTR m_pDesc;
}METRICS[] = {
SM_CXSCREEN, TEXT("SM_CXSCREEN"), TEXT("屏幕的宽度"),
SM_CYSCREEN, TEXT("SM_CYSCREEN"), TEXT("屏幕的高度"),
SM_CXFULLSCREEN, TEXT("SM_CXFULLSCREEN"), TEXT("全屏窗口的客户区宽度"),
SM_CYFULLSCREEN, TEXT("SM_CYFULLSCREEN"), TEXT("全屏窗口的客户区高度"),
SM_ARRANGE, TEXT("SM_ARRANGE"), TEXT("如何排列最小化窗口"),
SM_CLEANBOOT, TEXT("SM_CLEANBOOT"), TEXT("系统启动方式"),
SM_CMONITORS, TEXT("SM_CMONITORS"), TEXT("监视器的数量"),
SM_CMOUSEBUTTONS, TEXT("SM_CMOUSEBUTTONS"), TEXT("鼠标上的按钮数"),
SM_CONVERTIBLESLATEMODE, TEXT("SM_CONVERTIBLESLATEMODE"), TEXT("笔记本电脑或平板电脑模式"),
SM_CXBORDER, TEXT("SM_CXBORDER"), TEXT("窗口边框的宽度"),
SM_CYBORDER, TEXT("SM_CYBORDER"), TEXT("窗口边框的高度"),
SM_CXCURSOR, TEXT("SM_CXCURSOR"), TEXT("光标的宽度"),
SM_CYCURSOR, TEXT("SM_CYCURSOR"), TEXT("光标的高度"),
SM_CXDLGFRAME, TEXT("SM_CXDLGFRAME"), TEXT("同SM_CXFIXEDFRAME,有标题但不可调整大小的窗口边框的宽度"),
SM_CYDLGFRAME, TEXT("SM_CYDLGFRAME"), TEXT("同SM_CYFIXEDFRAME,有标题但不可调整大小的窗口边框的高度"),
SM_CXDOUBLECLK, TEXT("SM_CXDOUBLECLK"), TEXT("鼠标双击事件两次点击的X坐标不可以超过这个值"),
SM_CYDOUBLECLK, TEXT("SM_CYDOUBLECLK"), TEXT("鼠标双击事件两次点击的Y坐标不可以超过这个值"),
SM_CXDRAG, TEXT("SM_CXDRAG"), TEXT("拖动操作开始之前,鼠标指针可以移动的鼠标下方点的任意一侧的像素数"),
SM_CYDRAG, TEXT("SM_CYDRAG"), TEXT("拖动操作开始之前,鼠标指针可以移动的鼠标下移点上方和下方的像素数"),
SM_CXEDGE, TEXT("SM_CXEDGE"), TEXT("三维边框的宽度"),
SM_CYEDGE, TEXT("SM_CYEDGE"), TEXT("三维边框的高度"),
SM_CXFIXEDFRAME, TEXT("SM_CXFIXEDFRAME"), TEXT("同SM_CXDLGFRAME,有标题但不可调整大小的窗口边框的宽度"),
SM_CYFIXEDFRAME, TEXT("SM_CYFIXEDFRAME"), TEXT("同SM_CYDLGFRAME,有标题但不可调整大小的窗口边框的高度"),
SM_CXFOCUSBORDER, TEXT("SM_CXFOCUSBORDER"), TEXT("DrawFocusRect绘制的焦点矩形的左边缘和右边缘的宽度"),
SM_CYFOCUSBORDER, TEXT("SM_CYFOCUSBORDER"), TEXT("DrawFocusRect绘制的焦点矩形的上边缘和下边缘的高度"),
SM_CXFRAME, TEXT("SM_CXFRAME"), TEXT("同SM_CXSIZEFRAME,可调大小窗口边框的宽度"),
SM_CYFRAME, TEXT("SM_CYFRAME"), TEXT("同SM_CYSIZEFRAME,可调大小窗口边框的高度"),
SM_CXHSCROLL, TEXT("SM_CXHSCROLL"), TEXT("水平滚动条中箭头位图的宽度"),
SM_CYHSCROLL, TEXT("SM_CYHSCROLL"), TEXT("水平滚动条中箭头位图的高度"),
SM_CXVSCROLL, TEXT("SM_CXVSCROLL"), TEXT("垂直滚动条中箭头位图的宽度"),
SM_CYVSCROLL, TEXT("SM_CYVSCROLL"), TEXT("垂直滚动条中箭头位图的高度"),
SM_CXHTHUMB, TEXT("SM_CXHTHUMB"), TEXT("水平滚动条中滚动框(滑块)的高度"),
SM_CYVTHUMB, TEXT("SM_CYVTHUMB"), TEXT("垂直滚动条中滚动框(滑块)的宽度"),
SM_CXICON, TEXT("SM_CXICON"), TEXT("图标的默认宽度"),
SM_CYICON, TEXT("SM_CYICON"), TEXT("图标的默认高度"),
SM_CXICONSPACING, TEXT("SM_CXICONSPACING"), TEXT("大图标视图中项目的网格单元格宽度"),
SM_CYICONSPACING, TEXT("SM_CYICONSPACING"), TEXT("大图标视图中项目的网格单元格高度"),
SM_CXMAXIMIZED, TEXT("SM_CXMAXIMIZED"), TEXT("最大化顶层窗口的默认宽度"),
SM_CYMAXIMIZED, TEXT("SM_CYMAXIMIZED"), TEXT("最大化顶层窗口的默认高度"),
SM_CXMAXTRACK, TEXT("SM_CXMAXTRACK"), TEXT("具有标题和大小调整边框的窗口可以拖动的最大宽度"),
SM_CYMAXTRACK, TEXT("SM_CYMAXTRACK"), TEXT("具有标题和大小调整边框的窗口可以拖动的最大高度"),
SM_CXMENUCHECK, TEXT("SM_CXMENUCHECK"), TEXT("菜单项前面复选框位图的宽度"),
SM_CYMENUCHECK, TEXT("SM_CYMENUCHECK"), TEXT("菜单项前面复选框位图的高度"),
SM_CXMENUSIZE, TEXT("SM_CXMENUSIZE"), TEXT("菜单栏按钮的宽度"),
SM_CYMENUSIZE, TEXT("SM_CYMENUSIZE"), TEXT("菜单栏按钮的高度"),
SM_CXMIN, TEXT("SM_CXMIN"), TEXT("窗口的最小宽度"),
SM_CYMIN, TEXT("SM_CYMIN"), TEXT("窗口的最小高度"),
SM_CXMINIMIZED, TEXT("SM_CXMINIMIZED"), TEXT("最小化窗口的宽度"),
SM_CYMINIMIZED, TEXT("SM_CYMINIMIZED"), TEXT("最小化窗口的高度"),
SM_CXMINSPACING, TEXT("SM_CXMINSPACING"), TEXT("最小化窗口的网格单元宽度"),
SM_CYMINSPACING, TEXT("SM_CYMINSPACING"), TEXT("最小化窗口的网格单元高度"),
SM_CXMINTRACK, TEXT("SM_CXMINTRACK"), TEXT("窗口的最小拖动宽度,用户无法将窗口拖动到小于这些尺寸"),
SM_CYMINTRACK, TEXT("SM_CYMINTRACK"), TEXT("窗口的最小拖动高度,用户无法将窗口拖动到小于这些尺寸"),
SM_CXPADDEDBORDER, TEXT("SM_CXPADDEDBORDER"), TEXT("标题窗口的边框填充量"),
SM_CXSIZE, TEXT("SM_CXSIZE"), TEXT("窗口标题或标题栏中按钮的宽度"),
SM_CYSIZE, TEXT("SM_CYSIZE"), TEXT("窗口标题或标题栏中按钮的高度"),
SM_CXSIZEFRAME, TEXT("SM_CXSIZEFRAME"), TEXT("同SM_CXFRAME,可调大小窗口边框的宽度"),
SM_CYSIZEFRAME, TEXT("SM_CYSIZEFRAME"), TEXT("同SM_CYFRAME,可调大小窗口边框的厚度"),
SM_CXSMICON, TEXT("SM_CXSMICON"), TEXT("小图标的建议宽度"),
SM_CYSMICON, TEXT("SM_CYSMICON"), TEXT("小图标的建议高度"),
SM_CXSMSIZE, TEXT("SM_CXSMSIZE"), TEXT("小标题按钮的宽度"),
SM_CYSMSIZE, TEXT("SM_CYSMSIZE"), TEXT("小标题按钮的高度"),
SM_CXVIRTUALSCREEN, TEXT("SM_CXVIRTUALSCREEN"), TEXT("虚拟屏幕的宽度"),
SM_CYVIRTUALSCREEN, TEXT("SM_CYVIRTUALSCREEN"), TEXT("虚拟屏幕的高度"),
SM_CYCAPTION, TEXT("SM_CYCAPTION"), TEXT("标题区域的高度"),
SM_CYKANJIWINDOW, TEXT("SM_CYKANJIWINDOW"), TEXT("屏幕底部的日文汉字窗口的高度"),
SM_CYMENU, TEXT("SM_CYMENU"), TEXT("单行菜单栏的高度"),
SM_CYSMCAPTION, TEXT("SM_CYSMCAPTION"), TEXT("小标题的高度"),
SM_DBCSENABLED, TEXT("SM_DBCSENABLED"), TEXT("User32.dll是否支持DBCS"),
SM_DEBUG, TEXT("SM_DEBUG"), TEXT("是否安装了User.exe的调试版本"),
SM_DIGITIZER, TEXT("SM_DIGITIZER"), TEXT("设备支持的数字转换器输入类型"),
SM_IMMENABLED, TEXT("SM_IMMENABLED"), TEXT("是否启用了输入法管理器/输入法编辑器功能"),
SM_MAXIMUMTOUCHES, TEXT("SM_MAXIMUMTOUCHES"), TEXT("系统中是否有数字化仪"),
SM_MEDIACENTER, TEXT("SM_MEDIACENTER"), TEXT("当前操作系统是不是Windows XP Media Center"),
SM_MENUDROPALIGNMENT, TEXT("SM_MENUDROPALIGNMENT"), TEXT("下拉菜单是否与相应的菜单栏项右对齐"),
SM_MIDEASTENABLED, TEXT("SM_MIDEASTENABLED"), TEXT("系统是否启用希伯来语和阿拉伯语"),
SM_MOUSEHORIZONTALWHEELPRESENT, TEXT("SM_MOUSEHORIZONTALWHEELPRESENT"), TEXT("是否安装了带有水平滚轮的鼠标"),
SM_MOUSEPRESENT, TEXT("SM_MOUSEPRESENT"), TEXT("是否安装了鼠标"),
SM_MOUSEWHEELPRESENT, TEXT("SM_MOUSEWHEELPRESENT"), TEXT("是否安装了带有垂直滚轮的鼠标"),
SM_NETWORK, TEXT("SM_NETWORK"), TEXT("是否存在网络"),
SM_PENWINDOWS, TEXT("SM_PENWINDOWS"), TEXT("是否安装了Microsoft Windows for Pen Computing扩展"),
SM_REMOTECONTROL, TEXT("SM_REMOTECONTROL"), TEXT("当前终端服务器会话是否被远程控制"),
SM_REMOTESESSION, TEXT("SM_REMOTESESSION"), TEXT("调用进程是否与终端服务客户机会话关联"),
SM_SAMEDISPLAYFORMAT, TEXT("SM_SAMEDISPLAYFORMAT"), TEXT("所有显示器的颜色格式是否相同"),
SM_SECURE, TEXT("SM_SECURE"), TEXT("始终返回0"),
SM_SERVERR2, TEXT("SM_SERVERR2"), TEXT("系统是否是Windows Server 2003 R2"),
SM_SHOWSOUNDS, TEXT("SM_SHOWSOUNDS"), TEXT("用户是否要求应用程序在其他情况下以可视方式呈现信息"),
SM_SHUTTINGDOWN, TEXT("SM_SHUTTINGDOWN"), TEXT("当前会话是否正在关闭"),
SM_SLOWMACHINE, TEXT("SM_SLOWMACHINE"), TEXT("计算机是否具有低端(慢速)处理器"),
SM_STARTER, TEXT("SM_STARTER"), TEXT("当前操作系统版本"),
SM_SWAPBUTTON, TEXT("SM_SWAPBUTTON"), TEXT("鼠标左键和右键的功能是否互换了"),
SM_SYSTEMDOCKED, TEXT("SM_SYSTEMDOCKED"), TEXT("停靠模式的状态"),
SM_TABLETPC, TEXT("SM_TABLETPC"), TEXT("是否启动了Tablet PC输入服务"),
SM_XVIRTUALSCREEN, TEXT("SM_XVIRTUALSCREEN"), TEXT("虚拟屏幕左侧的坐标"),
SM_YVIRTUALSCREEN, TEXT("SM_YVIRTUALSCREEN"), TEXT("虚拟屏幕顶部的坐标")
};
Main.cpp
#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; //设备上下文DC句柄
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; // 无效区域
if (WM_CREATE == uMsg) //窗口创建的消息
{
//查窗口工作区 设备上下文DC的句柄
hdc = ::GetDC(hwnd);
//创建宋体特征字族逻辑字体
hFont = ::CreateFont(12, 0, 0, 0, 0, 0, 0, 0, GB2312_CHARSET, 0, 0, 0, 0, TEXT("宋体"));
//查设备上下文DC用的默认字体
hFontOld = (HFONT)SelectObject(hdc, hFont);
//遍历所有的单元格,找到1~3最大的宽X
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;
}
}
//采用最大的高Y+2
s_iHeight = size.cy + 2;
//查当前设备上下文DC的文本指标 ,文本指标提供了一系列指标值
::GetTextMetrics(hdc, &tm);
//查到平均字符宽度
s_cxChar = tm.tmAveCharWidth;
//设备上下文DC的字体恢复为默认字体
::SelectObject(hdc, hFontOld);
//删除宋体逻辑字体对象
::DeleteObject(hFont);
//释放设备上下文DC
::ReleaseDC(hwnd, hdc);
return 0;
}
else if (WM_SIZE == uMsg) //窗口大小调整改变的消息
{
// 客户区宽度、高度
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;
}
else if (WM_VSCROLL == uMsg) //拉动垂直滚动条的消息
{
//查垂直滚动条信息
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_ALL;
GetScrollInfo(hwnd, SB_VERT, &si);
iVertPos = si.nPos; //更新当前垂直滚动位置
if (LOWORD(wParam) == SB_LINEUP)
{
si.nPos -= 1;
}
else if (LOWORD(wParam) == SB_LINEDOWN)
{
si.nPos += 1;
}
else if (LOWORD(wParam) == SB_PAGEUP)
{
si.nPos -= si.nPage;
}
else if (LOWORD(wParam) == SB_PAGEDOWN)
{
si.nPos += si.nPage;
}
else if (LOWORD(wParam) == SB_THUMBTRACK)
{
si.nPos = si.nTrackPos;
}
// 设置位置,然后获取位置,如果si.nPos越界,Windows不会设置
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_POS;
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
// 如果Windows更新了滚动条位置,我们更新客户区
GetScrollInfo(hwnd, SB_VERT, &si);
if (iVertPos != si.nPos)
{
::ScrollWindow(hwnd, 0, s_iHeight * (iVertPos - si.nPos), NULL, NULL);
::UpdateWindow(hwnd);
}
return 0;
}
else if (uMsg == WM_HSCROLL) //拉动水平滚动条的消息
{
//查水平滚动条信息
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_ALL;
GetScrollInfo(hwnd, SB_HORZ, &si);
iHorzPos = si.nPos;//更新当前水平滚动位置
if (LOWORD(wParam) == SB_LINELEFT)
{
si.nPos -= 1;
}
else if (LOWORD(wParam) == SB_LINERIGHT)
{
si.nPos += 1;
}
else if (LOWORD(wParam) == SB_PAGELEFT)
{
si.nPos -= si.nPage;
}
else if (LOWORD(wParam) == SB_PAGERIGHT)
{
si.nPos += si.nPage;
}
else if (LOWORD(wParam) == SB_THUMBTRACK)
{
si.nPos = si.nTrackPos;
}
else if (LOWORD(wParam) == SB_BOTTOM)
{
si.nPos = NUMLINES - 1;
}
else if (LOWORD(wParam) == SB_TOP)
{
si.nPos = 0;
}
// 设置位置,然后获取位置,如果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;
}
else if (uMsg == 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;
}
else if (uMsg == WM_DESTROY)
{
::PostQuitMessage(0);
return 0;
}
else if (uMsg == WM_KEYDOWN)
{ //接收非系统击键按下消息
if (wParam == VK_UP) //向上箭头键
{
::SendMessage(hwnd,WM_VSCROLL,SB_LINEUP,0);
}
else if (wParam == VK_DOWN) //向下箭头键
{
::SendMessage(hwnd, WM_VSCROLL,SB_LINEDOWN,0);
}
else if (wParam == VK_PRIOR) //PageUp键
{
::SendMessage(hwnd,WM_VSCROLL,SB_PAGEUP,0);
}
else if (wParam == VK_NEXT) //PageDown键
{
::SendMessage(hwnd, WM_VSCROLL,SB_PAGEDOWN,0);
}
else if (wParam == VK_HOME) //Home键(Fn+PageUp键)
{
::SendMessage(hwnd, WM_VSCROLL,SB_TOP,0);
}
else if (wParam == VK_END) // End键 (Fn+PageDown键)
{
::SendMessage(hwnd, WM_VSCROLL, SB_BOTTOM, 0);
}
else if (wParam == VK_LEFT) //左键头键
{
::SendMessage(hwnd,WM_HSCROLL,SB_LINELEFT,0);
}
else if (wParam == VK_RIGHT) //右箭头键
{
::SendMessage(hwnd, WM_HSCROLL,SB_LINERIGHT,0);
}
}
return ::DefWindowProc(hwnd, uMsg, wParam, lParam);
}
我们把每一个WM_KEYDOWN消息转换为等同的WM_VSCROLL或WM_HSCROLL消息,这是通过给窗口过程发送假冒的
WM_VSCROLL或WM_HSCROLL消息来欺骗WindowProc窗口过程实现的,使它认为收到了滚动条消息,这就避免了在WM_KEYDOWN和WM_VSCROLL或WM_HSCROLL消息中存在两份相同的滚动条处理代码。
SendMessage()
函数使用频率非常高。SendMessage()
函数用于向一个窗口发送消息,不仅可以发送给自己的窗口,还可以发送给其他程序窗口,只要获得了它们的窗口句柄︰
LRESULT WINAPI SendMessage
(
_In_HWND hWnd,//要向哪个窗口发送消息,hWnd窗口的窗口过程将接收该消息
_In_ UINT Msg,//消息类型
_In_WPARAM wParam,//消息的wParam参数
_In__LPARAM lParam //消息的IParam参数
);
如果将hWnd
参数指定为HWND_BROADCAST(0xFFFF)
,则会将消息发送到系统中的所有顶级窗口。我们需要根据消息类型构造wParam和lParam参数。函数的返回值是窗口过程中该消息的返回值。
注意,SendMessage()
函数实质是去调用指定窗口的窗口过程,并且在窗口过程处理完消息之前函数不会返回,即在窗口过程处理完指定的消息以后,Windows才把控制权交还给紧跟着SendMessage()
调用的下一条语句。
转义状态
在击键消息中,虚拟键码0x41~0x5A对应的是A~Z键,但是,通常认为按下A~Z键应该是小写字母a~z,
在按下字母键的同时按下了Shift键或Caps Lock
键,才认为是大写字母A~Z。例如:
case WM_KEYDOWN:
if (wParam == 'a')
{
MessageBox(hwnd,TEXT("小写字母a"),TEXT("提示"),MB_OK);
}
if (wParam == 'A')
{
MessageBox(hwnd,TEXT("大写字母A"),TEXT("提示"),MB_OK);
}
return 0;
上面的代码,切换为英文输入法,按下A键,会弹出第二个消息框。
还有其他情况,例如按下A键时,如果Ctrl键也被按下,那么就是Ctrl +A组合键,这是一个快捷键。键盘快捷键通常和程序菜单一起在程序的资源脚本文件中定义,Windows会把这些键盘快捷键转换为菜单命今消息,程序不必自己去做转换。
对于产生可显示字符的击键组合,Windows在发送击键消息的同时还发送字符消息。有些键不产生字符,如Shift键、功能键、光标移动键等,对于这些键,Windows只产生击键消息。
在处理击键消息时,我们可能需要知道是否有转义键(Shift键、Ctrl键和Alt键)或切换键(CapsLock键、NumLock键和
ScrollLock键)被按下。可以通过调用GetKeyState函数获取击键消息发生时一个按键的状态︰
SHORT WINAP GetKeyState(_In_ int nVirtKey); //nVirtKey参数指定虚拟键码
返回值反映了指定按键的状态。
- 如果高位为1,则指定按键为按下状态,否则指定按键为释放状态。
- 最低位反映了指定切换键的状态。如果低位为1,则指定切换键为已切换(打开),如果低位为0,则指定切换键为未切换(未打开)。
case WM_KEYDOWN:
if
(
(wParam == 'A' && GetKeyState(VK_SHIFT) < 0) ||
(wParam == 'A' && GetKeyState(VK_CAPITAL) & 1)
)
{
//GetKeyState(VK_SHIFT) < 0 => 总之,可以理解成返回值是负数的时候,表示被监视的键VK_SHIFT,正在被按着不放
// GetKeyState(VK_CAPITAL) & 1 => 该值低位位1,则表示CapsLock按下
MessageBox(hwnd,TEXT("大写字母A"),TEXT("提示"),MB_OK);
}
return O;
通常使用虚拟键码VK_SHIFT VK_CONTROLL和VK_MENU来调用GetKeyState()
函数,也可以使用虚拟键码VK_LSHIFT
VK_RSHIFT
VK_LCONTROL
VK_RCONTROL
VK_LMENU
或 VK_RMENU
来调用GetKeyState()
函数,用于确定是左侧还是右侧的Shift键、Ctrl键或Alt键被按下。
注意,GetKeyState()
函数并非实时检测键盘状态,它只是反映了到目前为止的键盘状态,用于确定击键消息发生时一个按键的状态。假设需要确定用户是否按下了Shift + Tab
组合键,可以在处理Tab键的WM_KEYDOWN消息时,调用包含VK_SHIFT参数的GetKeyState()
函数。如果GetKeyState函数的返回值是负的,就可以确定在按下Tab键之前已经按下了Shift键。
如果需要确定某个按键的实时状态,可以使用GetAsyncKeyState()
函数∶
SHORT WINAPl GetAsyncKeyState(_ln_ int vKey);
在实际开发中,我们要根据这个函数定义一个宏KEY_DOWN
,这样可以方便以后的调用。
#define KEY_DOWN(VK_NONAME) ((GetAsyncKeyState(VK_NONAME) & 0x8000) ? 1:0)
关于他的取值,请看下表
常量名 对应按键 取值
—————————————————————————————————————————————————————————
VK_LBUTTON 鼠标左键 0x01
VK_RBUTTON 鼠标右键 0x02
VK_CANCEL Ctrl + Break 0x03
VK_MBUTTON 鼠标中键 0x04
VK_BACK Backspace 键 0x08
VK_TAB Tab 键 0x09
VK_RETURN 回车键 0x0D
VK_SHIFT Shift 键 0x10
VK_CONTROL Ctrl 键 0x11
VK_MENU Alt 键 0x12
VK_PAUSE Pause 键 0x13
VK_CAPITAL Caps Lock 键 0x14
VK_ESCAPE Esc 键 0x1B
VK_SPACE 空格键 0x20
VK_PRIOR Page Up 键 0x21
VK_NEXT Page Down 键 0x22
VK_END End 键 0x23
VK_HOME Home 键 0x24
VK_LEFT 左箭头键 0x25
VK_UP 上箭头键 0x26
VK_RIGHT 右箭头键 0x27
VK_DOWN 下箭头键 0x28
VK_SNAPSHOT Print Screen 键 0x2C
VK_Insert Insert 键 0x2D
VK_Delete Delete 键 0x2E
'0' – '9' 数字 0 - 9 0x30 - 0x39
'A' – 'Z' 字母 A - Z 0x41 - 0x5A
VK_LWIN 左WinKey(104键盘才有) 0x5B
VK_RWIN 右WinKey(104键盘才有) 0x5C
VK_APPS AppsKey(104键盘才有) 0x5D
VK_NUMPAD0 小键盘 0 键 0x60
VK_NUMPAD1 小键盘 1 键 0x61
VK_NUMPAD2 小键盘 2 键 0x62
VK_NUMPAD3 小键盘 3 键 0x63
VK_NUMPAD4 小键盘 4 键 0x64
VK_NUMPAD5 小键盘 5 键 0x65
VK_NUMPAD6 小键盘 6 键 0x66
VK_NUMPAD7 小键盘 7 键 0x67
VK_NUMPAD8 小键盘 8 键 0x68
VK_NUMPAD9 小键盘 9 键 0x69
VK_F1 - VK_F24 功能键F1 – F24 0x70 - 0x87
VK_NUMLOCK Num Lock 键 0x90
VK_SCROLL Scroll Lock 键 0x91