子类化

子类化
为了知道什么时候一个消息被发送到应用程序,必须用自己的窗口过程代替 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()函数发来的消息。

你可能感兴趣的:(子类化)