Windows程序设计2(消息机制、菜单)

一 、小记;

  PostQuitMessage(0);

  产生WM_QUIT消息给进程队列,且立即返回,同时使得消息循环退出,使得进程终止。(其实它通过PostMessage(hWnd,WM_QUIT,0,0)发送消息)

  MoveWindow();//移动窗口

  BOOL  MoveWindow(

    HWND hWnd,      // 窗口句柄

    int X,          // 水平位置

      int Y,          // 垂直位置

      int nWidth,     // 宽度

      int nHeight,    // 高度

      BOOL bRepaint   // 重绘标识

    );

二 、消息机制

1.程序执行机制:

  过程驱动:程序的执行是有序的,按照预先预定义的顺序执行。

  事件驱动:程序执行是无序的,用户可以按照需要随机触发相应的事件。

  Win32窗口程序就是按照事件驱动方式执行,即消息机制。当系统通知窗口时,采用消息的方式派发给窗口。

  每个窗口都要有个消息处理函数。

  当系统通知窗口时,会调用窗口处理函数同时,将消息ID和消息参数传递给窗口处理函数。

  在窗口处理函数中,不处理的消息,使用缺省窗口处理函数,例如DefWindowProc 。

2.消息相关函数

  回调机制:程序员自己定义的函数,但交给操作系统调用。

  获取消息:

  BOOL GetMessage(

    LPMSG lpMsg, //存放获取到的消息BUFF

    HWND hWnd,   //窗口句柄

    UINT wMsgFilterMin, //获取消息的最小ID范围

    UINT wMsgFilterMax    //获取消息的最大ID范围

  );

  lpMsg - 当获取到消息后,将消息的参数存放到MSG结构中。

  hWnd - 获取hWnd所指定窗口的消息,若NULL,则获取所有消息。

  wMsgFilterMin和wMsgFilterMax -只能获取到由它们指定的消息范围内的消息,如果都为0,表示没有范围。

  若接受的消息为WM_QUIT,则返回值为0,则退出消息循环。

  GetMessage - 从系统获取消息,将消息从系统中移除,阻塞函数。当系统无消息时,GetMessage会等候下一条消息。

  PeekMessage - 以查看的方式从系统获取消息,可以不将消息从系统移除,非阻塞函数。当系统无消息时,返回FALSE,继续执行后续代码。

  使用PeekMessage()可以在不锁住我们的程序的前提下对消息进行检查。许多程序使用GetMessage(),也可以很好的工作。但使用GetMessage(),程序在收到paint消息或其他别的什么窗口消息之前不会做任何事。

  翻译消息:将按键消息,翻译成字符消息。

  BOOL TranslateMessage(

  CONST MSG *lpMsg //要翻译的消息地址

      );

  检查消息是否是按键的消息,若是,则产生一个字符消息。如果不是按键消息,不做任何处理,继续执行。

  派发消息:  将消息派发到该消息所属窗口的窗口处理函数上。

  DispatchMessage(&msg)

  {

    1. 获取消息是属于本进程哪个窗口(&msg)->hWnd;
    2. 根据窗口句柄获取内存中的信息 如:类名
    3. 将类名与操作系统中去匹配 窗口类名
    4. 若匹配,调用被匹配的哪个窗口类中存的函数(消息处理函数)。
    5. 将其中的4个参数一一传递给处理函数。

  }

3.常用Windows消息

  WM_DESTROY - 窗口被销毁时的消息,无消息参数。常用于在窗口被销毁之前,做相应的善后处理,例如资源、内存等。

  WM_SYSCOMMAND - 系统命令消息,当点击窗口的最大化、最小化、关闭等命令时,收到这个消息。

  常用在窗口关闭时,提示用户处理。

      wParam - 具体命令,例如关闭SC_CLOSE等.( wParam==SC_CLOSE)

    lParam - 鼠标位置

       LOWORD 低字 - 水平位置 (LOWORD(lParam))

    HIWORD 高字 - 垂直位置 (HIWORD(lParam))

  WM_CREATE - 在窗口创建成功还未显示之前,收到这个消息。

    常用于初始化窗口的参数、资源等等,包括创建子窗口等。

    WPARAM - 不使用,LPARAM - 是CREATESTRUCT结构的指针,保存了CreatWindowEx中的12个参数。若使用它,注意要强制转换类型。(CREATESTRUCT*)lParam。

  WM_SIZE --常用于窗口大小变化后,调整窗口内各个部分的布局

    WPARAM - 窗口大小变化的原因,LPARAM-变化窗口客户区的大小LOWORD - 变化后的宽度    (LOWORD(lParam))

    HIOWORD- 变化后的高度    (HIWORD(lParam))

  WM_QUIT - 用于结束消息循环处理。

    wParam - PostQuitMessage函数传递的参数,lParam -不使用。

    当GetMessage收到这个消息后,会返回FALSE,结束while处理,退出消息循环。

  WM_PAINT - 绘图消息

  键盘消息

  鼠标消息

  定时器消息

4.消息的发送(仅此两种,其他形式的发送消息都是基于这两种)

  SendMessage - 发送消息,会等候消息处理的结果。

  PostMessage - 投递消息,消息发出后立刻返回,不等候消息执行结果。

  LRESULT SendMessage / BOOL PostMessage(

    HWND hWnd,//消息发送的目的窗口

    UINT Msg, //消息ID

    WPARAM wParam, //消息参数

    LPARAM lParam  //消息参数

    );

5.消息的分类

1 系统消息 - ID范围 0 - 0x03FF (0~1023)

  由系统定义好的消息,可以在程序中直接使用。

2 用户自定义消息 - ID范围 0x0400 - 0x7FFF (31743个)

  由用户自己定义,满足用户自己的需求。由用户自己发出消息,并响应处理。

  自定义消息宏:WM_USER (其实已定义为0x0400)

3 应用程序消息 - ID范围 0x8000 - 0xBFFF

  程序之间通讯时使用的消息。

  应用程序消息宏:WM_APP

4 系统注册消息 - ID范围 0xC000 - 0xFFFF

  在系统注册并生成相应消息,然后可以在各个程序中使用这个消息。

用户使用自定义消息时:

  #define WM_MYMESG WM_USER+N  (0<N<=31743)

6.消息队列

  消息队列用于存放消息的一个队列,消息在队列中先入先出。所有窗口程序都具有消息队列。程序可以从队列中获取消息。

  消息队列的类型

    系统消息队列-由系统维护的消息队列。存放系统产生的消息,例如鼠标、键盘等。

    程序消息队列-属于每一个应用程序(线程)的消息队列。由应用程序(线程)维护。

  消息队列的关系

  1 当鼠标、键盘产生消息时,会将消息存放到系统消息队列

  2 系统会根据存放的消息,找到对应窗口的消息队列。

  3 将消息投递到程序的消息队列中。

  操作系统如何将一个消息正确转发?

  1. 将系统消息队列中的消息提取出来。
  2. 根据消息的hWnd,获取内存。
  3. 在内存中获取“Main”。
  4. 拿着“Main”去操作系统中去匹配窗口类名。
  5. 一旦匹配到窗口类,可以确定这窗口类的句柄g_hInstance。
  6. 将消息转发给相应的窗口程序的消息队列中。

7.消息和消息队列

  根据消息和消息队列之间使用关系,将消息分成两类:

  队列消息 - 消息的发送和获取,都是通过消息队列完成。

  非队列消息 - 消息的发送和获取,是直接调用消息的窗口处理完成。

  队列消息 -  消息发送后,首先放入队列,然后通过消息循环,从队列当中获取。

  GetMessage - 从消息队列中获取消息

  PostMessage - 将消息投递到(系统的)消息队列,后直接返回,不管对方是否获取消息并成功执行。以后交给操作系统转发给相应的窗口的消息队列,此后交给GetMessage()去获取。

  常见队列消息:WM_PAINT、键盘、鼠标、定时器。

  非队列消息 - 消息发送时,首先查找消息接收窗口的窗口处理函数,直接调用处理函数,完成消息,(不用排队,且无需Getmessage()去自己的消息队列去获取消息)。

  SendMessage - 直接将消息发送给窗口的处理函数,并等候处理结果。

  常见消息:WM_CREATE、WM_SIZE等。

如何找到相应的处理函数呢?(类似于DispatchMessage())

  1. 根据hWnd,找到内存。
  2. 在内存中获取 “Main”。
  3. 拿着“Main”在操作系统中去匹配窗口类。
  4. 匹配成功,具体的窗口类。
  5. return 那个窗口类中的处理函数(WndProc)

8.消息获取:GetMessage  /PeekMessage次序

  1 .在程序(线程)消息队列查找消息,如果队列有消息,检查消息是否满足指定条件(HWND,ID范围),不满足条件就不会取出该消息,否则从队列取出消息返回。

  2 .如果程序(线程)消息队列没有消息,向系统消息队列获取属于本程序的消息。如果系统队列的当前消息属于本程序,系统会将消息转发到程序

  3 .如果系统消息队列也没有消息,检查当前窗口的需要重新绘制的区域,如果发现有需要绘制的区域,产生WM_PAINT消息,取得消息返回处理。

  4 .如果没有重新绘制区域,检查定时器如果有到时的定时器,产生WM_TIMER,返回处理执行。

  5 .如果没有到时的定时器,整理程序的资源、内存等等。

  6 .GetMessage会继续等候下一条消息。PeekMessage会返回FALSE,交出程序的控制权。

  注意:GetMessage如果获取到是WM_QUIT,函数会返回FALSE。

9. WM_PAINT - 绘图消息

  WM_PAINT -- 当窗口需要绘制的时候,会发送窗口处理函数。

  窗口无效区域:

  被声明成需要重新绘制的区域。

  BOOL InvalidateRect(

   HWND hWnd,  //窗口句柄

         CONST RECT* lpRect,  //区域的矩形坐标

         BOOL bErase  //重绘前是否先擦除

    );

  在程序中,如果需要绘制窗口,调用函数声明窗口无效区域。

  WM_PAINT参数:WPARAM – 设备上下文,LPARAM - 不使用。

  消息处理步骤:

  1 开始绘图处理

    HDC BeginPaint(

      HWND hwnd, //绘图窗口

      LPPAINTSTRUCT lpPaint //绘图参数的BUFF

      ); 返回绘图设备句柄HDC

  2 绘图

  3 结束绘图处理

    BOOL EndPaint(

      HWND hWnd, //绘图窗口

      CONST PAINTSTRUCT *lpPaint  //绘图参数的指针BeginPaint返回

      );

10.键盘消息

  • 1 键盘消息

    WM_KEYDOWN - 按键被按下时产生

    WM_KEYUP - 按键被放开时产生

    WM_SYSKEYDOWN - 系统键按下时产生  比如ALT、F10等

    WM_SYSKEYUP - 系统键放开时产生

    WM_CHAR - 字符消息

  • 2 消息参数

  按键消息:

  WPARAM - 按键的Virtual Key

    LPARAM - 按键的参数,例如按下次数

  WM_CHAR消息:

  WPARAM - 输入的字符

    LPARAM - 按键的相关参数

  

  1  KEYDOWN可以重复出现,KEYUP只能在按键松开时出现1次。

  2  TranslateMessage在转换WM_KEYDOWN消息时,对于可见字符可以产生WM_CHAR,不可见字符无此消息。

  过程:

    1、先判断是否是WM_KEYDOWN消息(msg.message==WM_KEYDOWN)。若是进入2,否则返回。

    2、判断是否是可见字符(通过msg.wParam(虚拟键码),判断是否为可见字符按键),若不是,则返回。若是进入3。

    3、产生可见字符的WM_CHAR消息给操作系统,然后操作系统再转发给窗口的消息队列,再通过GetMessage()抓取,此时跳过TranslateMessage(),由DispatchMessage()分派消息给窗口的处理函数。(判断Caps Lock按键是否打开,根据Caps Lock按键的状态将虚拟键码转换为ASCII码)。(其实对于可见)

  3  WM_KEYDOWN/UP的wParam是表示的按键,WM_CHAR是表示输入的字符。

11.鼠标消息

1 基本鼠标消息

  WM_LBUTTONDOWN - 鼠标左键按下

  WM_LBUTTONUP - 鼠标左键抬起

  WM_RBUTTONDOWN - 鼠标右键按下

  WM_RBUTTONUP - 鼠标右键抬起

  WM_MOUSEMOVE - 鼠标移动消息

2 双击消息

  WM_LBUTTONDBLCLK - 鼠标左键双击

  WM_RBUTTONDBLCLK - 鼠标右键双击

3 滚轮消息

  WM_MOUSEWHEEL - 鼠标滚轮消息

基本鼠标消息:

  消息参数

    WPARAM - 其他按键的状态,例如Ctrl/Shift等

    LPARAM - 鼠标的位置,窗口客户区坐标系。

    LOWORD   X坐标位置

    HIWORD   Y坐标位置

  鼠标消息使用

    一般情况鼠标按下/抬起成对出现。在鼠标移动过程中,会根据移动速度产生一系列的WM_MOUSEMOVE消息。

双击消息:

  消息参数:

    WPARAM - 其他按键的状态,例如Ctrl/Shift等

    LPARAM - 鼠标的位置,窗口客户区坐标系。

    LOWORD X坐标位置

    HIWORD Y坐标位置

  使用时需要在注册窗口类的时候添加CS_DBLCLKS 风格,否则不会有(鼠标左右键)双击效果。

消息产生的先后顺序:

以WM_LBUTTONDBLCLK为例:

  WM_LBUTTONDOWN

  WM_LBUTTONUP

  WM_LBUTTONDBLCLK

  WM_LBUTTONUP

滚轮消息:

  消息参数

    WPARAM:

    LOWORD - 其他按键的状态

    HIWORD - 滚轮的偏移量,是120的倍数,通过正负值表示表示滚动方向。

      正:向前滚动

      负:向后滚动

    LPARAM:

      鼠标当前的位置,屏幕坐标系(注意不是当前窗口的坐标系)

      LOWORD - X坐标

      HIWORD - Y坐标

      通过偏移量,获取滚动的方向和倍数。

12.定时器消息:

定时器消息(WM_TIMER):

  可以在程序中设置定时器,当到达时间间隔时,定时器会向程序发送一个WM_TIMER消息。定时器的精度是毫秒,但是准确度很低。例如设置时间间隔为1000ms,但是会在非1000毫秒到达。

  消息的参数:

    WPARAM - 定时器ID

    LPARAM - 定时器处理函数的指针

定时器使用:

  创建定时器:

    UINT SetTimer(

      HWND hWnd,//定时器窗口句柄

      UINT nIDEvent, //定时器ID

      UINT uElapse,//时间间隔(毫秒为单位)

      TIMERPROC lpTimerFunc //定时器处理函数指针

      );创建成功,返回非0。

    若lpTimerFunc为NULL,则使用窗口处理函数做为定时器处理函数,否则使用定时器处理函数处理定时器消息。

  关闭定时器:

    BOOL KillTimer(

      HWND hWnd,//定时器窗口句柄

      UINT uIDEvent //定时器ID

      );

附:GetClientRect 获取窗口客户区大小.

RECT rect={0};

GetClientRect(hWnd,&rect);

定时器处理函数原型:

  VOID   CALLBACK   TimerProc(

      HWND   hwnd,     // 定时器窗口句柄

      UINT    uMsg,     // WM_TIMER消息

      UINT    idEvent,  //定时器ID

      DWORD  dwTime   // 自系统开启到当前流逝的时间(毫秒为单位)

    );

三、 菜单

1. 菜单的分类

1.1  窗口的顶层菜单

1.2  弹出式菜单

1.3  系统菜单

HMENU类型表示菜单窗口的顶层菜单,菜单每一项有相应的ID。

窗口的顶层菜单:

创建菜单:

  HMENU CreateMenu(VOID); //创建成功返回菜单句柄

增加菜单项

  BOOL AppendMenu(

    HMENU hMenu, //菜单句柄

    UINT uFlags, //菜单项风格

    UINT_PTR uIDNewItem, //菜单项ID

    LPCTSTR lpNewItem //菜单项的名称

    );

弹出式菜单 Popup:

创建菜单

  HMENU CreatePopupMenu(VOID);//创建成功返回菜单句柄

加入顶层菜单

  BOOL AppendMenu(

    HMENU hMenu, //菜单句柄

    UINT uFlags, //菜单项增加选项,此时为MF_POPUP

    UINT_PTR uIDNewItem, //弹出式菜单的句柄

    LPCTSTR lpNewItem //菜单项的名称

    );

注意:或是InsertMenu();

  MF_POPUP  -弹出下拉菜单或子菜单,uIDNewItem必须填写下拉菜单或子菜单句柄。

  MF_SEPARATOR  -分割线

  MF_STRING   -具有这种风格的菜单项,被点击后会发出消息(WM_COMMAND)

设置到窗口:

  BOOL SetMenu(

    HWND hWnd,  //窗口句柄

    HMENU hMenu //菜单句柄

    );

如:

  HMENU  hMain=CreateMenu();//创建菜单

  HMENU hFile=CreatePopupMenu();//创建弹出式菜单

  AppendMenu(hFile,MF_STRING|MF_CHECKED,1003,"新建");//新建前打钩

  AppendMenu(hFile,MF_SEPARATOR,0,"");//分割线

  AppendMenu(hFile,MF_STRING|MF_MENUBREAK,1004,"退出");//退出在弹出菜单中 下换列在一列

  HMENU hHelp=CreatePopupMenu();

  AppendMenu(hHelp,MF_STRING|MF_GRAYED,1005,"关于");//关于变灰,使其不可用

  AppendMenu(hMain,MF_POPUP,(UINT)hFile,"文件");

  AppendMenu(hMain,MF_POPUP,(UINT)hHelp,"帮助");

  SetMenu(hWnd,hMain);

菜单命令处理:

  WM_COMMAND 消息

    WPARAM:

      HIWORD(高16位) – 当对于菜单为0

      LOWORD (低16位)- 菜单项的ID

      LPARAM – 当对于菜单为NULL

命令处理过程

获取WPARAM菜单ID,根据ID处理。

菜单的使用

菜单项的状态

在增加菜单项可以设置菜单项的状态。

可以使用菜单API 修改状态

CheckMenuItem

EnableMenuItem

DWORD CheckMenuItem(

    HMENU hmenu,       // 菜单句柄

    UINT uIDCheckItem,   // 测试的菜单项

    UINT uCheck         // 菜单项标识

);

函数使用时,需要通过设置uCheck 为MF_BYPOSITION或者MF_BYCOMMAND,确定使用菜单索引或者菜单ID。若为MF_BYPOSITION,则uIDCheckItem应为相应菜单的索引序号,注意,此序号为以0开始,分割线也要计数的,另外使用了MF_MENUBREAK的那一列项计入在第二列,即从第一列计数完再第二列。若为MF_BYCOMMAND,则uIDCheckItem应为菜单的ID号。EnableMenuItem()相似用法。

  在菜单被激活但是未显示(比如点击下拉菜单时里面的菜单项),窗口会收到WM_INITMENUPOPUP消息

    WPARAM - 菜单句柄(即将显示出来的菜单句柄)

    LPARAM - LOWORD 是菜单项索引(被点击的菜单项的索引,从左至右自0开始的…)

         - HIWORD 是窗口菜单标识,(即是否为窗口菜单的标识,顶层菜单、系统菜单为窗口菜单,顶层菜单不是.)

窗口菜单:。。。

右键菜单Context Menu

1  创建菜单

  右键菜单是一个弹出式菜单(鼠标右键弹起触发,可以用WM_RBUTTONUP,但建议用WM_CONTEXTMENU更专业),使用CreatePopupMenu创建。

2  显示菜单

  BOOL TrackPopupMenu(

    HMENU  hMenu, //菜单句柄

    UINT   uFlags, //显示方式

    int  x, //水平位置,屏幕坐标系,非窗口

    int  y, //垂直位置,屏幕坐标系,非窗口

    int nReserved, //保留,必须为0

    HWND  hWnd, //处理菜单消息的窗口句柄

    CONST RECT  *prcRect  //NULL,忽略

    );

  TrackPopupMenu是阻塞函数。

  使用WM_RBUTTONUP时,由于获得的是窗口坐标参数,故窗口坐标需要转化为屏幕坐标如下。

  //客户区窗口坐标转变为Windows屏幕坐标

  BOOL ClientToScreen(

    HWND hWnd,       // 窗口句柄

    LPPOINT lpPoint  // 坐标(输入、输出同一参数),入为客户窗口,输出为屏幕

    );

  ///Windows屏幕坐标转变为客户区窗口坐标

    BOOL ScreenToClient(

        HWND hWnd,       // 窗口句柄

        LPPOINT lpPoint  // 坐标(输入、输出同一参数)(与上相反过程)

      );

  而使用WM_CONTEXTMENU时,不需要转换,参数已经为屏幕坐标系下的。

  WParam - 右键点击的窗口句柄

  LPARAM -  LOWORD X坐标,屏幕坐标系

     HIWORD Y坐标,屏幕坐标系

  WM_CONTEXTMENU消息是在WM_RBUTTONUP消息之后产生。

3   命令处理

  触发WM_COMMAND消息,与窗口菜单一致

  如果Track设置了TPM_RETURNCMD选项,那么被点击的菜单项ID通过函数的返回值获取,另外为阻塞函数且选择该选项,此时不会进入消息循环,返回值的菜单项ID可以处理其他事物。若没有该选项,不会返回被点击菜单项ID,且会进入消息循环。

4 菜单项的状态

  WM_INITMENUPOPUP,按照弹出菜单处理,用法同WM_INITMENUPOPUP消息,即点击右键激活但为显示的状态下。

系统菜单(比如窗口图标点击弹出的菜单)

1 系统菜单的获取

  HMENU GetSystemMenu(

    HWND hWnd,//窗口句柄

    BOOL bRevert //重置选项

    ); 返回获取到的系统菜单句柄

  bRevert:  TRUE - 删除旧菜单,恢复到默认的系统菜单

      FALSE - 返回当前的系统菜单句柄。一般下建议使用FALSE。

2 系统菜单的修改

  AppendMenu

  DeleteMenu (也可以删除其他的非系统菜单项)

  BOOL     DeleteMenu(

      HMENU   hMenu,     //菜单句柄

      UINT uPosition,  // 菜单选项或索引

      UINT  uFlags      // 菜单项标识

    );

  使用同CheckMenuItem()相似,

3 系统菜单命令响应

  通过WM_SYSCOMMAND消息响应菜单命令。

  WPARAM的LOWORD是命令ID,即点击的相应菜单项ID。

上一篇:【笨嘴拙舌WINDOWS】键盘消息,鼠标消息


下一篇:09 Windows编程——键盘消息