• Programming Windows 第五版读书笔记 第三章 窗口和消息


    Programming windows 5th Edition Chapter 3 窗口和消息

    1. 本章讲述了一个最简单的带窗口的windows程序HelloWin。

    2. 窗口类别。在创建窗口(调用CreateWindow)之前,需要先注册窗口类别(RegisterClass)。所谓窗口类别表示的是窗口大体应该遵循 的共性,比如按钮窗口,他们的类别是一样的,比如光标,背景,最关键的是消息处理函数,这些东西都应该是一样的,至于每个按钮窗口不同的地方,比如大小, 位置等,放在CreateWindow中由我们来定义。所以窗口类别就是抽象了窗口的一些共性的东西,多个窗口在CreateWindow的时候,可以共 享一个窗口类别,也就是只需执行一次RegisterClass。窗口类别中最重要的就是定义了消息CALLBACK函数了,也就是消息处理函数了。看到 这里有人会担心了,既然窗口类别中定义了消息处理函数,那么,如果一堆窗口创建的时候都是用的一个窗口类别的话,那这些窗口的消息处理函数就都是一样的 喽?--没错,OK,那既然一样,我怎么对这些窗口的同一个消息做不同的处理呢?--很简单,在消息处理函数中,有一个参数hWnd,用来标识了窗口的句 柄(也就是CreateWindow返回的东西),用这个就可以区分不同的窗口了。

    3. 来看本章HelloWin的代码:

    Code: Select all
    /*------------------------------------------------------------
       HELLOWIN.C -- Displays "Hello, Windows!" in client area
                     Eric Zhang 2007
      ------------------------------------------------------------*/

    #include <windows.h>

    LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

    int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                        PSTR szCmdLine, int iCmdShow)
    {
         static TCHAR szAppName[] = TEXT ("HelloWin") ;
         HWND         hwnd ;
         MSG          msg ;
         WNDCLASS     wndclass ;

         wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
         wndclass.lpfnWndProc   = WndProc ;
         wndclass.cbClsExtra    = 0 ;
         wndclass.cbWndExtra    = 0 ;
         wndclass.hInstance     = hInstance ;
         wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;
         wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
         wndclass.hbrBackground = (HBRUSH) GetStockObject (BLACK_BRUSH) ;
         wndclass.lpszMenuName  = NULL ;
         wndclass.lpszClassName = szAppName ;

         if (!RegisterClass (&wndclass))
         {
              MessageBox (NULL, TEXT ("This program requires Windows NT!"),
                          szAppName, MB_ICONERROR) ;
              return 0 ;
         }
         hwnd = CreateWindow (szAppName,                  // window class name
                              TEXT ("The Hello Program"), // window caption
                              WS_OVERLAPPEDWINDOW,        // window style
                              CW_USEDEFAULT,              // initial x position
                              CW_USEDEFAULT,              // initial y position
                              CW_USEDEFAULT,              // initial x size
                              CW_USEDEFAULT,              // initial y size
                              NULL,                       // parent window handle
                              NULL,                       // window menu handle
                              hInstance,                  // program instance handle
                              NULL) ;                     // creation parameters
         
         ShowWindow (hwnd, iCmdShow) ;
         UpdateWindow (hwnd) ;
         
         while (GetMessage (&msg, NULL, 0, 0))
         {
              TranslateMessage (&msg) ;
              DispatchMessage (&msg) ;
         }
         return msg.wParam ;
    }

    LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
         HDC         hdc ;
         PAINTSTRUCT ps ;
         RECT        rect ;
         
         switch (message)
         {
         case WM_CREATE:
              PlaySound (TEXT("hellowin.wav"), NULL, SND_FILENAME | SND_ASYNC) ;
              return 0 ;

         case WM_PAINT:
              hdc = BeginPaint (hwnd, &ps) ;
             
              GetClientRect (hwnd, &rect) ;
             
              DrawText (hdc, TEXT ("Hello, Windows!"), -1, &rect,
                        DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;
              EndPaint (hwnd, &ps) ;
              return 0 ;
             
         case WM_DESTROY:
              PostQuitMessage (0) ;
              return 0 ;
         }
         return DefWindowProc (hwnd, message, wParam, lParam) ;
    }


    4. HelloWin很简单,就是建立了一个窗口,在中央位置写上文字,另外在窗口显示的时候会播放一个wav文件。注:为了播放这个wav文件,代码中调用了PlaySound函数,要使用这个函数在Project中记得将WinMM.lib文件链接进去。

    5. 现在来看代码。首先代码中有很多常量,比如CS_HREDRAW, WM_PAINT等,这些常量不用记忆,需要的时候查手册即可,这些常量都有特定的意义和一些约定俗成的书写格式:

    附件1

    除此以外,代码中还有一些其他的数据类型,如下:

    附件2 附件3

    附 件3中列的都是句柄,只是指向不同东西的句柄而已。在windows中,句柄使用非常普遍,就好像是Linux编程中的文件描述符一样。windows中 的句柄就是一个32位的整数,用来代表一个对象而已。很多windows函数都需要句柄,这样windows函数才知道我们操作的对象是谁。

    6. 匈牙利标记法。代码中的变量名称基本上都使用了匈牙利标记法,其实第一章中的程序开始就已经开始使用了,这里做了一个总结,以后写代码可以参考:

    附件4

    7. 现在来看代码。首先定义了一个函数原型,也就是我们的消息处理函数的原型。LRESULT就是long型;CALLBACK和WINAPI一样,就是 __stdcall,加上CALLBACK,windows将来调用我们的消息处理函数的时候,免得发生call convention不一致的问题,所以一般都要加这个CALLBACK;WndProc的四个参数中,第一个参数是窗口句柄,第二个参数是消息代码,第 三个参数和第四个参数是两个parameter,wParam在32位windows下,就是UINT -- unsigned int,lParam就是long型,在16位windows下,wParam原来是WORD -- unsigned short类型。所以,按照匈牙利表示法,wParam应该写成uiParam,但由于原来16位windows下是wParam,所以就没有修改保留了 下来。

    8. 然后的代码就是注册窗口类别了。注册窗口类别调用RegisterClass,这个函数只需要一个参数,就是窗口类别structure WNDCLASS。由于这个WNDCLASS中含有两个字符串成员变量,所以回想起第二章的内容,自然这个WNDCLASS就有WNDCLASSA和 WNDCLASSW两个版本了。而这个WNDCLASS中的字符串自然就是 T 类型的字符串了。OK,下面来看这个Structure中的内容:

    style -- 指出窗口的风格。代码中写的是CS_HREDRAW|CS_VREDRAW,这表示窗口在横向或纵向发生变化的时候,重绘窗口。后面会看到,这就是为什么 窗口大小改变后,文字依然会显示在窗口的正中,就是因为这里的设定,窗口重绘,触发WM_PAINT消息。在WINUSER.H中可以找到全部的CS打头 的(表示窗口类别样式)的常量。注意观察这些常量的数值,其实他们每个数值都将二进制数中的某一位置成了1,所以,他们之间可以互相用 或 符号连接起来。

    lpfnWndProc -- 这是最重要的了,指定消息处理函数,这是一个函数指针。

    cbClsExtra, cbWndExtra -- 预留的空间,如果需要,程序可以自定义这部分内容。

    hInstance -- 就是我们这个程序的实例句柄,WinMain的第一个参数

    hIcon -- 指定窗口图标,该图标出现在窗口标题栏的最左边和任务栏中

    hCursor -- 指定鼠标的光标。这里,LoadIcon和LoadCursor,如果第一个参数是NULL,就表示使用windows预定义好的那些图标和鼠标光标。具 体看这两个函数的MSDN;如果我们要使用自己的图标或光标,那么,第一个参数要设置成hInstance,也就是我们这个程序的实例句柄。

    hbrBackground -- 窗口的背景。hbr表示handle to a brush。windows中的brush表示用来填充一个区域的着色样式。Windows有几个标准的brush,他们称为Stock Brush(库存画刷)。所以我们用GetStockObject得到了一个白色画刷。

    lpszMenuName -- 指定应用程序菜单

    lpszClassName -- 窗口类别名称。这里的内容要和CreateWindow中的第一个参数一样,如果我们创建的这个window想使用这个窗口类别的话。

    9. OK,一切具备,调用RegisterClass了,这里代码做了一个出错处理判断,这是应该的,因为RegisterClass也有A/W两个版本,如 果我们定义了UNICODE条件编译变量,那么将来被调用的将是RegisterClassW,而win98虽然有这个函数,但是一调这个函数win98 会立刻返回错误(win9x只有很小一部分支持Unicode,比如MessageBoxW是可以用的)。

    BTW: 对于出错的处理,很多时候我们会调用GetLastError函数,这个函数能返回上次操作失败的错误码,这些错误码对应的含义可以在MSDN的 /Platform SDK/Windows Base Services/Debugging and Error Handling/Error Codes/System Errors - Numerical Order中找到

    10. 然后就看到CreateWindow函数了。代码中有注释,就不一项一项说了。只有第二个参数,CW_OVERLAPPEDWINDOW,这个常量是一批常量的合集(定义在WINUSER.H中):

    Code: Select all
    #define WS_OVERLAPPEDWINDOW (WS_OVERLAPPED     | \
                                 WS_CAPTION        | \
                                 WS_SYSMENU        | \
                                 WS_THICKFRAME     | \
                                 WS_MINIMIZEBOX    | \
                                 WS_MAXIMIZEBOX)


    用来表示窗口的表现形态。

    11. 然后代码调用了ShowWindow,用来显示窗口,然后又调了UpdateWindow,这个函数会触发WM_PAINT消息,用来初始化窗口中的图形和文字。

    其 实我自己试了一下,发现不调用UpdateWindow也照样有WM_PAINT消息的产生,窗口也照样能正常显示。原来我以为这里调用 UpdateWindow只是为了规范,可是后来通过下一章的学习,发现这里调用UpdateWindow是有道理的。道理就在于 UpdateWindow是产生WM_PAINT消息,但是不同的是,这次产生的WM_PAINT消息是非入队消息,也就是说,调用这个函数 后,windows会立马调用我们的消息处理函数,直到这个消息处理完毕,UpdateWindow才返回。如果不使用这个函数,一般情况下产生的 WM_PAINT消息只会排在消息队列中,而且windows约定WM_PAINT消息是一个优先级低的消息,所以,这样会导致界面更新不及时--简言 之,当我们需要窗口界面立即更新的时候,请使用UpdateWindow,在很多地方都可以使用。

    12. 然后就进入消息循环了,用GetMessage从消息队列中取出消息,注意:windows中不是什么消息都会进消息队列的,有些消息是不进消息队列的, 此时windows会直接调用消息处理函数,这些消息叫非入队消息。不过我们不需要关心这些复杂的问题,我们只需要知道--任何消息都会在我们的消息处理 函数中被处理。GetMessage中MSG的结构是这样的:

    Code: Select all
    typedef struct tagMSG
    {
         HWND   hwnd ;
         UINT   message ;
         WPARAM wParam ;
         LPARAM lParam ;
         DWORD  time ;
         POINT  pt ;
    } MSG, * PMSG ;


    GetMessage函数的参数含义可以看MSDN,第二个参数为NULL表示接受本进程创建的所有窗口的消息;第三个参数和第四个参数表示过滤 消息的min和max值,也就是说,消息编号在这个区间内的才会被GetMessage抓下来,如果这两个值都是0,那就是没有消息过滤。 GetMessage函数会一直返回非0的数,除非取到了WM_QUIT消息。

    MSG结构中,hwnd是消息发生窗口句柄,message是消息编号,wParam,lParam是消息参数,可以在这里取到消息的详细内容,time是发生时间,PT是发生该消息的时候鼠标所在的坐标。

    13. TranslateMessage是把msg传给windows,进行一些键盘转换。这一点会在第六章深入讨论;DispatchMessage是把 msg传回给windows,然后windows就会调用我们的消息处理函数(WndProc),所以对于入队消息,是由我们程序手动 GetMessage,然后再派发的,非入队消息不通过GetMessage,直接由windows调用消息处理函数。

    14. 对于消息循环和消息处理。有几点需要重视:

    1. DispatchMessage将消息派发出去后,会一直等到这个消息被处理完(消息处理函数返回了),本函数才会返回,GetMessage才会被执行去取下一条消息

    2. 在消息处理函数中,也是一样,如果在消息处理过程中触发了其他的消息,那么触发消息的代码也会block,一直到被触发出来的新消息被处理完为止。

    3. 消息处理函数必须return 0,除非是return DefWindowProc。

    15. 下面的代码就是消息处理函数了,有这么一些要点:

    WM_CREATE消息会在CreateWindow执行过程中产生

    对于WM_PAINT消息的处理,基本上都是从BeginPaint开始,以EndPaint结束。因为BeginPaint会返回一个hdc, 有了这个hdc,我们才能在窗口上写字,绘图。而且BeginPaint会填充一个PAINTSTRUCTURE,能告诉我们哪些地方需要重绘 (invalid)。在BeginPaint中,如果显示区域背景没有被删除,则由windows来删除,windows根据注册窗口类别中的背景来擦除 显示区域。如果我们的消息处理函数不处理WM_PAINT,那么DefWindowProc只是简单的调用BeginPaint和EndPaint来使显 示区域重新生效。

    WM_DESTROY消息相应中我们调用了PostQuitMessage函数,该函数 会发出WM_QUIT消息,GetMessage碰到这个消息会返回0,从而WinMain中的消息循环结束。参数0会被设置到该消息的wParam,所 以我们在WinMain中return了msg.wParam

    有时候,DefWindowProc处理完消息后会产生其它的消息。例如,假设使用者执行HELLOWIN,并且使用者最终单击了 Close按钮,或者假设用键盘或鼠标从系统菜单中选择了 Close, DefWindowProc处理这一键盘或者鼠标输入,在检测到使用者选择了Close选项之后,它给窗口消息处理程序发送一条 WM_SYSCOMMAND消息。WndProc将这个消息传给DefWindowProc。DefWindowProc给窗口消息处理程序发送一条 WM_CLOSE消息来响应之。WndProc再次将它传给DefWindowProc。DestroyWindow呼叫DestroyWindow来响 应这条WM_CLOSE消息。DestroyWindow导致Windows给窗口消息处理程序发送一条WM_DESTROY消息。WndProc再呼叫 PostQuitMessage,将一条WM_QUIT消息放入消息队列中,以此来响应此消息。这个消息导致WinMain中的消息循环终止,然后程序结 束。
  • 相关阅读:
    普通文本(.txt)篇章排版样式参考 [文档说明][日志]
    C++基础练习案例
    Markdown 编写技巧汇总(二)
    Markdown 编写技巧汇总(一)
    有关C++程序设计基础的各种考题解答参考汇总
    有关算法与数据结构的考题解答参考汇总 [C++] [链表] · 第三篇
    有关算法与数据结构的考题解答参考汇总 [C++] [链表] · 第二篇
    有关算法与数据结构的考题解答参考汇总 [C++] [链表]
    DOS批处理小案例应用分享
    基于Processing图像序列处理保存导出的流程梳理
  • 原文地址:https://www.cnblogs.com/super119/p/2011323.html
Copyright © 2020-2023  润新知