Delphi 7事件的多處理機制
Delphi 7事件的多處理機制Allen Tao
2007-08-19
首先解釋一下這個題目。在我使用Delphi
7的過程中發現,一個對象的事件只能被一個過程處理。如果多次給這個對象的事件賦給處理事件的過程,最后真正處理事件的將是最后賦值的那個過程。例如,有
類TMyClass中定義了一個事件OnSomeFired,在類TClientClass中該類被實例化,它的事件被處理。如下所示:
constructor TClientClass.Create;
var
myObj: TMyClass;
begin
//…
myObj:= TMyClass.Create;
myObj.OnSomeFired:= SomeFired1;
myObj.OnSomeFired:= SomeFired2;
這里的SomeFired1與SomeFired2都是TClientClass中定義的處理過程。其最終的結果是當OnSomeFired事件發生時,只有SomeFired2被調用。
但在編程的實際中,往往需要一個事件被多個方法所處理。為此,我參考了一些對這個問題的解決辦法,總結得出了一個自己的方法,稱為事件的多處理機制。
原理
Delphi 7中的事件本質上是一種過程指針。但事件類型在定義時要比一般過程指針在最后多一個“of object”,如常用的TNotifyEvent的定義是:
TNotifyEvent = procedure(Sender: TObject) of object;
因此,給一個事件屬性賦值,也就是給一個類的過程指針類型的成員變量賦值,當然是最后一次的賦值才有效。要想多次賦值有效就必須有一個數據結構把每次賦
值賦給的過程指針變量都記錄下來,最合適的數據結構當然是列表TList。但如果在每一個有事件的類中都加一個記錄事件賦值的列表對象,自然是不方便的,
而且使用這個列表的代碼在不同類中也差不多,應該抽取出來形成一個類。這個類就是事件多處理機制的核心內容。
做法
要記錄事件處理過程,就需要將過程指
針變量放入列表對象中。但列表對象只能添加指針類型對象(也就是引用類型),而過程指針變量是指類型變量,不能直接添加。這就需要有一個類對過程指針變量
進行包裝,轉化為指針類型對象。于是,先要定義包裝類TCallMethod,如下所示:
TCallMethod = class
private
_callback: TMethod;
public
property Callback: TMethod read _callback write _callback;
end;
這里的TMethod是Delphi預定義的記錄類型,任何過程指針變量都可以強制轉化為這種類型。之后,再定義記錄處理過程的類,如下所示:
TEventObject = class
private
callList: TList;
function GetItem(i: Integer): TMethod;
function GetCount: Integer;
public
constructor Create;
procedure Add(p: TMethod);
procedure Remove(p: TMethod);
property Count: Integer read GetCount;
property Items[i: Integer]: TMethod read GetItem; default;
end;
下面是實現部分:
constructor TEventObject.Create;
begin
callList:= TList.Create;
end;
procedure TEventObject.Add(p: TMethod);
var
a: TCallMethod;
begin
a:= TCallMethod.Create;
a.Callback:= p;
callList.Add(a);
end;
procedure TEventObject.Remove(p: TMethod);
var
i: Integer;
begin
for i:= 0 to GetCount - 1 do
if (TCallMethod(callList[i]).Callback.Code = p.Code) and
(TCallMethod(callList[i]).Callback.Data = p.Data) then
callList.Delete(i);
end;
function TEventObject.GetCount: Integer;
begin
Result:= callList.Count;
end;
function TEventObject.GetItem(i: Integer): TMethod;
var
a: TCallMethod;
begin
a:= TCallMethod(callList[i]);
Result:= a.Callback;
end;
從上面的代碼可以看到,TEventObject的目的是包裝TList對象,屏蔽了TList類的大多數方法,只對外暴露了2個過程、1個屬性與1個索引,分別用于添加、刪除處理事件過程、獲得過程個數及通過序號檢索記錄的過程變量。
使用
使用時,在需要使用事件的類中將事件的類型由過程指針改為TEventObject,即可使該事件擁有多處理的能力。如下面代碼所示:
TMyClass=class
private
someFired: TEventObject;
public
constructor Create;
procedure DoSth;
property OnSomeFired: TEventObject read someFired write someFired; // 多處理事件
end;
以下是實現代碼:
constructor TMyClass.Create;
begin
Self.someFired:= TEventObject.Create;
end;
procedure TMyClass.DoSth;
var
i: Integer;
method: TNotifyEvent;
begin
if someFired.Count > 0 then
for i:= 0 to someFired.Count - 1 do
begin
method:= TNotifyEvent(someFired[i]); // 在該類中事件的真正類型是TNotifyEvent,因此在觸發事件時先要轉成這種類型的過程指針后再進行調用
method(Self);
end;
end;
定義了一個包含多處理事件的類后,再看看這種類如果在其客戶類中被調用。如以下代碼:
{類TClientClass中}
var
myObj: TMyClass;
//…
myObj:= TMyClass.Create;
myObj.OnSomeFired.Add(SomeFired1);
myObj.OnSomeFired.Add(SomeFired2);
當客戶類代碼中調用TMyClass的DoSth方法時,事件將被觸發,而2個處理過程SomeFired1與SomeFired2則會依次調用,實現多處理的目的。
再發展一步
上面的TEventObject
類可以實現事件多處理的目的,但它對加入其中的過程指針類型沒有檢查,這是一個隱患。因此可以針對每一種事件要求的過程指針類型從
TEventObject繼承一個類,實現類型檢查。如要求事件的類型是TNotifyEvent,就可以繼承一個TNotifyEventObject
類,如下面代碼:
TNotifyEventObject = class(TEventObject)
public
procedure Add(p: TNotifyEvent); overload;
procedure Remove(p: TNotifyEvent); overload;
end;
以下是實現部分:
procedure TNotifyEventObject.Add(p: TNotifyEvent);
begin
inherited Add(TMethod(p));
end;
procedure TNotifyEventObject.Remove(p: TNotifyEvent);
begin
inherited Remove(TMethod(p));
end;
在這個類中重載了添加與移除方法,可以有效地對過程指針類型進行檢查。