Windows编程系列——第五讲:与窗口“交互”

  • Post author:
  • Post category:其他


上一讲:

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//是否重新绘制背景
);

接下来我们会看到如何使用这个函数。

打字程序

我们的打字程序要实现以下三个功能:

  1. 能够获取按键的输入并保存在一个字符串中
  2. 能够把这个字符串显示在窗口中
  3. 能够通过退格键(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



版权声明:本文为Neo_kh原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。