zoukankan      html  css  js  c++  java
  • 图解win32应用程序框架

    如果把操作系统比喻成一个生产汽车的公司;应用程序在内存的那块空间比喻成为一个汽车工厂的厂房;窗口类WNDCLASS比喻成为图纸(或生产汽车用到的部件的清单);窗口Window比喻成为具体的汽车(如奥迪A6)。则整个生产汽车的过程(建立窗体的过程)如下图:

    需要注意的难点是:如果注册多个窗口类并且每个窗口类都对应一个自己的窗口函数,则相当于注册了多个窗口函数(全局的函数)到操作系统,那么while消息循环中DispatchMessage(&msg); 这一句话会根据hWnd参数即窗口句柄把对应的msg发送都对应的窗口函数(至于怎么对应的,应该是hWnd找到那个窗口对象,而每个窗口对象又都是根据某个注册的窗口类来创建的,那么每个窗口对象里面应该保存有对应窗口类的名称,也就是上面图中清单的名称,所以更具hWnd就能找到那个窗口类即清单,找到了清单自然就能找到对应的窗口函数了),所以hWnd参数是很重要的。关于这个问题我已经测试过了。

    总之:可以简单的认为hWnd参数和某个窗口函数对应或关联,hWnd确定了,则DispatchMessage函数就能够转向某个窗口函数了。

    下面解释新建一个空的Win32应用程序书写代码的步骤:

    首先,exe应用程序由操作系统调用进入内存,再给应用程序分配好内存后,操作系统给这个应用程序一个HINSTANCE句柄,然后操作系统调用应用程序的winmain函数开始执行。

    winmain函数声明如下:

    #include <windows.h>    //windows用到的相关头文件,编写win32程序必须包含这个头文件

    #include<stdio.h>  //C语言的库函数所在的头文件,可能用到C语言的库函数,所以要包含进去

    int WINAPI WinMain(HINSTANCE hInstance,      
                         HINSTANCE hPrevInstance,
                         LPTSTR    lpCmdLine,
                         int       nCmdShow)

    这个winmain函数是由操作系统调用的,所以里面的参数也都是由操作系统赋值的。其中hInstance指标志这个应用程序的句柄;hPrevInstance标志应用程序前一个应用程序的句柄,在win32下一般规定就是0;lpCmdLine是用户执行应用程序时传的命令行参数,比如dos下执行1.txt,则这个1.txt就是命令行参数,根据这个参数打开或新建对应的文件;nCmdShow指程序运行的主窗口显示的状态,是最大化、最小化还是隐藏显示。

    第二,注册窗口类和创建具体的窗口

    如果把产生一个窗体的过程比喻成生产一辆汽车,则生产这辆汽车需要地盘、轮子等部件,还可能需要汽车工程师等人员来装配。而窗口类WNDCLASS就是产生这辆汽车需要所有物件的清单,它描述了需要哪些部件才能生产出这辆汽车(也就是产生这个窗口)。在这个清单中,最重要的部件就是窗口函数的指针和应用程序实例句柄。

    (1)窗口类的定制和注册,就是把清单中需要的具体部件都拿过来(或都产生出来,包括图标、光标、画刷等资源,这些部件可以被共享的,节约了内存)。

    下面是把注册窗口类的所有过程封装在了一个函数MyRegisterClass中:

    void MyRegisterClass(HINSTANCE hInstance) {  

         WNDCLASS wc;  //定义一个窗口类的实例,即一个空清单

         wc.style   = CS_HREDRAW | CS_VREDRAW;  //窗口类的样式,都是全局的宏定义,定义在WinUser.h中,这个样式是指较全局话的样式,比如是豪华车型还是普通车型,而不是指具体的车型如奥迪A6

         wc.lpfnWndProc = WndProc; //窗口函数的首地址记录下来

         wc.cbClsExtra  = 0;  //给这个窗口类的实例分配多大的扩展或保留内存,一般为0

         wc.cbWndExtra  = 0;  //给以后生成的每个具体窗口的实例分配多大的扩展或保留内存,一般为0

         wc.hInstance  = hInstance;  //本窗口对应的应用程序的句柄是多少

         wc.hIcon   = LoadIcon(NULL/*或hInstance*/, IDI_APPLICATION);  //图标资源,其中hInstance是winmain中传来的句柄,为null时表示使用默认的标志图标,IDI_APPLICATION是全局的宏定义

         wc.hCursor  = LoadCursor(NULL, IDC_ARROW);  //同上

         wc.hbrBackground = (HBRUSH)GetStockObject(DKGRAY_BRUSH);  //背景画刷,DKGRAY_BRUSH是全局宏定义

         wc.lpszMenuName = NULL;  //菜单名,NULL表示不用菜单

         wc.lpszClassName = "某个窗口类"; //这个窗口类实例的名字,标记这个窗口类的实例  

         RegisterClass(&wc); //窗口类实例的注册

    }

    窗口类实例的注册是在一个专门的内存区域进行备案,以后随时根据窗口类实例的名字挑选出来使用,用如下图可以便于理解:

    (2)窗体的创建

    HWND CreateWindow(

      LPCTSTR lpClassName,  //安装清单的名称,即窗口类实例的名称

      LPCTSTR lpWindowName,  //窗口实例的标题

      DWORD dwStyle,  //窗口实例的样式,具体的样式,具体的车型,比如奥迪A6

      int x,  //窗口左上角坐标值x

      int y,  //窗口左上角坐标值y

      int nWidth,  //窗口的宽度

      int nHeight,  //窗口的高度

      HWND hWndParent,  //父窗口的句柄

      HMENU hMenu,  //主菜单的句柄

      HANDLE hInstance,  //应用程序实例句柄,和窗口类实例一样都有

      LPVOID lpParam //WM_Create消息用到的附加参数,一般只在mfc中用

    );

    (3)窗口的显示

    bool ShowWindow(

        HWND hwnd, //窗口实例的句柄

        int nCmdShow  //窗口的显示方式,最大化等

    );

    bool UpdateWindow(

        HWND hWnd  //窗口实例的句柄

    );

    第三、消息循环

    消息循环是属于整个应用程序的(所以放在main函数中),因为消息循环要从消息队列中取消息,而消息队列是属于整个应用程序的。

    消息结构的定义如下:

    typedef struct tagMSG{

      HWND hwnd;  //产生消息的那个窗口实例的句柄

      UINT message;  //消息的类型(即事件的类型)

      WPARAM wParam;  //消息的附加信息1,比如存放WM_CHAR消息时的对应按键的ASCII码

      LPARAM lParam;  //消息的附加信息2,具体功能还不知道

      DWORD time;  //消息被投递到应用程序队列的时间

      POINT pt;  //当消息被投递时,光标在屏幕上的位置

    }MSG;

    消息循环的代码如下,在main函数中:

    while(GetMessage(&,NULL,NULL,Null)){ //GetMessage函数只有收到WM_QUIT消息时才返回false

      TranslateMessage(&msg);  //对消息进行转换,如解释键盘消息,即当键盘按下时是WM_KeyDown消息,这个函数转换产生一个WM_CHAR消息并放入到应用程序消息队列中

      DispatchMessage(&msg);  //把消息路由给(发送给)操作系统,最后由操作系统负责调用具体的消息处理函数(窗口函数)

      //我猜测分发消息时,是根据msg中的产生消息的那个窗口实例的句柄找到对应的窗口类的实例(清单),再根据这个窗口类实例(清单)找到具体的窗口函数

    }

    下面讲讲GetMessage函数

    bool GetMessage(

      LPMSG lpMsg,  //装具体的消息

      HWND hWnd,  //窗口句柄,指示我要获取那个窗口的消息并装入到第一个参数lpMsg中,为NULL表示我要获取所有窗口的消息

      UINT wMsgFilterMin,  //消息范围的最小值,表示我要获取比这个值大的消息然后装入lpMsg中

      Uint wMsgFilterMax  //消息范围的最大值,如果最小和最大范围都设为0则表示不设置范围,获取所有范围的消息。

    );

     第四、窗口过程(函数)

    LRESULT CALLBACK WndProc_XXX(  //LRESULT是个LONG型,CALLBACK是_stdcall调用约定。 下面的四个参数其实就是消息结构体MSG的前四个参数(去掉了时间和光标当前位置)

      HWND hwnd,   //窗口句柄,由操作系统赋值

      UINT uMsg,  //消息类型,由操作系统赋值

      WPARAM wParam,  //消息附加参数,由操作系统赋值

      LPARAM lParam  //同上

    )

    {

      HDC hdc;

      PAINTSTRUCT ps;

      switch (uMsg)

      {

        case WM_PAINT:  //窗体重绘消息。如果在这里设置一个断点,则由于窗口从无到有都执行到这里,所以窗口就永无法显示出来

          hdc=BeginPaint(hwnd,&ps);  //所有的DC都是和窗口相关的

          TextOut(hdc,0,0,"初始显示的文字",strlen("初始显示的文字"));

          EndPaint(hwnd,&ps); //和BeginPaint是一对。另外BeginPaint函数只能用在WM_PAINT窗口重绘消息中,不能用在其他消息中

          break;

        case WM_CHAR:

          char szChar[20];

          spintf(szChar,"char is %d",wParam);  //格式化字符串到内存szChar中

          MessageBox(hwnd,szChar,"标题",MB_OK);

          break;

        case WM_LBUTTONDOWN:

          MessageBox(hwnd,"您按下了鼠标左键","标题",MB_OK);

          HDC hDC;  //看作是一个和具体窗口关联了的画布

          hDC=GetDC(hwnd);

          TextOut(hDC,0,50,"画出了一些文字",strlen("画出了一些文字"));  //在画布hDC上画文字

          ReleaseDC(hwnd,hDC); //和GetDC是一对。另外GetDC函数只能用在其他消息中,不能用在WM_PAINT窗口重绘消息中

          break;   

        case WM_CLOSE:

          if(IDYES==MessageBox(hwnd,"是否退出程序?","标题",MB_YESNO));

          {

            DestroyWindow(hwnd); //这个函数会销毁窗口并发送一个WM_DESTROY和WM_NCDESTROY消息,从而转到下面的case语句

          }

          break;

        case WM_DESTROY:

          PostQuitMessage(0);  //向消息队列中投递一个WM_QUIT消息,从而是while循环退出。参数0是作为WM_QUIT消息的附加参数,意义不大

          break;

        default:

          return DefWindowProc(hwnd,uMsg,wParam,lParam);  //对其他的喜爱系进行缺省处理,不可缺少,否则窗口可能不显示出来。

       }

      return 0;

    }

  • 相关阅读:
    在360工作的这几天
    ISBN号码
    poj 3667 Hotel
    命令模式之2 Invoker Vs. Client
    vehicle time series data analysis
    JSON之三:获取JSON文本并解释(以google的天气API为例)
    创建型模式--工厂方法模式
    OpenStack Heat总结之:Icehouse中通过Heat+Ceilometer实现Autoscaling
    MyEclipse10 中增加svn插件
    activitie用户手册
  • 原文地址:https://www.cnblogs.com/hnfxs/p/3133405.html
Copyright © 2011-2022 走看看