2011年11月20日
为了知道什么时候一个消息被发送到应用程序,必须用自己的窗口过程代替 Application的窗口过程。当在自己的窗口过程中对消息处理完后,要把消息再传递给原窗口过程。这样的过程就叫做子类化窗口。
可以传递一个常量GWL _ WNDPROC给Win32 API函数SetWindowLong( )来指定一个新的窗口过程。窗口过程可以是以下两种格式:一是利用API定义;二是利用Delphi使窗口方法作为窗口过程。注意 当子类化一个VCL窗口的窗口过程时,可能由于窗口的句柄被重复创建而导致应用程序失败。使用子类化技术一定要小心。一种更为安全的使用方法是使用 Application.HookMainWindow()。我们将在后面讨论它。
1. 一个Win32 API的窗口过程
一个API的窗口过程必须像这样声明:
1 function AWndProc (Handle:hWnd; Msg, wParam, lParam:Longint):Longint;stdcall;
声明中,Handle参数用于标识目标窗口;Msg是一个消息;wParam、lpParam参数含有消息的附加信息。函数的返回值要依靠收到的消息确定。需要特别注意,此函数必须用 stdcall作为调用约定。
可以这样使用SetWindowLong( )函数给应用程序的窗口指定窗口过程:
var
WProc: Pointer;
begin
WProc:=Pointer(SetWindowLong(Application.Handle, GWL_WINDPROC, Integer(@NewWndProc)));
在此调用后,返回一个指针类型的WProc指向旧的窗口过程。对这个值的保留是很必要的,因为
有些消息可能需要传递给旧的窗口过程。下面是一个窗口过程的实现示例:
1 function AWndProc (Handle:hWnd; Msg, wParam, lParam:Longint):Longint;stdcall;
2 begin
3 Result:=CallWindowProc(WProc, Application.Handle, Msg, wParam, lParam);
4 end;
5
ScWndPrc. pas单元的代码,程序中利用自己的窗口过程代替了 Application对象的窗口过程来处理自定义的消息DDG MFOOMSG。
unit ScWndPro;
1 interface
2 uses Forms, Messages;
3
4 const DDGM_FOOMSG = WM_USER;
5
6 var
7 WProc: Pointer;
8
9 function NewWndProc(Handle:hWnd; Msg, wParam, lParam:Longint):Logint;stdcall;
10 begin
11 if Msg = DDGM_FOOMSG then
12 showmessage('DDGM_FOOMSG new');
13
14 Result:= CallWindowProc(WProc, Handle, Msg, wParam, lParam);
15 end;
16
17 initialization
18 WProc := Pointer(SetWindowLong(Application.Handle, gwl_WndProc, Integer(@NewWndProc)));
19
20 end.
警告 一定要把SetWindowLong( )函数值保存起来。如果你在自定义的窗口过程中不把该值返还给旧窗口过程,有可能导致应用程序甚至操作系统的崩溃。
2. Delphi的窗口方法
利用Delphi提供的函数MakeObjectInstance()可以把一个API窗口过程与一个Delphi方法关联。
MakeObjectInstance()能够创建一个TWndMethod类型的方法,该方法可以当作窗口过程使用。
MakeObjectInstance()在Forms单元中声明如下:
function MakeObjectInstance(Method: TWndMethod): Pointer;
TWndMethod在Forms单元中定义如下:
type
TWndMethod = procedure (var Message: TMessage) of object;
MakeObjectInstance()的返回值为一个指针即Pointer,它指向新创建的窗口过程。这是SetWindowLong()需要的最后一个参数的值。最后,你要利用FreeObjectInstance()函数释放用MakeObjectInstance()创建的窗口方法。
作为一个示范程序,WinProc.dpr演示了子类化应用程序的窗口过程和如何利用Application.OnMessage的方法。
1 unit Unit1;
2
3 interface
4 uses
5 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
6 Dialogs, StdCtrls;
7
8 type
9 TForm1 = class(TForm)
10 btnSend: TButton;
11 btnPost: TButton;
12 procedure FormCreate(Sender: TObject);
13 procedure FormDestroy(Sender: TObject);
14 procedure btnSendClick(Sender: TObject);
15 procedure btnPostClick(Sender: TObject);
16 private
17 OldWndProc: Pointer;
18 WndProcPtr: Pointer;
19 procedure WndMethod(var Msg:TMessage);
20 procedure HandleAppMessage(var Msg:TMsg; var Handle: Boolean);
21 public
22 { Public declarations }
23 end;
24
25 var
26 Form1: TForm1;
27
28 implementation
29
30 {$R *.dfm}
31
32 procedure TForm1.btnPostClick(Sender: TObject);
33 begin
34 PostMessage(Application.Handle, DDGM_FOOMSG, 0,0);
35 end;
36
37 procedure TForm1.btnSendClick(Sender: TObject);
38 begin
39 SendMessage(Application.Handle, DDGM_FOOMSG, 0,0);
40 end;
41
42 procedure TForm1.FormCreate(Sender: TObject);
43 begin
44 Application.OnMessage := HandleAppMessage;
45 WndProcPtr := MakeObjectInstance(WndMethod);
46 OldWndProc := Pointer(SetWindowLong(Application.Handle, GWL_WNDPROC, integer(WndProcPtr)));
47 end;
48
49 procedure TForm1.FormDestroy(Sender: TObject);
50 begin
51 SetWindowLong(Application.Handle,GWL_WNDPROC, Longint(OldWndProc));
52 FreeObjectInstance(WndProcPtr);
53 end;
54
55 procedure TForm1.HandleAppMessage(var Msg: TMsg; var Handle: Boolean);
56 begin
57 if Msg.message = DDGM_FOOMSG then
58 Showmessage('DDGM_FOOMSG');
59 end;
60
61 procedure TForm1.WndMethod(var Msg: TMessage);
62 begin
63 if Msg.Msg = DDGM_FOOMSG then
64 showmessage('New DDGM_FOOMSG');
65 with Msg do
66 result:= CallWindowProc(OldWndProc, Application.Handle, Msg, wParam, lParam);
67 end;
68
69 end.
SendBtn按钮被按下时,API函数SendMessage()发送一个消息DDGM_FOOMSG给Application的窗
口句柄。当PostBtn按钮被按下时,同样的消息被PostMessage()API函数发送给Application。
HandleAppMessage()被指定来处理Application.OnMessage事件。HandleAppMessage()只是简单地
使用ShowMessage()显示一个消息框。OnMessage在主窗体的OnCreate事件处理过程中被指定。
注意在主窗体的OnDestroy事件处理过程中,要首先恢复应用程序原有的窗口过程,然后再通过调
用FreeObjectInstance()来释放由MakeProcInstance()创建的窗口过程。请注意,一定要先恢复再释放。
否则,会导致应用程序或操作系统被破坏。
可以看出,ScWndPrc单元被Main.pas引用。这意味着应用程序窗口被两次子类化一次是由
ScWndPrc单元使用API技术实现;另一次是在Main单元中使用窗口方法技术实现。注意:一定要牢记
在自定义的窗口过程和窗口方法中必须要用CallWindowProc()把消息传递给原窗口过程。
当运行此程序时,会看到无论哪一个按钮被按下,都有一个消息框被窗口过程或窗口方法引发。
但是,Application.OnMessage事件只能看到由PostMessage()函数发来的消息。
posted @
2011-11-20 17:52 firebird 阅读(483) |
评论 (0) |
编辑 收藏
今天是2011年11月20日,决定开博,目的仅仅是督促自己,在人生的路上,越战越勇.....
posted @
2011-11-20 17:28 firebird 阅读(326) |
评论 (0) |
编辑 收藏