上一讲:
Windows编程系列——第四讲:GDI
Windows编程系列——第五讲:与窗口“交互”
学了上一讲,你可能觉得不过瘾:只是单向的从屏幕上输出“Hello World”而已嘛,又不能输入。别急,这一讲就会讲到,如何在窗口上“打字”。
按键消息
要想完成“交互”,首先要知道按键消息。当你按下一个键时,Windows会向获得输入焦点(
InputFocus
)的那个窗口的消息队列插入一个WM_KEYDOWN或WM_SYSKEYDOWN消息;当释放这个键时,Windows会插入一个WM_KEYUP或WM_SYSKEYUP消息。
现在的问题是按键怎么被识别的?比如说我按下一个 “w” , 程序怎么知道是 “w” 而不是 “q” ? 这就引出来一个叫做“虚拟键码”的东东。可以这样理解,虚拟键码是连接
物理键盘
和
各种字符
(包括打印字符和控制字符)的桥梁。以后用到虚拟键码表,可以查阅
这篇博客
。
系统向消息队列插入的按键信息中,wParam参数就用来传递按键的虚拟键码,这样就知道是哪个键了(可以回顾一下第三讲的MSG结构体和第二讲的窗口函数形参,都有提到wParam)。而lParam则用来包含按键的其他消息。关于这两个参数的详细含义不展开讲了,有需要的话可以查阅技术文档或相关博客。
目前只需要记住处理按键消息时,wParam中包含了虚拟键码。
我们第三讲提到过:
TranslateMessage(&msg)用于把键盘输入转换成字符消息,例如把WM_KEYDOWN和WM_KEYUP转换成WM_CHAR
也就是说,按键消息还要进行一步转换,变成
字符消息
。
当转换成字符消息后,wParam里面存储的值也就变了,变成了按键的ANSI码(Windows下的扩展ASCII码)
。例如当用户按一次“A”键(按下、释放为一次),程序会按照顺序收到(产生)以下三个消息:
- 首先是WM_KEYDOWN消息,由Windows插入到消息队列
- GetMessage函数取出该消息,TranslateMessage函数将该消息转换成WM_CHAR消息
- 最后是WM_KEYUP消息,由Windows插入消息队列
我们在实际使用的话,只会接触到字符消息层面的内容
,不用接触按键消息,讲上面这些是为了知道里面的执行细节。
强制刷新
除了用户对窗口的一些行为使客户区产生无效区域外,窗口函数也可以在需要的时候通过调用InvalidateRect迫使客户区的矩形无效。该函数原型如下:
BOOL InvalidateRect(
HWND hWnd,//窗口句柄
CONST RECT *lpRect,//无效矩形的坐标,NULL则代表刷新整个客户区
BOOL bErase//是否重新绘制背景
);
接下来我们会看到如何使用这个函数。
打字程序
我们的打字程序要实现以下三个功能:
- 能够获取按键的输入并保存在一个字符串中
- 能够把这个字符串显示在窗口中
- 能够通过退格键(backspace)删除最后一个字符
为此,设计步骤如下:
- 设计存储数据的字符串。由于每次调用窗口函数时都要使用这个字符串,所以最好是全局变量。同时为了兼容Unicode编码,采用wstring数据类型(相当于string)。注意使用wstring须包含头文件string,并使用命名空间std。
-
在
WM_CHAR字符消息
中对字符串进行更新,同时调用InvalidateRect对客户区进行刷新。 -
在WM_PAINT消息中对字符串进行绘制。
程序如下(只显示修改部分):
#include<string>
using namespace std;
//全局变量
wstring wstr;//接收来自键盘的字符
//...... ......
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CHAR://处理字符消息
{
switch (wParam)
{
case 8://8是退格对应的ASCII码
if (wstr.size() == 0)return 0;//如果字符串为空,则直接返回
wstr.erase(wstr.size() - 1, 1);//删除字符串最后一个字符
break;
default:
wstr += TCHAR(wParam);//获取的字符直接添加到字符串的最后面
}
InvalidateRect(hWnd, NULL, TRUE);//刷新窗口,注意此时还未打印接收的字符串
return 0;
}
//...... ......
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// TODO: 在此处添加使用 hdc 的任何绘图代码...
RECT r;
GetClientRect(hWnd, &r);
SetTextColor(hdc, RGB(255, 0, 0));//设置字体颜色红色
DrawText(hdc, wstr.c_str(), -1, &r, DT_WORDBREAK | DT_LEFT|DT_TOP);
EndPaint(hWnd, &ps);
}
break;
//...... ......
运行结果如图:
也可以输入中文:
设置字符串
现在讲一下上面程序中的SetTextColor函数,可以看到我们打印的字符颜色是红色,就是用这个函数改的。它的函数原型如下:
COLORREF SetTextColor(
HDC hdc,
COLORREF color
);
原型很简单,如果函数调用成功,则其返回一个代表前一个字体的颜色。而COLORREF是一个32位无符号整数,每8位分别代表红、绿、蓝的取值。为了使用方便,wingdi.h中定义了一个宏来直观地表示颜色:
RGB(r,g,b)
这样我们只需要传递代表三原色的0~255的值就可以了。例如RGB(0,0,0)代表黑色,RGB(255,255,255)代表白色,RGB(255,0,0)代表红色,RGB(0,255,0)代表绿色,RGB(0,0,255)代表蓝色。
其实不仅可以改变字体颜色,也可以修改文本的背景色。文本背景色与定义窗口类时设置的背景并不一样。窗口类中的背景是一个画刷,可以是一种纯色,也可以是一种图案,Windows用它来擦出客户区。在程序向显示器输出文本时,Windows使用文本背景色来填充字符周围的矩形空间(也称为字符框)。我们通过SetBkColor来设置文本背景色,原型如下:
COLORREF SetBkColor(
HDC hdc,
COLORREF color
);
同样的,如果函数成功,则返回代表前一种文本背景的颜色。
下一讲:
Windows编程系列——第六讲:GDI