关于消息机制的介绍

消息机制

  消息机制是Windows应用程序的核心。在Windows中发生的一切都可以用消息来表示,消息用于告诉操作系统发生了什么,所有的Windows应用程序都是消息驱动的,

  在Windows中,不同的消息由应用程序的不同部分进行处理。MFC库将很多底层的消息都屏蔽了,使用户更加方便、简易地处理消息。例如,用户接收到诸如移动鼠标键(WM_MOUSEMOVE)消息或单击鼠标键(WM_LRBUTTONDOWN)消息时不必处理窗口和鼠标的重画工作,MFC及应用程序框架会替用户做这些工作。在使用MFC进行编程时,用户只需处理一些高层的消息,例如,用户在单击窗口中的OK按扭用户现在选中了下拉列表框中的第五项等等,这样就大大减轻了程序员的负担。

  一个消息是由消息的名称(UINT)和两个参数(WPARAM, LPARAM)组成。消息的参数中包含有重要的信息。例如对鼠标消息而言,LPARAM中一般包含鼠标的位置信息,而WPARAM参数中包含了发生该消息时,SHIFTCTRL等键的状态信息,对于不同的消息类型来说,两个参数也都相应地具有明确意义。

  消息与输入焦点

  Windows是一个以消息为导向的系统,应用程序只能被动地等待用户按键的消息,不能主动地去读键盘的状态,也就是说,每当键盘上有个键被按下,系统就会发出一个按键消息给窗口,告诉它某个键被按下去了,只要鼠标移动一下,系统也会发出相应的消息,并把鼠标的坐标信息传给窗口。

  Windows可以同时执行许多程序,但键盘只有一个,怎么判断由哪个窗口接收键盘及鼠标的消息呢?采用输入焦点inpuut focus)技术可以解决这个问题。只要某个窗口取得输入焦点,它不但会被提升到屏幕的最前面,颜色也会有所不同,所有的键盘消息就会导向该窗口,该窗口也成为活动窗口

  窗口如何取得输入焦点?通常被鼠标单击的窗口会得到输入焦点,除此之外,程序本身也可以利用SetFocus()来指定哪个窗口拥有输入焦点。

  CWnd* CWnd::SetFocus();

  如果调用某窗口的SetFocus()成员函数,该窗口就可以取得输入焦点,该函数返回前一个拥有输入焦点的窗口。

  如果某个窗口的输入焦点被抢走,Windows系统就会发出WM_KILLFOCUS消息给这个失去输入焦点的窗口,同时还会告诉该窗口下一个取得输入焦点的窗口的指针。而获得输入焦点的窗口则会收到WM_SETFOCUS消息。

  消息响应函数分别为:

  afx_msg void OnKillFocus(CWnd* pNewWnd);

  其中的参数为得到输入焦点的窗口的指针。

  Afx_msg void OnSetFocus(CWnd* pOldWnd);

  其中的参数为失去输入焦点的窗口的指针。

  消息的分类

  Windows系统预定义了许多消息,每个消息都拥有一个宏定义,即用形象的字符串来标识消息,一系列#define 语句将消息与特定数值联系起来,可以在头文件WinUser.h中找到这些宏定义,例如

  #define WM_PAINT 120

  可以在程序中通过消息名“WM_PAINT”来访问它。其他消息如:

  #define WM_MOUSEMOVE 0x0200

  #define WM_LBUTTONDOWN 0x0201

  #define WM_LBUTTONUP 0x0202

  #define WM_LBUTTONDBLCLK 0x0203

  #define WM_RBUTTONDOWN 0x0204

  #define WM_RBUTTONUP 0x0205

  #define WM_RBUTTONDBLCLK 0x0206

  #define WM_MBUTTONDOWN 0x0207

  #define WM_MBUTTONUP 0x0208

  #define WM_MBUTTONDBLCLK 0x0209

  系统定义的消息有不同的前缀,不同的前缀有不同的含义。

  1. 标准的Windows消息

  除了WM_COMMAND消息,所有以WM_为前缀的消息都是标准的Windows消息,如窗口、鼠标移动、窗口大小改变等,程序启动或退出甚至每一段固定的时间都会产生标准Windows消息。如

  1        键盘消息

  对于窗口而言,来自用户的按键输入可分为两类,一类是系统键(system key),另一类则是非系统键。凡是ALT和其它键一同按下的组合称为系统键,窗口收到系统键之后,会自动地将它解释成系统事件,或者查阅键盘加速表,将系统键翻译成加速表指定的信息。如:ALT+F4的组合会迫使窗口关闭,“ALT+字母的组合可能会拉下某个菜单。

  当用户按下某个键时,Windows系统会先发出WM_KEYDOWN消息给窗口,这个消息的意思是按键被压下去。接着Windows系统会发出WM_CHAR给同一个窗口,这个消息代表的意义是系统送来某个字符,如果用户放开此键,Windows系统会发出WM_KEYUP消息,表示按键被放开。如果用户一直按住某个键不放,经过一段时间之后会产生连发的效果,造成Windows系统不停地发出WM_KEYDOWNWM_CHAR消息。

  计算机内部以ASCII码的规则来记录所有的英文字母和数字符号。不过不是键盘上每个按键都可以对应成ASCII码中的字符,如大小写键、CTRL键、F1F12键等。

  每个按键都有对应的扫描码,PC BIOS收到键盘的中断消息后,会自动将扫描码翻译成ASCII码,但有些控制键无法译成ASCII码,如Page UPPage Down等。Windows定义了一套与硬件无关的虚拟键码来表示键盘上所有的按键,如A键就是VK_AESC键就是VK_ESCF1键是VK_F1ALT键是VK_MENU等。因为虚拟键码定义的规则与硬件无关,所以有些虚拟键在通常的键盘上根本就找不着。

  #define VK_LBUTTON 0x01

  #define VK_RBUTTON 0x02

  #define VK_CANCEL 0x03

  #define VK_MBUTTON 0x04 /* NOT contiguous with L & RBUTTON */

  #define VK_BACK 0x08

  #define VK_TAB 0x09

  #define VK_CLEAR 0x0C

  #define VK_RETURN 0x0D

  #define VK_SHIFT 0x10

  #define VK_CONTROL 0x11

  #define VK_MENU 0x12

  #define VK_PAUSE 0x13

  #define VK_CAPITAL 0x14

  #define VK_F1 0x70

  #define VK_F2 0x71

  #define VK_F3 0x72

  #define VK_F4 0x73

  #define VK_F5 0x74

  #define VK_F6 0x75

  #define VK_F7 0x76

  #define VK_F8 0x77

  #define VK_F9 0x78

  #define VK_F10 0x79

  #

  #define WM_CHAR 0x0102 //字符消息

  WM_CHAR也称为键盘消息,如果某窗口拥有输入焦点,当用户在应用程序运行时按下一个键时,系统就会产生一个键盘消息WM_CHAR,告诉此窗口键盘上哪个键被按下了。该消息的处理函数为OnChar()。具体形式为:

  afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)

  各参数含义为:

  nChar: 键盘所输入的ASCII码。

  nRepCnt: 按键的重复次数,当用户按下某个键不放时,该参数将持续增加。

  nFlag: 用于传递按键的其它一些信息,如扫描码,上一次按键状态等。具体如下:

  字节        说明

  0-7        键盘扫描码

  8        此按键为扩充按键,如F1F12等功能键,此字节等于1时为真

  9-12        保留

  13        此字节为1表示按下键的同时,ALT键也被按住了

  14        前一个按键状态。此字节为1代表信息在按键被按下之前就送出来了

  15        此字节为1表示这个按键已经被放开了,反之就表示还被按着

  此外还有两个常用的键盘消息:WM_KEYDOWNWM_KEYUP.

  WM_KEYDOWN消息是当用户按下一个非系统键时产生的,非系统键就是不按下ALT键时的按键。

  WM_KEYUP 消息是当用户释放一个非系统键时产生的。

  2) 鼠标消息

   #define WM_MOUSEMOVE 0x0200 //鼠标移动消息

  当鼠标在某个窗口内移动时,Windows会不断地发出鼠标移动消息WM_MOUSEMOVE,并把鼠标的最新位置传给该窗口。如果在窗口的范围内按下鼠标左键,系统就会发出按下左键WM_LBUTTONDOWN消息给该窗口,等到用户放开按键后,再发出放开左键WM_LBUTTONUP消息给该窗口。

  鼠标移动消息的消息响应函数为:

  afx_msg void OnMouseMove(UINT nFlags CPoint point)

  其中的参数含义如下:

  UINT nFlag:此事件发生时,鼠标按键、键盘控制键的状态,可以是以下值的任意组合:

  当用户按下CTRL键时,nFlags设置为MK_CONTROL

  当用户按下鼠标左键时,nFlags设置为MK_LBUTTON

  当用户按下鼠标中键时,nFlags设置为MK_MBUTTON

 

 

 

一、      消息概述

Windows窗体是怎样展现在屏幕上的呢?众所周知,是通过API绘制实现的。Windows操作系统提供了一系列的API函数来实现界面的绘制功能,例如:

       DrawText 绘制文字

       DrawEdge 绘制边框

       DrawIcon 绘制图标

       BitBlt 绘制位图

       Rectangle 绘制矩形

       …

再复杂的程序界面都是通过这个函数来实现的。

那什么时候调用这些函数呢?显然我们需要一个控制中心,用来进行发号施令,我们还需要一个命令传达机制,将命令即时的传达到目的地。这个控制中心,就是一个动力源,就像一颗心脏,源源不断地将血液送往各处。这个命令传达机制就是Windows消息机制,Windows消息就好比是身体中的血液,它是命令传达的使者。

Windows消息控制中心一般是三层结构,其顶端就是Windows内核。Windows内核维护着一个消息队列,第二级控制中心从这个消息队列中获取属于自己管辖的消息,后做出处理,有些消息直接处理掉,有些还要发送给下一级窗体(Window)或控件(Control)。第二级控制中心一般是各Windows应用程序的Application对象。第三级控制中心就是Windows窗体对象,每一个窗体都有一个默认的窗体过程,这个过程负责处理各种接收到的消息。

 

消息是以固定的结构传送给应用程序的,结构如下:

Public Type MSG

    hwnd As Long

    message As Long

    wParam As Long

    lParam As Long

    time As Long

    pt As POINTAPI

End Type

其中hwnd是窗体的句柄,message是一个消息常量,用来表示消息的类型,wParamlParam都是32位的附加信息,具体表示什么内容,要视消息的类型而定,time是消息发送的时间,pt是消息发送时鼠标所在的位置。

Windows操作系统中包括以下几种消息:

1、标准Windows消息:

这种消息以WM_打头。

2、通知消息

通知消息是针对标准Windows控件的消息。这些控个包括:按钮(Button)、组合框(ComboBox)、编辑框(TextBox)、列表框(ListBox)、ListView控件、Treeview控件、工具条(Toolbar)、菜单(Menu)等。每种消息以不同的字符串打头。

3、自定义消息

编程人员还可以自定义消息。

 

二、           关于Windows句柄

不是每个控件都能接收消息,转发消息和绘制自身,只有具有句柄(handle)的控件才能做到。有句柄的控件本质上都是一个窗体(window),它们可以独立存在,可以作为其它控件的容器,而没有句柄的控件,如Label,是不能独立存在的,只能作为窗口控件的子控件,它不能绘制自身,只能依靠父窗体将它绘制来。

句柄的本质是一个系统自动维护的32位的数值,在整个操作系统的任一时刻,这个数值是唯一的。但该句柄代表的窗体释放后,句柄也会被释放,这个数值又可能被其它窗体使用。也就是说,句柄的数值是动态的,它本身只是一个唯一性标识,操作系统通过句柄来识别和查找它所代表的对象。

然而,并非所有的句柄都是窗体的句柄,Windows系统中还中很多其它类型的句柄,如画布(hdc)句柄,画笔句柄,画刷句柄,应用程序句柄(hInstance)等。这种句柄是不能接收消息的。但不管是哪种句柄,都是系统中对象的唯一标识。本文只讨论窗体句柄。

那为什么句柄使窗口具有了如此独特的特性呢?实际是都是由于消息的原因。由于有了句柄,窗体能够接收消息,也就知道了该什么时候绘制自己,绘制子控件,知道了鼠标在什么时候点击了窗口的哪个部分,从而作出相应的处理。句柄就好像是一个人的身份证,有了它,你就可以从事各种社会活动;否则的话,你要么是一个社会看不到的黑户,要么跟在别人后面,通过别人来证明你的存在。

 

三、           消息的传送

1、从消息队列获取消息:

可以通过PeekMessageGetMessage函数从Windows消息队列中获取消息。Windows保存的消息队列是以线程(Thread)来分组的,也就是说每个线程都有自己的消息队列。

2、发送消息

发送消息到指定窗体一般通过以下两个函数完成:SendMessagePostMessage。两个函数的区别在于:PostMessage函数只是向线程消息队列中添加消息,如果添加成功,则返回True,否则返回False,消息是否被处理,或处理的结果,就不知道了。而SendMessage则有些不同,它并不是把消息加入到队列里,而是直接翻译消息和调用消息处理,直到消息处理完成后才返回。所以,如果我们希望发送的消息立即被执行,就应该调用SendMessage

还有一点,就是SendMessage发送的消息由于不会被加入到消息队列中,所以通过PeekMessageGetMessage是不能获取到由SendMessage发送的消息。

另外,有些消息用PostMessage不会成功,比如wm_settext。所以不是所有的消息都能够用PostMessage的。

还有一些其它的发送消息API函数,如PostThreadMessageSendMessageCallbackSendMessageTimeoutSendNotifyMessage等。

 

四、           消息循环与窗体过程

消息循环是应用程序能够持续存在的根本原因。如果循环退出,则应用程序就结束了。

我们来看一看Delphi中封装的消息循环是怎样的:

第一步:程序开始运行(Run)

  Application.Initialize;  //初始化

  Application.CreateForm(TForm1, Form1); //创建主窗体

  Application.Run;  //开始运行,准备进行消息循环

如果不创建主窗体,应用程序同样可以存在和运行。

 

第二步:开始调用消息循环(HandleMessage)

procedure TApplication.Run;

begin

  FRunning := True;

  try

    AddExitProc(DoneApplication);

    if FMainForm <> nil then

    begin

      case CmdShow of

        SW_SHOWMINNOACTIVE: FMainForm.FWindowState := wsMinimized;

        SW_SHOWMAXIMIZED: MainForm.WindowState := wsMaximized;

      end;

      if FShowMainForm then

        if FMainForm.FWindowState = wsMinimized then

          Minimize else

          FMainForm.Visible := True;

      Repeat   //注:循环开始

        try

          HandleMessage;

        except

          HandleException(Self);

        end;

      until Terminated;  //循环结束条件

    end;

  finally

    FRunning := False;

  end;

end;

第三步:消息循环中对消息的处理。

procedure TApplication.HandleMessage;

var

  Msg: TMsg;

begin

  if not ProcessMessage(Msg) then Idle(Msg);

end;

 

function TApplication.ProcessMessage(var Msg: TMsg): Boolean;

var

  Handled: Boolean;

begin

  Result := False;

  if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then

  begin

    Result := True;

    if Msg.Message <> WM_QUIT then

    begin

      Handled := False;

      if Assigned(FOnMessage) then FOnMessage(Msg, Handled);

      if not IsHintMsg(Msg) and not Handled and not IsMDIMsg(Msg) and

        not IsKeyMsg(Msg) and not IsDlgMsg(Msg) then

      begin

        TranslateMessage(Msg);

        DispatchMessage(Msg);

      end;

    end

    else

      FTerminate := True;

  end;

end;

 

窗体过程实际上是一个回调函数。所谓的回调函数,实际上就是由Windows操作系统或外部程序调用的函数。回调函数一般都有规定的参数格式,以地址方式传递给调用者。窗口过程中是Windows操作系统调用了,在一个窗口创建的时候,在分配窗体句柄的时候就需要传入回调函数地址。那为什么我们平时编程看不到这个回调函数呢?这是由于我们的编程工具已经为我们生成了默认的窗体过程,这个过程的要做的事情就是判断不同的消息类型,然后做出不同的处理。例如可以为键盘或鼠标输入生成事件等。

五、           消息与事件

事件本质上是对消息的封装,是IDE编程环境为了简化编程而提供的有用的工具。这个封装是在窗体过程中实现的。每种IDE封装了许多Windows的消息,例如:

事件

消息

OnActivate

WM_ACTIVATE

OnClick

WM_XBUTTONDOWN

OnCreate

WM_CREATE

OnDblClick

WM_XBUTTONDBLCLICK

OnKeyDown

WM_KEYDOWN

OnKeyPress

WM_CHAR

OnKeyUp

WIN_KEYUP

OnPaint

WM_PAINT

OnResize

WM_SIZE

OnTimer

WM_TIMER

了解了这一点后,我们完成可以封装自己的事件。

通过上面的介绍,相信各位已经对Windows消息机制有了一定的理解了。通过Windows消息编程,我们不但可以实现很多常规功能,而且可以实现很多IDE类库没有提供的功能;另外,我们还可以通过消息钩子,对消息进行截获,改变其默认的处理函数,从而突破平台或软件功能的限制,极大的扩展程序的功能;我们还可以修改默认的窗体过程,按自己的要求来响应消息;或者自定义消息,实现程序之间的即时通讯等等。通过更加深入的学习,我们还会接触到更多与Windows消息机制相关其它Windows相对比较底层的知识,如果能够这样,蓦然回首,你会发现自己原来离高手不远了。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值