SendMessage(PostMessage)详解

Windows API(应用程序接口)是Windows系列软件为程序开发人员提供的火力强大的“武器库”,在这个武器库中,有很多极具威力的武器,SendMessage就是其中之一,它的功能非常丰富,灵活使用这个函数,会给编程工作带来很多便利。本文以Visual Basic为例,结合几个具体的例子介绍该函数的功能。
 
一、SendMeaasge函数简介 
顾名思义,SendMessage函数的功能是“发送消息”,即将一条消息发送到指定对象(操作系统、窗口或控件等)上,以产生特定的动作(如滚屏、修改对象外观等)。 
SendMessage函数在VB中的函数说明如下:
 
Declare Function SendMessage Lib "user32" Alias "SendMessageA" (Byval hwnd As Long, Byval wMsg As Long,Byval wParam As Long,lParam As Any) As Long 
其中四个自变量的含义和说明如下: 

hWnd:对象的句柄。希望将消息传送给哪个对象,就把该对象的句柄作为实参传送,在VB中可以简单地用“对象.hWnd”获得某个对象的句柄,如Text1.hWnd和Form1.hWnd分别可以得到Text1和Form1的句柄。 
wMsg:被发送的消息。根据具体需求和不同的对象,将不同的消息作为实参传送,以产生预期的动作。
wParam、lParam:附加的消息信息。这两个是可选的参数,用来提供关于wMsg消息更多的信息,不同的wMsg可能使用这两个参数中的0、1或2个,如果不需要哪个附加参数,则将实参赋为NULL(在VB中赋为0)
。 
在简单了解了SendMessage函数的格式和功能后,让我们以几个例子来看看它的威力。
 
二、SendMessage函数使用实例 

例1 多行TextBox中的快速处理功能在处理多行TextBox时我们经常会碰到以下几种情况: 
希望了解多行TextBox中目前共有多少行文字。 
想快速返回第N行的文字。 
对于上面的情况,如果用VB自身的语句或函数来实现的话,要写不短的代码,而且由于要采用顺序查找的办法来完成,因此代码的执行效率也很低。如果使用SendMessage函数则可以大大减少代码量,并大幅度的提高执行效率。 
用SendMessage函数完成上面两个任务的方法非常简单,每个任务只需简单地发送一条消息给多行TextBox即可,两个消息分别为:EM_GETLINECOUNT、EM_GETLINE,其它参数和返回值见附表。 
下面用一个简单的实例演示这两个功能: 
新建工程,在Form1上添加三个TextBox(名称分别为Text1、txtLineCount、TxtString,将Text1的Multi 
Line属性置为True)、三个标签和一个命令按钮。为工程添加一个模块Moudle1,在其中写如下声明(其中 
SendMessage函数的声明可以从VB的“API浏览器”中复制): 消息常量名 消息值 wParam lParam 返回值 EM_GETLINECOUNT &HBA 未用 未用 行数 EM_GETLINE &HC4 要找的行号 存结果的字节串 结果字节串的字节数 
Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long,lParam As Any) As Long 
Public Const EM_GETLINECOUNT=&HBA 
Public Const EM_GETLINE=&HC4 
在Form1的代码模块中写如下代码: 
Private Sub Command1_Click() 
Dim str(256) As Byte 
str(1)=1 '最大允许存放256个字符 
'获取总行数,结果显示在文本框txtLineCount中 
txtlineCount=SendMessage(Text1.hwnd,EM_GETLINECOUNT,0,0) 
'获取第3行的数据放在str中,转换为字符串后显示在文本框txtString中 
SendMessage Text1.hwnd,EM_GETLINE,2,str(0) 
txtString= StrConv(str,vbUnicode) 
End Sub 
之后,按F5运行程序,在多行文本框中随便键入几行文字,然后按下[确定]按钮,出现如图画面,说明程序正确统计出了总行数和第3行的文字。 
两点补充说明:在调用SendMessage获取第N行字符串时,lParam需要说明为字节数组,在调用完成后,再将字节数组转换为字符串;另外,调用前必须在lParam的前两个字节指明允许存放的最大长度,其中第一个字节为低位,第二个字节为高位,本例将高位(即str(1))置1.说明最大允许存放256个字符。 

例2 程序控制拉下或收起组合框的下拉列来 
一般情况下,为了拉下或收起组合框的下拉列表,需要用键盘或鼠标进行操作,而有时我们希望程序运行的某个时刻自动拉出下拉列表(比如在一些演示程序中),为了实现这个目的,我们也只有借助于SendMessage函数,方法是发一个CB_SHOWDropDOWN(&H14F)消息给组合框。 
在发CB_SHOWDropDOWN消息时,wParam参数决定了是拉下列表(=True时)还是收起列表(=False时),lParam无用(设为0)。 
为说明具体的使用方法,下面提供简单的程序片段。首先在代码模块中做如下声明: 
Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long,ByVal wMsg As Long,ByVal wParam As Long,lParam As Any) As Long 
Const CB_SHOWDropDOWN=&H14F 
当程序中某处需要拉下组合框Combol的列表时,写如下调用语句: 
SendMessage Combol.hwnd,CB_SHOWDropDOWN,True,0 
当需要收起组合框Combol的列表时,写如下语句: 
SendMessage Combol.hwnd,CB_SHOWDropDOWNN,False,0 

例3 在列表框中查找匹配的项目 
在Win95风格的帮助系统中一般都有一个“索引”页,索引页含有一个文本框和一个列表框,当用户在文本框中输入文字时,下拉列表会动态地显示与文本框中文字最匹配的项目,为用户提供了最大的方便。这种效果在应用程序的帮助系统中很容易实现(只要按照Win95帮助系统的正常制作过程制作就可以实现),如果想在应用程序的其它地方实现这种特性就需费一番心思了。 
而使用SendMessage函数实现上述特性则非常简单,甚至只需一条语句就足够了,那就是在文本框的Change事件中给列表框发一条LB_FINDSTRING(&H18F)消息,该消息告诉列表框在列表中查找匹配的项目。 
在发LB_FINDSTRING消息时,wParam参数代表从列表框的哪一个项目后面开始查找,一般情况下该参数可定为-1,表示从List1(0)即第一项开始向后循环查找,lParam则传进欲搜索的字符串(必须采用值传递)。 
具体的代码和运行画面与后面的例4合并在一起演示。
 
例4 为ListBox添加水平滚动条 
在VB中,列表框控件仅提供垂直滚动条,没有设置水平滚动条的能力,当某些项目的文本宽度较长时,超出列表框宽度部分的文本就无法显示出来,因此,很有必要为ListBox添加一个水平滚动条来方便操作。 
为添加水平滚动条,只需发一条LB_SETHORIZONTALEXTENT(&H194)消息给列表框即可。发送消息时,wParam为滚动条的长度(以像素为单位,可通过计算得出准确的长度,也可随便给一个大于最大文本宽度的数字,如本例的250),lParam无用。下面是例3和例4合并在一起的代码和运行画面 
Declare Function SendMessage Lib "user32" Alias "SendMessageA"(ByVal hwnd As Long,ByVal wMsg As Long,ByVal wParam As Long,lParam As Any) As Long 
Public Const LB_FINDSTRING=&H18F 
Public Const LB_SETHORIZONTALEXTENT=&H194 
Private Sub Form_Load() 
List1.AddItem "软件" 
List1.AddItem "电脑游戏" 
List1.AddItem "电视机" 
List1.AddItem "电视台" 
List1.AddItem "电脑" 
List1.AddItem "电脑游戏软件" 
'下一句为列表框添加水平滚动条 
SendMessage List1.hwnd,LB_SETHORIZONTALEXTENT,250,0 
End Sub 
Private Sub Text1_Change() 
'注意!当lParam传入的是字符串时,必须用ByVal传递 
List1.ListIndex = SendMessage(List1.hwnd,LB_FINDSTRING,-1,ByVal Text1.Text) 
End Sub 
通过上面几个例子,想必您已经对SendMessage函数的强大功能有了初步的了解。事实上利用该函数我们还可以完成更多更好的任务,如控制文本框的自动滚屏、实现文字编辑过程中的Undo功能、操纵应用程序的窗体控制菜单等等,感兴趣的读者请参阅有关Windows API的资料。 
本文程序均用Visual Basic 5.0企业版编写,在Pwin95环境下运行正常。 

---------------------------------------------------------------------------
东南大学 梁云 

Windows系统是由消息机制驱动的,每个线程如果建立了一个窗口,则由系统分配一个消息队列用于窗口消息的处理。另外,消息也可以不经过消息队列而利用SendMessage函数直接发送给窗口,窗口过程将处理这个消息,但只有当消息被处理之后,SendMessage才能返回到调用程序。下面结合两个Delphi程序,讨论如何利用SendMessage向控件发送消息和控件对这种消息的响应。 
用SendMessage向控件发送消息 
在编程中,有时需要控件以特殊的风格显示,而这种要求又无法通过设置控件属性实现。例如,读取客户列表并显示在下拉框供用户选择,如果下拉框宽度太窄,则不能全部显示;如果将宽度定得太宽,界面又有不紧凑之感。因此希望能在运行期动态地确定下拉框显示区域的宽度,这种要求如果不用SendMessage函数就很难实现。 
解决办法是,在读数据库时计算字符串的显示宽度,用显示宽度的最大值确定下拉框显示区域的宽度。再用SendMessage函数向下拉框发送CB_SETDropPEDWIDTH消息和宽度值,下拉框根据消息中传来的信息,就可以进行正确显示。 
部分源程序代码如下: 
i:=0; //计数 
MaxWidth:=0; 
Query1.SQL.Clear; 
Query1.SQL.Add(‘select Company from Customer’); 
Query1.Open; 
//读客户列表到下拉框 
while not Query1.Eof do begin  
ComboBox1.Items.add(Query1.FieldByName 
(‘Company’).AsString); 
Width:=ComboBox1.Font.Size * Length 
(ComboBox1.Items[i]); 
if Width>MaxWidth then 
MaxWidth:=Width; //找出最大值 
Query1.Next; 
i:=i+1; 
end; 
Query1.Close; 
ComboBox1.Text:=ComboBox1.Items[0]; 
//发送消息以确定显示区域的宽度 
SendMessage(ComboBox1.Handle, 
CB_SETDropPEDWIDTH,MaxWidth,0); 
利用SendMessage函数还可以实现一些有趣的效果,例如在按钮的Click事件中加入如下语句: 
SendMessage(Button.Handle,BM_SETSTYLE, 
BS_RADIOBUTTON,1); 
运行后点击按钮,就可以把按钮变成一个收音机按钮。 
控件接收SendMessage消息 
上面讨论了用SendMessage向控件发送消息的过程。但凡事有利就有弊,用SendMessage发送的消息在处理上存在着一定困难。因为该消息不经过消息队列,所以无法用OnMessage方式来指定对消息的响应,甚至用HookMainWindow也不行,因为消息直接发送到控件,绕过了主窗体。要对这种类型的消息作出响应,需要重载控件的WndProc方法。 
例如,对于一个列表框,滚动条的滚动消息就是用SendMessage方式发送的,因此该消息不在TlistBox的事件列表中。下面是处理控件响应该滚动消息的具体步骤。 
1.首先从TlistBox继承一个TmyListBox类,并重载WndProc方法。在程序中加入下列定义: 
type 
TMyListBox=class(TListBox) 
private 
procedure WndProc(var Msg: TMessage);  
override; 
//重载WndProc,处理发送到控件的消息 
public 
end; 
其中WndProc方法指定控件对消息的响应,输入参数是TMessage类型,该数据类型是一个记录,包含了消息代码和消息的参数,消息参数可以用Longint或Word方式获得。 
2.对滚动事件做出响应,在WndProc方法中加入如下处理代码: 
if (Msg.Msg=WM_VSCROLL) and  
(Msg.WParamLo=SB_ENDSCROLL) then 
begin 
//获得鼠标位置对应的列 
ItemIndex:=ItemAtPos(Point,true);  
Form1.Edit1.Text:=inttostr(ItemIndex); 
inherited; 
end 
else 
inherited; 
当程序接收到WM_VSCROLL消息,且WParamLo参数为SB_ENDSCROLL时,表示竖直滚动条停止滚动,就可以用ItemAtPos方法确定与鼠标位置对应的ItemIndex。ItemAtPos方法的Point参数是一个TPoint类型的变量,用来保存鼠标的位置。  
3.定义方法ListBoxMouseMove,在鼠标移动时,将当前位置保存在Point中: 
procedure TForm1.ListBoxMouseMove(Sender:  
TObject; Shift: TShiftState; X,Y: Integer); 
begin 
Point.X:=X; 
Point.Y:=Y; 
end; 
4.在运行期创建和初始化列表框,并指定列表框的MouseMove事件对应上一步定义的ListBoxMouseMove方法。在主窗体的Create事件中输入下面的代码:begin 
Point.X:=0; 
Point.Y:=0; 
//创建自定义列表框 
List:=TMyListBox.Create(Form1);  
List.Parent:=Form1; 
List.Left:=5; 
List.Top:=30; 
List.Width:=150; 
List.Height:=200; 
for i:=0 to 300 do 
begin 
List.Items.Add(inttostr(i)); //初始化 
end; 
//指定处理MouseMove事件的方法 
List.OnMouseMove := ListBoxMouseMove;  
end;

其他举例:
1,获得MEMO中的光标位置.
Lops:=SendMessage(memol.Handle,EM_LINEFROMCHAR,Memol.SelStart,0);//光标所在的行号
Cpos:=SendMessage(meno1.Handle,EM_LINEINDEX,Lpos,0);//光标所在的字符位置
LineLength:=SendMessage(memol.handle,EM_LINELENGTH,Cpos,0);//这行的字符数.
2,开关显示器.
SendMessage(Handle, WM_SYSCOMMAND, SC_MONITORPOWER, 0);//关闭显示器.
SendMessage(Handle, WM_SYSCOMMAND, SC_MONITORPOWER, -1);//打开显示器.
3,有人问我如何让Memo翻页,我试了一下,
Next: 
sendmessage(memo1.handle,wm_keydown,VK_NEXT,-1); 
sendmessage(memo1.handle,wm_keyup,VK_NEXT,-1); 
Prev: 
sendmessage(memo1.handle,wm_keydown,VK_PRIOR,-1); 
sendmessage(memo1.handle,wm_keyup,VK_PRIOR,-1); 

---------------------------------------------------------------------------
参数:
hWnd
窗口过程接收消息的窗口句柄。如果此参数为HWND_BROADCAST,则消息被送到系统的所有顶层窗口,包括无效或不可见的非自身拥有的窗口、被覆盖的窗口和弹出式窗口。消息不被送到子窗口。
Msg 
指定被发送的消息; 
wParam 
指定附加消息的特定信息; 
lParam 
指定附加消息的特定信息。 


.返回值:
返回值返回消息处理的结果,其依赖于所发送的消息。

.常见问题
1)使用SendMessage来实现剪切、复制和粘贴

SendMessage(hwnd, WM_COPY, 0, 0);SendMessage(hwnd, WM_CUT, 0, 0);SendMessage(hwnd, WM_PASTE, 0, 0);
2)SendMessage与PostMessage的区别
PostMessage将消息放入消息队列后马上返回,而SendMessage直到窗口过程处理完消息后才返回
3)SendMessage发送WM_COPYDATA消息在进程间传送数据
WM_COPYDATA消息主要目的是允许在进程间传递少量只读数据。SDK文档推荐用户使用SendMessage()函数,接收方在数据复制完成前不返回,这样发送方就不可能删除和修改数据。
例如:

std:string 
strData = "VC知识库 VCKBASE.COM";
COPYDATASTRUCT cds;
cds.dwData = 0;
cds.cbData = strData.Length();
cds.lpData = strData.c_str();
::SendMessage(hwnd, WM_COPYDATA, NULL, (LPARAM)&cds); 

转载于:https://www.cnblogs.com/rogee/archive/2010/09/14/1827254.html

猜你喜欢

转载自blog.csdn.net/weixin_33827731/article/details/94680833