後一頁
前一頁
回目錄
回首頁
第十九章 Delphi自定義部件開發(二)

19.2.2 Delphi部件程式設計 

19.2.2.1 建立屬性 

 屬性(Property)是部件中最特殊的部分,主要因為部件用戶在設計時可以看見和操作它們,並且在互動過程中能立即得到返回結果。屬性也很重要,因為如果將它們設計好後,將使用戶更容易地使用,自己維護起來也很容易。

  為了使你在部件中更好地使用屬性,本部分將介紹下列內容:

 ● 為什麼要建立屬性

  ● 屬性的種類

 ● 公佈(publishing)繼承的屬性

  ● 定義部件屬性

  ● 編寫屬性編輯器

 

  1. 為什麼要建立屬性

  屬性提供非常重要的好處,最明顯的好處是屬性在設計時能出現在Object Inspector視窗中,這將簡化程式設計工作,因為你只需讀用戶所賦的值,而不要處理構造物件的參數。

  從部件使用者的觀點看,屬性象變數。用戶可以給屬性賦值或讀值,就好象屬性是物件的欄位。

  從部件編寫者的觀點看屬性比物件的欄位有更強的功能;

  ぇ 用戶可以在設計時設定屬性

  這是非常重要的,因為不象方法,只能在執行時存取。屬性使用戶在執行程式之前就能定制部件,通常你的部件不應包含很多的方法,它們的功能可以通過屬性來實現。

  え 屬性能隱藏詳細的實現細節

  ぉ 屬性能引起簡單地賦值之外的響應,如觸發事件

  お 用於屬性的實現方法可以是虛擬方法,這樣看似簡單的屬性在不同的部件中,將實現不同的功能。

 

 2. 屬性的型式

  屬性可以是函數能返回的任何型式,因為屬性的實現可以使用函數。所有的Pascal型式,相容性規則都適用屬性。為屬性選擇型式的最重要的方面是不同的型式出現在Object Inspector視窗中的方式不同。Object Inspector將按不同的型式決定其出現的方式。

你也能在註冊部件時描述不同的屬性編輯器。

  下表列出屬性出現在Object Inspector視窗中的方式

 

表19.3 屬性出現在Object Inspector視窗中的方式

───────────────────────────────────────

屬性型式       處 理 方 式

———————————————————————————————————————

簡單型式   Numeric、Character和 String屬性出現在Object Inspector中,用戶可

以直接編輯

枚舉型式 枚舉型式的屬性顯示值的方式定義在代碼中。選擇時將出現下拉  

式列示方塊,顯示所有的可能取值。

集合型式 集合型式出現在Object Inspector視窗中時正如一個集合,展開後,用         

戶通過將集合元素設為True或False來選擇。

物件型式 作為物件的屬性本身有屬性編輯器,如果物件有自己的published屬

性,用戶在Object Inspector中通過展開物件屬性列,可以獨立編輯它們,

物件型式的屬性必須從TPersistent繼承。

數群群組型式 數群群組屬性必須有它們自己的屬性編輯器,Object Inspector沒有內嵌對數

群群組屬性編輯的支援。  

───────────────────────────────────────

 

3. 公佈繼承的屬性

  所有部件都從祖先型式繼承屬性。當你從已有部件繼承時,新部件將繼承祖先型式的所有屬性。如果你繼承的是抽象類,則繼承的屬性是protected或public,但不是published。如想使用戶存取protected或public屬性,可以將該屬性重定義為published。如果你使用TWinControl繼承,它繼承了Ctl3D屬性,但是protected的,因此用戶在設計和執行時不能存取Ctl3D,通過在新部件中將Ctl3D重聲明為published,就改變了Ctl3D的存取級別。下面的代碼演示如何將Ctl3D聲明為published,使之在設計時可被存取。

 

  type

TSampleComponent=class(TWinControl)

published

property Ctl3D;

end;

 

4. 定義部件屬性

  ぇ 屬性的聲明

  聲明部件的屬性,你要描述:

 ● 屬性名

  ● 屬性的型式

  ● 讀和設定屬性值的方法

 

  至少,部件屬性應當定義在部件物件聲明的public部分,這樣可以在執行時很方便地從外部存取;為了能在設計時編輯屬性,應當將屬性在published部分聲明,這樣屬性能自動顯示在Object Inspector視窗中。下面是典型的屬性聲明:

 

  type

TYourComponent=class(TComponent)

…

private

FCount: Integer { 內部存儲欄位 }

function GetCount: Integer; { 讀方法 }

procedure SetCount(ACount: Integer); { 寫方法 }

pubilic

property Count: Integer read GetCount write SetCount;

end;

 

  え 內部數據存儲

  關於如何存儲屬性的數據值,Delphi沒有特別的規定,通常Delphi部件遵循下列規定:

 ● 屬性數據存儲在物件的數據欄位處

  ● 屬性物件欄位的標識符以F開頭,例如定義在TControl中的屬性FWidth

  ● 屬性數據的物件欄位應聲明在private部分

 

  後代部件只應使用繼承的屬性自身,而不能直接存取內部的數據存儲。

  ぉ 直接存取

  使屬性數據可用的最簡單的辦法是直接存取。屬性聲明的read 和write部分描述了怎樣不通過呼叫存取方法來給內部數據欄位賦值。但一般都用read進行直接存取,而用write進行方法存取,以改變部件的狀態。

  下面的部件聲明演示了怎樣在屬性定義的read 和write部分都採用直接存取:

 

  type

TYourComponent=class(TComponent)

…

private { 內部存儲是私有 }

FReadOnly: Boolean; { 聲明存檔屬性值的欄位 }

published { 使屬性在設計時可用 }

property ReadOnly: Boolean read FReadOnly write FReadOnly;

end;

 

  お 存取方法

  屬性的聲明語法允許屬性聲明的read和write部分用存取方法取代物件私有數據欄位。不管屬性是如何實現它的read 和write部分,方法實現應當是private,後代部件只能使用繼承的屬性存取。

  ヾ 讀方法

屬性的讀方法是不帶參數的函數,並且返回同屬性相同型式的值。通常讀函數的名字是“Get”後加屬性名,例如,屬性Count的讀方法是GetCount。不帶參數的唯一例外是數群群組屬性。如果你不定義read方法,則屬性是只寫的。

  ゝ 寫方法

  屬性的寫方法總是只帶一個參數的過程。參數可以是引用或值。通常過程名是"Set"加屬性名。例如,屬性Count的寫方法名是SetCount。參數的值採用設定屬性的新值,因此,寫方法需要執行在內部存儲數據中寫的操作。

  如果沒有聲明寫方法,那麼屬性是唯讀的。

  通常在設定新值前要偵測新值是否與目前值不同。

  下面是一個簡單的整數屬性Count的寫方法:

 

  procedure TMyComponent.SetCount( value: Integer);

begin

if value <>FCount then

begin

FCount := Value;

update;

end;

end;

 

か 缺省屬性值

  當聲明一個屬性,能有選擇地聲明屬性的缺省值。部件屬性的缺省值是部件構造方法中的屬性值集。例如,當從Component Palette選擇某部件置於窗體中時,Delphi通過呼叫部件構造方法建立部件,並決定部件屬性初始值。

  Delphi使用聲明缺省值決定是否將屬性值存在DFM文件中。如果不描述缺省值,Delphi將總是存檔該屬性值。聲明缺省值的方法是在屬性聲明後加default指令,再跟缺省值。

  當重聲明一個屬性時,能夠描述沒有缺省值的屬性。如果繼承的屬性已有一個,則設立沒有缺省值的屬性的方法是在屬性聲明後加nodefault指令。如果是第一次聲明屬性,則沒有必要加nodefault指令,因為沒有default指令即表示如此。

  下例是名為IsTrue的布爾型式屬性設定缺省值True的過程:

 

  type

TSampleComponent=class(TComponent)

private

FIsaTrue: Boolean;

pubilic

constructor Create (AOwner: TComponent); Overvide;

published

property Istrue: Boolean read FIsTrue write FIsTrue default True;

end;

 

constructor TSampleComponent.Create (AOwner: TComponent);

begin

inherited Create ( Aowner);

Fistvue := True; { 設定缺省值 }

end;

 

5. 編寫屬性編輯器

Object Inspector提供所有型式屬性的缺省編輯器,Delphi也支援通過編寫和註冊屬性編輯器的方法為屬性設計自己的編輯器。可以註冊專門為自定義部件的屬性設計的編輯器,也可設計用於所有某型式的屬性。編寫屬性編輯器需要下列五個步驟:

● 繼承一個屬性編輯器物件

 ● 將屬性作為文本編輯

  ● 將屬性作為整體編輯

  ● 描述編輯器屬性

 ● 註冊屬性編輯器

 

  ぇ 繼承屬性編輯器物件

  DsgnIntf庫單元中定義了幾種屬性編輯器。它們都是從TPropertyEditor繼承而來。當建立屬性編輯器時,可以直接從TPropertyEditor中繼承或從表中的任一屬性編輯器中繼承。

 

 

表19.4 屬性編輯器的型式

─────────────────────────────────────

  型式 編輯的屬性

—————————————————————————————————————

TOrdinalProperty    所有有序的屬性(整數、字元、枚舉)

TIntegerProperty    所有整型,包括子界型式

TCharProperty     字元型式或字元子集

TEnumProperty   任何枚舉型式

TFloatProperty   所有浮點數

TStringProperty   字元串,包括定長的字元串

TSetElementProperty 集合中的獨立元素

TSetElementProperty 所有的集合,並不是直接編輯集合型式,而是展開成一列

集合元素屬性

TClassProperty 物件,顯示物件名,並允許物件屬性的展開

TMethodPropevty 方法指標,主要指事件

TComponentProperty 相同窗體中的部件,用戶不能編輯部件的屬性,

但能指向相容的部件

TColorProperty    部件顏色,顯示顏色常數,否則顯示十六進制數

TFontNameProperty 字形標簽

TFontProperty 字形,允許展開字形的屬性或彈出字形對話方塊

─────────────────────────────────────

 

 

 

下面是TFloatPropertyEditor的定義:

 

type

TFloatProperty=Class(TPropertyEditor)

public

function AllEqual: Boolean; override;

function GetValue: String; override;

procedure SetValue ( Const Value: string ); override;

end;

 

え 象文本一樣編輯屬性

  所有的屬性都需要將它們的值在Object Inspector視窗中以文本的方式顯示。屬性編輯器物件提供了文本表現和實際值之間轉換的虛方法。這些虛方法是GetValue和SetValue,你的屬性編輯器也能繼承了一系列的方法用於讀和寫不同型式的值。見下表:

 

表19.5 讀寫屬性值的方法

──────────────────────────

屬性型式   "Get"方法 "Set"方法

——————————————————————————

浮點數 GetFloatValue SetFloatVallue

方法指標 GetMethodValue SetMehodValue

有序型式 GetOrdValue SetOrdValue

字元串 GetStrValue SetStrValue

──────────────────────────

 

當覆蓋GetValue方法時,呼叫一個"Get"方法;當覆蓋SetValue方法時呼叫一個"Set"方法。

  屬性編輯器的GetValue方法返回一個字元串以表現目前屬性值。缺省情況下GetValue返回"unknown"。

屬性編輯器的SetValue接收Object Inspector視窗String型式的參數,並將其轉換成合適的型式,並設定屬性值。

  下面是TIntegerProperty的GetValue和SetValue的例子:

 

function TIntegerProperty GetValue: string;

begin

Result := IntToStr (GetOrdValue);

end;

 

proceduve TIntegerPropertySetValue (Const Value: string);

var

L: Longint;

begin

L := StrToInt(Value); { 將字元串轉換為數學 }

with GetTypeData (GetPropType)^ do

if ( L < Minvalue ) or ( L > MaxValue ) then

Raise EPropertyError.Create (FmtloadStr(SOutOfRange,

[MinValue,MaxValue]));

SetOrdValue (L);

end;

 

ぉ 將屬性作為一個整體來編輯

  Delphi支援提供用戶以對話方塊的方式可視化地編輯屬性。這種情況常用於對物件型式屬性的編輯。一個典型的例子是Font屬性,用戶可以找開Font對話方塊來選擇字形的屬性。

  提供整體屬性編輯對話方塊,要覆蓋屬性編輯物件的Edit方法。Edit方法也使用"Get"和"Set"方法。

 在大多數部件中使用的Color屬性將標準的Windows顏色對話方塊作為屬性編輯器。下面是TColorProperty的Edit方法

 

procedure TColorProperty.Edit

var

ColorDialog: TColorDialog;

begin

ColorDialog := TColorDialog.Create(Application); { 建立編輯器 }

try

ColorDialog.Color := GetOrdValue; { 使用已有的值 }

if ColorDialog.Execute then

  SetOrdValue (ColorDialog.Color);

finally

ColorDialog.Free;

end;

 end;

 

お 描述編輯器的屬性

  屬性編輯必須告訴Object Inspector視窗如何採用合適的顯示工具。例如Object Inspector視窗需要知道屬性是否有子屬性,或者是否能顯示可能取值的清單。描述編輯器的屬性通常覆蓋屬性編輯器的GetAttributes方法。

GetAttributes返回TPropertyAttributes型式的集合。集合中包括表中任何或所有的值:

 

表19.6 屬性編輯器特征標誌

   ──────────────────────────────

  標誌 含 義 相關方法

   ——————————————————————————————

paValuelist 編輯器能給予一群群組枚舉值 GetValues

paSubPropertie 屬性有子屬性 GetPropertises

paDialog 編輯器能顯示編輯對話方塊 Edit

PaMultiSelect 當用戶選擇多於一個部件

時,屬性應能顯示 N/A

   ──────────────────────────────

 

Color屬性是靈活的,它允許在Object Inspector視窗中以多種方式選擇他們。或者鍵入,或者從清單中選擇定編輯器。因此TColorProperty的GetAttributes方法在返回值中包含多種屬性。

 

  function TColorProperty.GetAttributes: TProrertyAttributes;

begin

Result := [PaMultiselect, paDialog, paValuelist];

end;

 

 か 註冊屬性編輯器

  一旦建立屬性編輯器,必須在Delphi中註冊。註冊屬性編輯器時,要與某種屬性相聯。

  呼叫RegisterPropertyEditor過程來註冊屬性編輯器。該過程接受四個參數:

  ● 要編輯的屬性的型式資訊的指標。這總是通過呼叫呼叫TypeInfo函數得到,如TypeInfo ( TMyComponent )

  ● 編輯器應用的部件型式,如果該參數為nil則編輯器應用於所給的型式的所有屬性

  ● 屬性名,該參數只有在前一參數描述了部件的情況下才可用

  ● 使用該屬性編輯器的屬性的型式

 

  下面引用了註冊標準部件的過程:

 

procedure Register;

begin

RegisterPropertyEditor (TypeInfo(TComponent), nil, TComponentProperty,

RegisterPropertyEditor(TypeInfo(TComponentName), TComponent,

'Name', (ComponentNamePropety);

RegisterPropertyEditor (TypeInfo(TMenuItem), TMenu, '', TMenuItemProperty);

end;

 

  這三句表達式使用RegisterPropertyEditor三種不同的用法:

  ● 第一種最典型

它註冊了用於所有TComponent型式屬性的屬性編輯器TComponentProperty。通常,當為某種型式屬性註冊屬性編輯器時,它就能應用於所有這種型式的屬性,因此,第二和第三個參數為nil。

  ● 第二個表達式註冊特定型式的屬性編輯器

它為特定部件的特定屬性註冊屬性編輯器,在這種情況下,編輯器用於所有部件的Name屬性。

 ● 第三個表達式介於第一個和第二個表達式之間

它為部件TMenu的TMenuItem型式的所有屬性註冊了屬性編輯器。

 

19.2.2.2 建立事件

 

  事件是部件的很重要的部分。事件是部件必須響應的系統事件與響應事件的一段代碼的聯接。響應代碼被稱為事件處理過程,它總是由部件用戶來編寫。通過使用事件,應用開發者不需要改變部件本身就能定制部件的行為。作為部件編寫者,運用事件能使應用發者定制所有的標準Delphi部件。要建立事件,應當理解:

  ● 什麼是事件

  ● 怎樣實現標準事件

  ● 怎樣定義自己的事件

 

1. 什麼是事件

事件是聯接發生的事情與某些代碼的機制,或者說是方法指標,一個指向特定物件實例的特定方法的指標。從部件用戶的角度,事件是與系統事件(如OnClick)有關的標簽,用戶能給該事件賦特定的方法供呼叫。例如,按鈕Buttonl有OnClick方法,缺省情況下Delphi在包含該按鈕的窗體中產生一個為ButtonlClick的方法,並將其賦給OnClick。當一個Click事件發生在按鈕上時,按鈕呼叫賦給OnClick的方法ButtonlClick:

部件用戶將事件看作是由用戶編寫的代碼,而事件發生時由系統呼叫的處理辦法。

  從部件編寫者角度事件有更多的含義。最重要的是提供了一個讓用戶編寫代碼響應特定事情的場所。

  要編寫一個事件,應當理解:

 ● 事件和方法指標

  ● 事件是屬性

  ● 事件處理過程型式

 ● 事件處理過程是可選的

 

  ぇ 事件是方法指標

  Delphi使用方法指標實現事件。一個方法指標是指向特定物件實例的特定方法的特定指標。作為部件編寫者,能將方法指標作為一種容器。你的代碼一發現事情發生,就呼叫由用戶定義的方法。  

  方法指標的工作方式就象其它的過程型式,但它們保持一個隱含的指向物件實例的指標。所有的控制都繼承了一個名為Click的方法,以處理Click事件。Click方法呼叫用戶的Click事件處理過程。

 

  procedure TControl.Click;

begin

if Assigned(OnClick ) then OnClick( Self );

end;

 

如果用戶給Control的OnClick事件賦了處理過程(Handle),那滑鼠點按Control時將導致方法被呼叫。

  え 事件是屬性

  部件採用屬性的形式實現事件。不象大多數其它屬性,事件不使用方法來使實現read和write部分。事件屬性使用了相同型式的私有物件欄位作為屬性。按約定網欄位標簽在屬性名前加“F”。例如OnClick方法的指標,存在TNotifyEvent型式FOnClick欄位中。OnClick事件屬性的聲明如下:

 

type

TControl=class ( TComponent )

private

FOnClick: TNofiFyEvent; { 聲明存檔方法指標的欄位 }

protected

property OnClick: TNotifyEvent read FOnClick write FOnClick;

end;

 

 象其它型式的屬性一樣,你能在執行時設定和改變事件的值。將事件做成屬性的主要好處是部件用戶能在設計時使用Object Inspector設定事件處理過程。

  ぉ 事件處理過程型式

  因為一個事件是指向事件處理過程的指標,因此事件屬性必須是方法指標型式,被用作事件處理過程的代碼,必須是相應的物件的方法。

  所有的事件方法都是過程。為了與所給型式的事件相容,一個事件處理過程必須有相同數目和相同型式的相同順序的參數。Delphi定義了所有標準事件處理過程的方法型式,當你建立自己的事件時,你能使用已有的事件型式,或建立新的。雖然不能用函數做事件處理過程,但可以用var參數得到返回資訊。

  在事件處理過程中傳遞var參數的典型例子是TKeyPressEvent型式的KeyPressed事件。TKeyPressEvent定義中含有兩個參數。一個指示哪個物件產生該事件。另一個指示那個鍵按下:

 

  type

TKeyPressEvent=procedure( Sender: TObject; var key: char) of Object;

 

通常key參數包含用戶按下鍵的字元。在某些情況下,部件的用戶可能想改變字元值。例如在編輯器中強制所有字元為大寫,在這種情況下,用戶能定義下列的事件處理過程:

 

 procedure TForml.EditlKeyPressed( Sender: TObject; var key: char);

begin

key := Upcase( key );

end;

 

也可使用var參數讓用戶覆蓋缺省的處理。

  お 事件處理過程是可選的

  在為部件建立事件時要記住部件用戶可能並不編寫該事件的處理過程。這意味著你的部件不能因為部件用戶沒有編寫處理代碼而出錯。這種事件處理過程的可選性有兩個方面:

  ヾ 部件用戶並非不得不處理事件

事件總是不斷地發生在Windows應用程式中。例如,在部件上方移動滑鼠就引起Windows發送大量的Mouse-Move訊息給部件,部件將滑鼠訊息傳給OnMouseMove事件。在大多數情況下,部件用戶不需要關心MouseMove事件,這不會產生問題,因為部件不依賴滑鼠事件的處理過程。同樣,自定義部件也不能依賴用戶的事件處理過程。

  ゝ 部件用戶能在事件處理過程寫任意的代碼

  一般說來,對用戶在事件處理過程中的代碼沒有限制。Delphi部件庫的部件都支援這種方式以使所寫代碼產生錯誤的可能性最小。顯然,不能防止用戶代碼出現邏輯錯誤。

  2. 怎樣實現標準事件

  Delphi帶的所有控制繼承了大多數Windows事件,這些就是標準事件。儘管所有這些事件都嵌在標準控制中,但它們缺省是protected,這意味著用戶無法存取它們,當建立控制時,則可選擇這些事件使用戶可用。將這些標準事件嵌入自定義控制需要考慮如下:

  ● 什麼是標準事件

  ● 怎樣使事件可見

  ● 怎樣修改標準事件處理過程

 

  ぇ 什麼是標準事件

  有兩種標準事件:用於所有控制和只用於標準Windows控制。

  最基本的事件都定義在物件TControl中。視窗控制、圖形控制和自定義控制都繼承了這些事件,下面列出用於所有控制的事件:

  OnClick OnDragDrop OnEndDrag OnMouseMove

 OnDblClick OnDragOver OnMouseDown OnMouseUp

 

  所有標準事件在TControl中都定義了相應的protected動態方法,只是沒有加“On”例如OnClick事件呼叫名為Click的方法。

  標準控制(從TWinControl繼承)具有下列事件:

 OnEnter OnKeyDown OnkeyPress OnKeyUp OnExit

 

正如TControl中的標準事件,視窗控制也有相應protected動態方法。

  え 怎樣使事件可見

  標準事件的聲明是protected,如果想使用戶在執行時或設計時能存取它們,就需要將它們重聲明為public和 published。重聲明屬性而不描述它的實現將繼承相同的實現方法,只是改變了存取級別。例如,建立一個部件並使它的OnClick事件出現在執行時,你可增加下面的部件聲明:

 

  type

TMyControl=class(TCustomControl)

published

property OnClick; { 使OnClick在objectinspector中可見 }

end;

 

ぉ 怎樣修改標準事件處理過程

  如果想修改自定義部件響應某種事件的方法,可以重寫代碼並將其賦給事件。將聯接每個標準事件的方法聲明的protected是出於慎密的考慮。通過,覆蓋實現方法,能修改內部事件處理過程,通過呼叫繼承的方法,能保持標準事件處理過程。

  呼叫繼承的方法的順序是很重要的。一般首先呼叫繼承的方法,允許用戶的事件處理過程代碼在你的定制代碼前執行。然而也有在呼叫繼承的方法之前執行自己的代碼情況出現。

  下面是一個覆蓋Click事件的例子:

 

procedure TMyControl.Click;

begin

inherited Click; { 執行標準處理,包括呼叫事件處理過程你自己的定制代碼 }

end;

 

3. 定義自己的事件

  定義全新的事件的情況是很少見的。只有當部件的行為完全不同於任何其它事件才需要定義新事件。定義新事件一般包含三個步驟:

  ● 觸發事件

   ● 定義處理過程型式

   ● 聲明事件

  ● 呼叫事件

 

ぇ 觸發事件

  定義自己的事件要遇到的第一個關鍵是:當使用標準事件時你不需要考慮由什麼觸發事件。對某些事件,問題是顯然的。例如:一個MouseDown事件是在用戶按下滑鼠的左鍵時發生,Windows給應用發送WM_LBUTTONDOWN訊息。接到訊息後,一個部件呼叫它的MouseDown方法,它依次呼叫用戶的OnMouseDown事件處理過程代碼。但是有些事件卻不是那麼可以描述清楚的。例如:滾行槓有一個OnChange事件,可被各種情況觸發,包括按鍵、滑鼠點按或其它按制中的改變。當定義事件時,你必須使各種情況的發生呼叫正確的事件。

  這�埵�TControl處理WM_LBUTTONDOWN訊息的方法,DoMouseDown是私有的實現方法,它提供了一般的處理左、右和中按鈕的方法,並將Windows訊息的參數轉換為MouseDown方法的值。

 

type

TControl = class(TComponent)

private

FOnMouseDown: TMouseEvent;

procedure DoMouseDown(var Message: TWMMouse; Button: TMouseButton;

Shift: TShiftState);

procedure WMLButtonDown(var Message: TWMLButtonDown);

message M_LBUTTONDOWN;

protected

procedure MouseDown(Button: TMouseButton; Shift: TShiftState;

X, Y: Integer); dynamic;

end;

 

procedure TControl.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);

begin

if Assigned(FOnMouseDown) then

FOnMouseDown(Self, Button, Shift, X, Y); { 呼叫事件處理過程 }

end;

 

procedure TControl.DoMouseDown(var Message: TWMMouse; Button: TMouseButton;

Shift: ShiftState);

begin

with Message do

MouseDown(Button, KeysToShiftState(Keys) + Shift, XPos, YPos); { 呼叫動態方法 }

end;

 

procedure TControl.WMLButtonDown(var Message: TWMLButtonDown);

begin

inherited; { perform default handling }

if csCaptureMouse in ControlStyle then

MouseCapture := True;

if csClickEvents in ControlStyle then

Include(FControlState, csClicked);

DoMouseDown(Message, mbLeft, []); { 呼叫傳統的mouse-down 方法 }

end;

 

  當兩種事情-狀態變化和用戶互動??發生時,處理機制是相同的,但過程稍微不同。用戶互動事件將總是由Windows訊息觸發。狀態改變事件也與Windows訊息有關,但它們也可由屬性變化或其它代碼產生。你擁有對自定義事件觸發的完全控制。

え 定義處理過程型式

  一旦你決定產生事件,就要定義事件如何被處理,這就是要決定事件處理過程的型式。在大多數情況下,定義的事件處理過程的型式是簡單的通知型式(TNotifyEvent)和已定義的事件型式。

  通知事件只是告訴你特定的事件發生了,而沒有描述什麼時候和什麼地方。通知事件使用時只帶一個TObject型式的參數,該參數是Sender。然而所有通知事件的處理過程都知道是什麼樣的事件發生和發生在那個部件。例如:Click事件是通知型式。當編寫Click事件的處理過程時,你知道的是Click事件發生和哪個部件被點按了。通知事件是單向過程。沒有提供反饋機制。

  在某些情況下,只知道什麼事件發生和發生在那個部件是不夠的。如果按鍵事件發生,事件處理過程往往要知道用戶按了哪個鍵。在這種情況下,需要事件處理過程包含有關事件的必要資訊的參數。如果事件產生是為了響應訊息,那麼傳遞給事件的參數最好是直接來自訊息參數。

  因為所有事件處理過程都是過程,所以從事件處理過程中返回資訊的唯一方法是通過var參數。自定義部件可以用這些資訊決定在用戶事件處理過程執行後是否和怎樣處理事件。

  例如,所有的擊鍵事件(OnKeyDown、OnKeyUp和OnKeyPressed)通過名為key的var參數傳遞鍵值。為了使應用程式看見包含在事件中的不同的鍵,事件處理過程可以改變key變數值。

 

ぉ 聲明事件

  一旦你決定了事件處理過程的型式,你就要準備聲明事件的方法指標和屬性。為了讓用戶易於理解事件的功能,應當給事件一個有意義的名字,而且還要與部件中相似的屬性的標簽保持一致。

  Delphi中所有標準事件的標簽都以“On”開頭。這只是出於方便,編譯器並不強制它。Object Inspector是看屬性型式來決定屬性是否是事件,所有的方法指標屬性都被看作事件,並出現在事件頁中。

お 呼叫事件

  一般說來,最好將呼叫集中在事件上。就是說在部件中建立一個虛方法來呼叫用戶的事件處理過程和提供任何缺省處理。當呼叫事件時,應考慮以下兩點:

  ● 必須允許空事件

● 用戶能覆蓋缺省處理

 

不能允許使空事件處理過程產生錯誤的情況出現。就是說,自定義部件的正常功能不能依賴來自用戶事件處理過程的響應。實際上,空事件處理過程應當產生與無事件處理過程一樣的結果。

  部件不應當要求用戶以特殊方式使用它們。既然一個空事件處理過程應當與無事件處理過程一樣動作,那麼呼叫用戶事件處理過程的代碼應當象這樣:

 

  if Assigned(OnClick) then OnClick(Self);

{ 執行缺省處理 }

 

而不應該有這樣的代碼:

 

if Assigned(OnClick) then

OnClick(Self)

else

…; { 執行缺省處理 }

 

  對於某些種類的事件,用戶可能想取代缺省處理甚至解除所有的響應。為支援用戶實現這種功能,你需要傳遞var參數給事件處理過程,並在事件處理過程返回時偵測某個值。空事件處理過程與無事件處理過程有相同作用。因為空事件處理過程不會改變任何var參數值。所以缺省處理總是在呼叫空事件處理過程後發生。

  例如在處理Key-Press事件,用戶可以通過將var參數key的值設定為空字元(#0)來壓制部件的缺省處理,代碼如下:

 

  if Assigned(OnkeyPress) then OnkeyPress(Self key);

if key <> #0 then { 執行缺省處理 } ;

 

實際的代碼將與這稍有不同,因為它只處理視窗訊息,但處理邏輯是相同的。在缺省情況下,部件先呼叫任何用戶賦予的事件處理過程,然後執行標準處理。如果用戶的事件處理過程將key設為空,則部件跳過缺省處理。

 

19.2.2.3 處理訊息

 

  在傳統Windows程式設計中,一個很關鍵的方面是處理Windows發送給應用程式的訊息。Delphi已經幫你處理了大多數的普通訊息,但是在建立部件的過程中有可能Delphi沒有處理方法,得由自己處理訊息,也可能建立了新的訊息需要處理它們。

  學習掌握Delphi的訊息處理,要掌握以下三個方面:

 ● 理解訊息處理系統

● 修改(改變)訊息處理方法

● 建立新的訊息處理方法

 

1. 理解訊息處理系統

  所有的Delphi物件內部具有處理訊息的機制,如呼叫訊息處理方法或訊息處理過程。訊息處理的基本思想是物件接收某種訊息並派送它們,這是通過呼叫與接收的訊息相應的方法來實現的,如果沒有相應於訊息的指定的方法,那就呼叫缺省處理。下面的圖解表示訊息派送系統:

Delphi部件庫定義了將所有Windows訊息(包括用戶自定義訊息)直接轉換到物件方法呼叫的訊息派送系統。一般沒有必要改變這種訊息派送系統,只要建立訊息處理方法。

  ぇWindows訊息中有什麼?

Windows訊息是包含若干有用的欄位的數據記錄。記錄中最重要的是一個整型大小的值,該值標識訊息。Windows定義了大量的訊息。庫單元Messages聲明了所有訊息的標識。訊息中其它的有用資訊包括兩個欄位參數和結果欄位。兩個參數分別是16位和32位的。Windows代碼總是以wParam和lParam來引用它們。

  最初,Windows程式員不得不記住包含的每一個參數。現在,微軟公司已經命名了這參數。這樣理解伴隨這些訊息的資訊就更簡單了。例如,WM_KEYDOWN訊息的參數被稱為vkey和keydata,這就比wParam和lParam給出了更多的描述資訊。

  Delphi為不同型式的訊息定義了指定的記錄型式。如滑鼠訊息在long參數中傳遞滑鼠事件的x、y座標,一個在高字,一個在低字。使用滑鼠訊息記錄,你不需要自己關心哪個字是哪個座標,因為引用這些參數時通過名子Xpos和Ypos取代了lParamLo和lParamHi。

  え 派送方法

  當應用程式建立視窗時,在Windows Kernel中註冊了一個視窗過程。視窗過程是處理視窗訊息的函數。傳統上,視窗過程包括了Case表達式,表達式的每個入口是視窗要處理的每一條訊息。當你每次建立視窗時,必須建立完整的視窗過程。

  Delphi在下列三方面簡化了訊息派送:

  ● 每個部件繼承了完整的訊息派送系統

● 派送系統具有缺省處理。用戶只需定義想響應的訊息的處理方法

● 可以修改訊息處理的一部分,依靠繼承的方法完成大多數處理

 

這種訊息派送系統的最大優點是用戶能在任何時候安全地發送任何訊息給任何部件。如果部件沒有為該訊息定義處理方法,那缺省處理方法會解決這個問題,通常是忽略它。

  Delphi為應用程式每種型式的部件註冊了名為MainWndProc的方法作為視窗過程。MainWndProc包含了異常處理塊,它完成從Windows到名為WndProc的虛方法轉送訊息記錄,並且通過呼叫應用程式物件的HandleException方法處理異常。

  MainWndProc是靜態方法,沒有包含任何訊息的指定處理方法。定制過程發生在WndProc中,因為每個部件型式都能覆蓋該方法以適合特定的需要。

  WndProc方法為每個影響它們處理的任何條件進行檢查,以捕捉不要的訊息。例如,當被拖動時,部件忽略鍵盤事件,因此,TWinControl的WndProc只在沒有拖動時轉送鍵盤事件。最後WndProc呼叫Dispatch方法,該方法是從TObject繼承來的靜態方法,決定什麼方法來處理訊息。

  Dispatch使用訊息記錄的Msg欄位來決定怎樣派送特定訊息。如果部件已經給該訊息定義了處理方法,則Dispatch呼叫該方法,反之,Dispatch呼叫缺省處理方法。

  2. 改變訊息處理方法

  在改變自定義部件的訊息處理方法之前,先要弄清楚你真正想要做什麼。Delphi將大多數的Windows訊息轉換成部件編寫者和部件用戶都能處理的事件。一般來說,你應當改變事件處理行為而不是改變訊息處理行為。

為了改變訊息處理行為,要覆蓋訊息處理方法。也能提供捕獲訊息防止部件處理該訊息。

ぇ 覆蓋處理方法

為了改變部件處理特定訊息的方法,要覆蓋那個訊息的處理方法。如果部件不處理該訊息,你就需要聲明新的訊息處理方法。

為了覆蓋訊息處理方法,要在部件中以相同的訊息索引聲明新的方法。不要使用override指令,你必須使用Message指令和相應的訊息索引。

  例如,為了覆蓋一個處理WM_PAINT訊息的方法,你要重聲明WMPaint方法:

 

type

TMyComponent=class(…)

procedure WMPaint(var Message: TWMPaint); message WM_PAINT;

end;

 

え 使用訊息參數

  在訊息處理方法內部,自定義部件存取訊息記錄的所有參數。因為訊息總是var參數,如果需要的話,事件處理過程可以改變參數的值。Result欄位是經常改變的參數。Result是Windows文檔中所指的訊息的返回值:由SendMessage返回。

  因為訊息處理方法的訊息參數的型式隨著被處理的訊息的變化而變化,所以應當參考Windows訊息文檔中的參數的名字和含義。如果出於某種原因要使用舊風格的訊息參數(wParam、lParam),可以配合一般的型式TMessage來決定Message。

  ぉ 捕獲訊息

  在某種情況下,你可能希望自定義部件能忽略某種訊息。就是說,阻止部件將該訊息派送給它的處理方法。為了那樣來捕獲訊息,可以覆蓋虛方法WndProc。

  WndProc方法在將訊息傳給Dispatch方法前屏蔽該訊息。它依次決定哪一個方法來處理訊息。通過覆蓋WndProc,部件得到了派送訊息之前過濾它們的機會。

  通常,象下面這樣覆蓋WndProc:

 

procedure TMyControl.WndProc(var Message: TMessage);

begin

{ 決定是否繼續處理過程 }

inherited WndProc (Message);

end;

 

下面的代碼是TControl的WndProc的一部分。TControl定義整個範圍內的滑鼠訊息,當用戶拖動和放置控制時,它們將被濾過。

 

  procedure TControl WndProc(var Message:TMessage);

begin

if (Message.Msg >= WM_MOVSEFIRST) and

(Message.Msg <= WM_MOUSELAST) then

if Dragging then

DragMouseMsg(TWMMOUSE(Message)) { 處理拖動 }

else

…   { 正常處理其它 }

…   { 否則正常處理 }

end;


後一頁
前一頁
回目錄
回首頁