窗口交互


***【在线视频教程】***

好文章,来自【福优学苑@音视频+流媒体】

按键消息

        要想完成“交互”,首先要知道按键消息。当你按下一个键时,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;
    //...... ......

运行结果如图:

image.png


设置字符串

        现在讲一下上面程序中的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

);


同样的,如果函数成功,则返回代表前一种文本背景的颜色。


好文章,来自【福优学苑@音视频+流媒体】
***【在线视频教程】***