1.消息是指什么?
消息系统对于一个win32程序来说十分重要,它是一个程序运行的动力源泉。一个消息,是系统定义的一个32位的值,他唯一的定义了一个事件,向 Windows发出一个通知,告诉应用程序某个事情发生了。例如,单击鼠标、改变窗口尺寸、按下键盘上的一个键都会使Windows发送一个消息给应用程序。
Windows以record的形式发送消息给应用程序。记录中包括事件的类型以及附加的特定信息。Windows发送给应用程序的记录类型是TMsg,定义在windows.pas单元中,定义如下:
{ Message structure }
PMsg = ^TMsg;
tagMSG = packed record
hwnd: HWND;
message: UINT;
wParam: WPARAM;
lParam: LPARAM;
time: DWORD;
pt: TPoint;
end;
{$EXTERNALSYM tagMSG}
TMsg = tagMSG;
MSG= tagMSG;
{$EXTERNALSYM MSG}
消息字段记录说明:
Hwnd:32位windows句柄,指向消息要发往的窗口,这个窗口可以是几乎所有的屏幕对象,因为window对大多数可是对象都维护了一个窗口句柄。
Message:代表某种消息的常量值,这些常量可以是在windows.pas中预定义的标准windows消息,也可以是用户自定义的消息。
Wparam:这个字段常常包含和消息关联的常量值,也可以包含一个窗口句柄或者消息关联的某个窗口或控件的ID值。
Lparam:这个字段经常容纳一个对内存数据的索引或指针,由于wparam和lparam都是32位大小,我们可以进行强制类型转换。
WM开头的通常是指WindowsMessage.
2.常用windows消息:
wm_active:窗口被激活
wm_char按下某个键发送wm_keydown和wm_keyup消息
wm_close窗口将要关闭
wm_keydown用户正在按下键盘上的一个键
wm_keyup:用户已经释放按下的键
wm_lbuttondown用户按下鼠标左键
wm_mousemove用户正在移动鼠标
wm_paint必须重绘窗口区域
wm_timer发生了一个计时器事件
wm_quit:发送终止程序请求
3.windows消息系统的工作方式
三个部分:消息队列、消息循环、窗口过程。
消息队列:Windows为每个应用程序维护一个消息队列,windows应用程序必须从这个队列中取得消息,并且把得到的消息分派到合适的窗口。
消息循环:windows程序从应用程序队列中取得一条消息,分派到适合的窗口中,然后再取得下一条消息,再分发到适合的窗口中,如此循环。这种消息机制就是消息循环。
窗口过程:应用程序中的每个窗口都有一个窗口过程,它接受消息循环中传入的每条消息。窗口过程的任务是接受各个窗口消息并且对此做相应的回应。窗口过程是所谓的回调函数,在处理完毕一条消息之后,窗口过程通常要给windows一个返回值。
4.发送自己的消息:
我们需要在应用程序窗口和控件之间发送消息。Delphi提供了以下几种方法:
Perform()方法:VCL为所有的TContol派生类提供了perform()方法,可以发送消息给任何已知对象实例的对象或控件对象。Perform有三个参数:消息,对应的lparam和wparam。如下:
function Tcontrol.perform(Msg :cardinal;wparam,lparam:longint):longint;
要发送一条消息给一个窗体或控件,使用下面的格式:
Retval:=controlname.perform(messageid,lparam,wparam);
Perform是同步调用的,因此直到消息被处理完毕,才能得到返回值。Perform()方法把它的参数组合成一个TMessage记录,然后调用该对象的Dispatch方法去发送这条消息,绕开了windowsAPI消息系统。
Sendmessage()和postmessage()API函数:
Sendmessage和perform()类似,是同步调用的。消息直接发往目的窗口,该消息被处理完毕才返回;postmessage是异步调用的,发送消息给windows队列,然后立即返回。
通知消息:窗口的子控件发生事件,如果需要通知父窗口,就发送此消息。只发生在windows标准控件中如button、 listbox 、combox和通用控件如tree view、 list view等。
5.自定义消息号和消息结构
const
WM_5001=WM_USER+5001;
WM_5002=WM_USER+5002;
WM_5003=WM_USER+5003;
WM_6001=WM_USER+6001;
type
//自定义消息结构
PMsgRec = ^TMsgRec;
TMsgRec = record
msgNo : Cardinal;
msgText: string;
end;
6.消息分发
向特定的对象分发消息:Dispatch函数
在TObject类中,有一个Dispatch()方法和一个DefaultHandler()方法,它们都是与消息分发机制相关的。
Dispatch()负责将特定的消息分发给合适的消息处理函数。首先它会在对象本身类型的类中寻找该消息的处理函数,如果找到,则调用它;如果没有找到而该类覆盖了TObject的DefaultHandler(),则调用该类的DefaultHandler();如果两者都不存在,则继续在其基类中寻找,直至寻找到TObject这一层,而TObject已经提供了默认的DefaultHandler()方法。
消息接收类:TMsgAccepter
//自定义消息接收类
TMsgAccepter=class
private
procedure Msg5001Accepter(var msg:TMsgRec); message WM_5001;
public
procedure defaultHandler(var message); override;
end;
implementation
uses
Main;
{ TMsgAccepter }
procedure TMsgAccepter.Msg5001Accepter(var msg: TMsgRec);
begin
FrmMain.Memo1.Lines.Add('接收类已收到5001消息,并对其进行了处理!')
end;
procedure TMsgAccepter.defaultHandler(var message);
var
theMsg:TMsgRec;
begin
theMsg:=TMsgRec(message);
FrmMain.Memo1.Lines.Add('接收类收到消息请求,但该消息号无指定处理函数!消息号为:'+IntToStr(theMsg.msgNo));
end;
a)向对象theMsgAccepter分发消息WM_5001(有对应处理函数)
var
theMsg:TMsgRec;
begin
theMsg.msgNo:=WM_5001;
theMsg.msgText:='';
theMsgAccepter.Dispatch(theMsg);
end;
运行结果:
b)向对象theMsgAccepter分发消息WM_6001(无对应处理函数)
var
theMsg:TMsgRec;
begin
theMsg.msgNo:=WM_6001;
theMsg.msgText:='';
theMsgAccepter.Dispatch(theMsg);
end;
运行结果:
7.消息投递:向特定窗口投递消息
消息响应窗口:TFrmMsgProcess
TFrmMsgProcess = class(TForm)
Memo1: TMemo;
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
procedure Msg5002Accepter(var msg:TMessage); message WM_5002;
procedure Msg5003Accepter(var msg:TMessage); message WM_5003;
public
{ Public declarations }
end;
var
FrmMsgProcess: TFrmMsgProcess;
implementation
uses
Main;
{$R *.dfm}
{ TFrmMsgProcess }
procedure TFrmMsgProcess.Msg5002Accepter(var msg:TMessage);
var
pTheMsg:PMsgRec;
begin
Self.Memo1.Lines.Add('窗口已收到5002号消息,并开始进行处理。。。');
Self.Memo1.Lines.Add('窗口收到的字符串参数信息为:'+String(msg.WParam));
pTheMsg:=PMsgRec(msg.LParam);
Self.Memo1.Lines.Add('窗口收到的结构体参数信息为:'+pTheMsg.msgText);
Self.Memo1.Lines.Add('窗口对5002号消息的处理已完成!');
end;
procedure TFrmMsgProcess.FormCreate(Sender: TObject);
begin
self.Memo1.Color:=clblack;
self.Memo1.Font.Color:=clgreen;
end;
procedure TFrmMsgProcess.Msg5003Accepter(var msg: TMessage);
begin
FrmMain.Memo1.Lines.Add('窗口已收到5003号消息,并开始进行处理。。。');
Sleep(1000);
FrmMain.Memo1.Lines.Add('窗口对5003号消息的处理已完成!');
end;
a)异步方式:Postmessage,发出后不等待,直接返回
begin
Self.Memo1.Lines.Add('主线程向子窗口发送5003号消息。。。');
PostMessage(FrmMsgProcess.Handle,WM_5003,0,0);
Self.Memo1.Lines.Add('主线程消息发送函数已结束!');
end;
运行结果:
b)同步方式:SendMessage,等窗口响应后,再返回
begin
Self.Memo1.Lines.Add('主线程向子窗口发送5003号消息。。。');
SendMessage(FrmMsgProcess.Handle,WM_5003,0,0);
Self.Memo1.Lines.Add('主线程消息发送函数已结束!');
end;
c)带参数信息的消息投递
发送“字符串或结构体”时,尽可能用sendMessage,否则,可能内存中的内容已被更改,而窗口消息响应函数还未处理。
var
msgStr:string;
theMsg:TMsgRec;
begin
msgStr:='WM_5002消息的字符串说明';
theMsg.msgNo:=WM_5002;
theMsg.msgText:='WM_5002消息的结构体说明';
SendMessage(FrmMsgProcess.Handle,WM_5002,WParam(PChar(msgStr)),LParam(@theMsg));
end;
运行结果:
.
PS:以上均是在同一个进程里投递消息,若在不同进程间投递消息,则必须使用“内存映射文件”存储字符串或结构体消息,而不是直接放在消息参数sparam和lparan中,那样会出现地址空间访问冲突的问题。
8.其他
a)wparam与lparam
wParam: 原win16系统中,word类型的整数,16位;现在的win32系统中,扩展为32位整型。
lParam: 原win16系统中,long类型的整数,32位;现在的win32系统中,32位整型。
注:WPARAM常常代表一些控件的ID或者高位底位组合起来分别表示鼠标的位置,如果
消息的发送者需要将某种结构的指针或者是某种类型的句柄时,习惯上用LPARAM来传递。
b)常用消息处理函数
Peekmessage() 从消息队列中读消息
Getmessage() 可以有选择的从消息列表中得到消息,一般用在window的窗口过程函数里面
DispatchMessage() 用来派送消息到窗口过程
TranslateMessage() 用于将Virtul-Key消息翻译为字符消息
Postthreadmessage() 用于向线程发送消息
Postmessage() 用于将消息传送到窗口的消息队列
SendMessage()不经过消息队列而直接发送给窗口的
c)窗口过程函数
从消息队列中取消息
重写窗口默认“窗口过程函数”
d)消息路由
e)参考界面 线程安全
http://blog.csdn.net/danscort2000/article/details/2780418 ——原理
http://topic.csdn.net/t/20040908/15/3352501.html ——消息机制的使用
https://www.cnblogs.com/edisonfeng/archive/2012/05/14/2499647.html