開發新的VCL 組件 -1 [轉]

開發新的VCL 組件 -1

Delphi 是一個快速的開發工具,利用它能夠很容易地開發出各類應用程序,程序員能夠開發本身
的組件,而且能夠將新的組件加到IDE 組件面板中,這也是Delphi 最重要的特性之一。從廣義上來講,
Delphi 的用戶能夠分爲兩類:應用程序員和組件開發者。本章內容特別適用於準備自行開發Delphi 組
件的用戶。
本章首先介紹VCL(Visual Componet Library)組件及其基本知識,而後依次詳細介紹開發一個
新VCL 組件的基本步驟、接着介紹如何爲組件添加屬性、事件和方法,還介紹了在編寫和使用新組件
過程當中須要注意的事項,最後用幾個不一樣方面的實例說明製做組件的基本過程。其中大部份內容也適
用於開發新的CLX 組件。
16.1 開發組件簡介
16.1.1 什麼是組件
組件是Delphi 應用程序的程序元素。儘管大多數組件表明用戶界面的可見元素,但組件也能夠是
程序中的不可見元素,如數據庫組件。
從應用程序員的角度看,組件是能夠在組件面板上選擇,在窗體設計窗口和代碼窗口中操做的元
素,而且能夠對其編寫必定的事件處理代碼,從而知足必定的功能需求。在實際編程中,組件是能插
入Delphi 開發環境的任何元素,它具備程序的各類複雜性。組件定義只是接口描述。
對於一個組件開發者,Delphi 組件是代碼中的對象,能夠直接或間接地從TComponent 派生出來
的一個ObjectPascal 類。TComponent 定義了全部組件必須具有的、最基本的行爲。例如能夠顯示在組
件面板上以及能夠在表單設計窗口中編輯。可是TComponent 並不知道如何處理組件的具體功能,因
此必須本身描述它。
嚴格說來,組件是具備下列能力的持續化(Persistence)對象。所謂持續化對象是與臨時(Transitory)
對象相對的。臨時對象即沒有辦法保存銷燬以前狀態的對象,持續化對象便可以從表單文件(*.dfm
或*.xfm 文件)或數據模塊中讀取,或向其中寫入自身狀態的對象。組件應該具備以下特性。
*IDE 集成:能夠在IDE 面板中顯示而且能夠在表單設計器中操做。
*擁有者屬性:能夠管理其餘組件。若是組件A 擁有組件B,則當A 被銷燬時,A 有責任銷燬B。
*對輸入/輸出流和文件的支持:這是TPersistent 類的持續化特性的增強。
*COM 支持:能夠經過使用Windows 提供的嚮導,轉化爲ActiveX 控件或其餘COM 對象。
16.1.2 爲何使用組件
程序員製做組件的目的之一,是把大量的重複勞動用組件的方法定製起來,加快軟件開發的效率。
當在Delphi 中添加一個新的軟件包時,實際上就是使用了一個新的類擴展了VCL。這個新類從一個已
有組件的相關類中派生出來,並添加了新的功能。
Delphi 提供了不少現成組件,並且隨着版本更新不斷增長新組件。另外還能夠買到第三方開發的
特點組件,或從Internet 下載免費組件。這些組件足以支持通常應用系統開發。
但應用開發人員仍有必要本身製做組件。採用組件形式能夠把對象嚴密封裝,並加上一層直觀外
殼,有利於軟件調試和代碼重用。開發羣體以組件爲功能單位分工協做,比較容易實現工程化管理,
從軟件規劃設計到測試修改均可以減小意外差錯,大大提升工做效率。成熟的組件還能夠做爲商品軟
件出售,帶來附加效益,有利於軟件開發的社會化分工協做。
node

·412·
16.1.3 Delphi 的組件庫基礎
Delphi 的組件庫包括VCL 和CLX(Component Library for Cross-Platform)。VCL 只用於Windows
開發,CLX 能夠用於Windows 和Linux 平臺開發。組件庫的範圍很是廣,能夠在IDE 中使用,也可
以在運行代碼中建立和使用。有些組件能夠用於全部應用程序,而有些只能用於部分應用程序。
1.組件庫
Delphi 的組件庫是由分散在幾個子庫中的對象組成,每一個子庫有不一樣的應用目的,這些子庫的組
成及描述如表16-1 所示。
表16-1 Delphi 組件子庫的組成及描述
組成部分 描述
BaseCLX 全部CLX 應用程序的底層類。包括運行時庫(Runtime Library,RTL),幷包括了Classes 單元
DataCLX
客戶數據訪問組件。包含與數據庫相關的組件的一部分。這些組件能夠用於跨平臺訪問數據庫的應用程
序,能夠從一個磁盤文件訪問數據庫,也能夠從使用dbExpress 的數據庫服務器訪問數據
NetCLX 創建Web 服務器應用程序的組件,包括了對Apache 或CGI Web 服務器的支持
VisualCLX 跨平臺的GUI 組件和圖形類。它底層使用跨平臺widget 庫(Qt)
WinCLX
只用於Windows 平臺的類。包括通過封裝的本地Windows 控件、使用不能用於Linux 的數據訪問機制
(如BDE 或ADO 等)進行數據庫訪問的組件以及只能在Windows 上使用的組件(如COM、NT 服務)
VCL 和CLX 包含許多一樣的子庫,它們都包括BaseCLX、DataCLX、NetCLX。VCL 包括WinCLX,
而CLX 包括VisualCLX。當須要使用本地的Windows 控件、Windows 的相關特性或擴展一個現存的
VCL 應用程序時應該使用VCL。當須要編寫一個跨平臺的應用程序或者使用能夠在CLX 應用程序中
獲得的控件(例如TLCDNumber)時應該使用CLX。
組件庫中全部對象都是TObject 的後代。TObject 引入了執行基本操做的方法如建立方法、銷燬方
法以及消息處理方法。組件是從類TComponent 派生的,是組件庫的子集。組件能夠放在表單或者數
據模塊中,而且設計時能夠修改它們的屬性。使用Delphi IDE 中的對象編輯器(Object Inspector),
能夠不用寫任何代碼改變組件的屬性值。
運行時可見的組件能夠叫做「可視組件」(Visual Component),而運行時不可見的組件叫做「非可
視組件」(Nonvisual Component)。一些組件能夠在IDE 的組件面板中顯示。
可視組件,如TForm、TSpeedButton,習慣上均可以叫做「控件」(Control),都是從TControl 派
生的。控件在GUI 應用程序中使用,而且運行時用戶能夠見到它們。TControl 提供了指定一個控件顯
示特性的屬性,如高度和寬度。
非可視組件,習慣上也能夠叫做「組件」,能夠用於不一樣的任務。例如當開發一個鏈接數據庫的
程序時,能夠將一個TDataSource 組件放在表單中而且將組件與數據庫聯繫起來,這種聯繫在運行時
用戶是看不到的,所以TDataSource 是非可視組件。設計的時候,非可視組件顯示爲一個圖標,程序
員能夠像可視組件同樣設置它們的屬性和事件。
非組件的類(即從TObject 而不是TComponent 派生的類)也有各類不一樣的用途。其中比較典型
的是用於訪問系統對象(如文件和剪貼板)或者執行一些臨時任務(如儲存數據到一個列表中)的類。
雖然這些類能夠由組件建立,可是設計時不能建立這些類的實例。
2.組件的屬性、方法和事件
VCL 和CLX 都是與IDE 集成的,所以能夠快速開發應用程序。組件庫中的全部類都是基於屬性、
方法和事件的。每一個類都包括數據成員(屬性)、操做數據的函數(方法)以及與用戶交互的途徑(事
件)。VCL 基於Windows API 函數,而CLX 是基於Qt widget 庫的。組件庫自己也是用Delphi 寫的。
(1)屬性
屬性容許在一個簡單、一致的界面下隱藏數據,也能夠在不通知應用程序開發者的狀況下改變屬
第16 章 開發新的VCL 組件
·413·
性信息的結構。它使應用程序開發者能夠像讀寫一個變量同樣對屬性進行讀寫,同時容許組件開發者
隱藏底層的數據結構以及訪問值時的特殊的處理過程。
屬性決定對象顯示出來的行爲和操做。例如Visible 屬性決定一個對象在應用程序界面中是否可
見。設計良好的屬性可使組件更容易被人使用和維護。下面列出了屬性的一些特徵。
*方法只在運行時可用,而屬性在設計時就可用而且能夠改變它的值,而且在IDE 中組件發生變
化時能夠迅速地反饋到它的屬性中。
*能夠在對象編輯器中訪問一些屬性,而且能夠在其中可視地改變對象的值。在設計時改變屬性
的值比使用代碼改變容易的多而且容易維護。
*屬性能檢查應用程序開發者指定給屬性的值的格式和值的有效性,設計時就能夠驗證輸入,預
防錯誤。
*屬性能夠根據須要在建立時設置合適的值。程序員容易犯的一個錯誤就是引用沒有初始化的變
量的值。而經過使用屬性表明數據,能夠保證須要時值永遠是可用的。
*由於數據被封裝了,因此在實際對象中屬性是受保護的(protected)或私有的(private)的。
*能夠調用方法設置或讀取屬性值,所以可使對象的使用者在不知道的狀況下進行特別的處
理。例如數據可能保存在一個表中,可是能夠像一個正常數據成員同樣提供給程序員。
*在訪問屬性時能夠觸發事件和修改數據,例如在程序中修改一個屬性值的同時要求修改其餘數
據時,能夠修改屬性的讀/寫方法。
*屬性能夠是虛擬的。
*屬性能夠不限於單個對象,修改一個對象的一個屬性可以影響其餘幾個屬性。例如在一個
RadioButton 中設置Checked 屬性時可影響同組中的其餘按鈕。
(2)方法
方法是一個與類相關聯的過程。方法定義了一個對象的行爲。類的方法能夠訪問全部的public、
protected 和private 屬性,以及類的域,而且一般是做爲一個成員函數。
組件的方法包括類方法和組件方法。類方法是在一個類中而不是在一個特定的類的實例中執行的
過程或函數。例如每一個組件的構造方法(Create)是類方法。組件方法是在組件的實例中執行的過程
或函數。應用程序開發者使用方法來使組件執行一個操做或獲得須要根據屬性計算才能得到的值。
由於方法要求執行代碼,因此方法只能在運行時調用。使用方法主要有如下優勢。
*方法封裝了組件的功能。
*方法能夠在一個簡單、一致的界面下隱藏複雜的過程。例如應用程序開發者能夠調用組件的
AlignControls 方法,而不須要知道方法如何工做,也不須要知道它和其餘組件的AlignControls 方法有
什麼區別。
*方法能夠在一次調用中修改幾個屬性。
(3)事件
在程序中,程序員沒法精確預測一個用戶將要執行的動做,用戶能夠選擇一個菜單項、單擊一個
按鈕或者輸入一些文本。所以能夠編寫一些代碼處理感興趣的動做而不是寫一些按照指定順序執行的
代碼。
如今許多應用程序都是事件驅動(Event Driven)的,由於它們都會對事件做出響應。
在組件中,事件是一個特殊的屬性,它在運行時根據輸入或其餘行爲調用執行代碼。事件使應用
程序員可以將運行時發生的特定事件,如鼠標和鍵盤動做,與一段代碼聯繫起來。事件發生時執行的
代碼稱爲事件處理過程(Event Handler)。不管事件如何觸發,VCL 對象都會查找是否存在程序員編
寫的事件處理過程。若是存在,代碼就會執行,不然執行默認的事件處理過程。
事件容許應用程序員不須要定義新的組件來對不一樣的輸入做出不一樣的反應。事件能夠分爲以下3
類。
*用戶事件:即用戶觸發的事件,用戶事件的例子有OnClick(用戶單擊鼠標)、OnKeyPress(用程序員

·414·
戶按了鍵盤上的一個鍵)、OnDblClick(用戶雙擊了鼠標)。
*系統事件:即操做系統觸發的事件。例如OnTimer(在預約時間間隔到達時,由Timer 組件觸
發)、OnPaint(組件或窗口須要重畫時)等。一般系統事件不禁用戶行爲發起。
*內部事件:即由應用程序內部的對象觸發的事件。內部事件的一個例子是OnPost,它是當應用
程序向數據庫中提交當前記錄時產生的。
3.對象、組件和控件
圖16-1 所示是簡化了的對象、組件和控件關係圖。
TObject TPersistent TComponent TControl TWincontrol*
[Objects]
[Objects]
Exception [Objects]
[Objects] [Objects] TGraphicControl [Objects]
*在跨平臺應用程序中是T荳莍莇莋莈莟荂
圖16-1 簡化的對象、組件和控件關係圖
每一個對象(類)都是從TObject 繼承。其中能夠在表單設計器中顯示的對象都是從TPersistent 或
TComponent 繼承的。而控件是從TControl 繼承的。有兩種類型的控件:圖形控件,從TGraphicControl
繼承;窗口控件,從TWinControl 或TWidgetControl 繼承。所以,一個相似TCheckBox 的控件繼承了
全部TObject、TPersistent、TComponent、TControl 以及TWinControl 或TWidgetControl 的屬性,而且
本身增長了特別的屬性。
對幾個重要的基類做了簡單的解釋,如表16-2 所示。
表16-2 組件庫中幾個重要基類的說明
類 描述
TObject
VCL 或CLX 中全部對象的祖先和基類。它封裝了全部VCL/CLX 對象的共同的基本方法,如建立、
維護或銷燬一個對象的實例等
Exception 與VCL 異常相關的全部類的基類。它爲錯誤處理提供了一致的接口,使應用程序方便地處理錯誤
TPersistent
實現發佈屬性的全部對象地基類。它的子孫類容許將數據發送到數據流,而且容許將對象的值賦
給另外一個對象
TComponent
全部組件的基類。組件能夠添加到組件面板中,而且能夠在設計時進行操做,組件也能夠擁有其
他的組件
TControl
表明了全部運行時可見的控件的基類。它是全部提供標準的、可視的屬性(如位置、光標)的控
件的祖先類。它也提供了對鼠標動做的反應
TWinControl 或
TWidgetControl
能夠得到鍵盤焦點的全部控件的基類。TWinControl 的後代都叫做窗口控件,而TWidgetControl
的後代都叫做widget
4.TObject 類及TObject 分支類
TObject 封裝了一個對象的基本行爲,提供了下面的方法。
*經過分配、初始化、釋放內存引入了建立、維護和銷燬一個對象實例的方法。
*返回一個類類型和一個對象的實例信息以及關於它的Published 屬性運行時的類型信息(RTTI)
的方法。
*消息處理方法。
*支持對象運行的接口。
對象的不少行爲都是在TObject 引入的方法基礎上實現的。它的不少方法都是在IDE 內部使用的,
通常用戶不須要使用。TObject 是抽象類,程序中不能直接建立TObeject 的實例。雖然TObject 是組
件框架的基礎對象,可是並非全部的對象都是組件,全部的組件都是從TComponent 繼承的。
第16 章 開發新的VCL 組件
·415·
TObject 分支類包括了全部從TObject 但不是從TPersistent 派生的VCL 和CLX 類。TObject 是許
多簡單類的直接祖先。TObject 分支類有一個共同的重要屬性,它們都是臨時對象,而不是持續化對
象,這意味着這些類沒有辦法保存在銷燬以前的狀態。
這個分支的主要類之一是Exception 類,這個類提供了大量的內建的異常類,能夠自動處理程序
中的錯誤,包括除以0 錯誤、文件I/O 錯誤、無效的類型轉換等。
TObject 分支類的另外一個重要組成部分是封裝數據結構的類。
*TBits:儲存Boolean 值的「數組」類。
*TList:鏈表類。
*TStack:堆棧類。
*TQueue:隊列類。
TObject 分支類還包括了封裝外部對象的類,如TPrinter 封裝了一個打印機接口,TIniFile 封裝了
INI 文件的讀寫接口。
而TStream 表明了這個分支的其餘類型的類。TStream 是能夠從各類存儲介質如磁盤文件、動態
內存等讀寫數據的流對象的基類。
5.TPersistent 類及TPersistent 分支類
TPersistent 是全部具備「指派」(Assignment)和「流」(Stream)屬性的對象的祖先類。所謂「指
派」屬性能夠將一個對象指定給其餘對象,而「流」屬性可以從一個表單文件(.xfm 或.dfm 文件)中
讀/寫屬性值。相應地,TPersisten 中也封裝了得到這兩個屬性的方法,包括如下幾種:
*定義了從一個輸入流中裝載和存儲非published 屬性的數據的過程。
*提供了給屬性指定值的方法。
*提供了將一個對象的內容指派給另外一個對象的方法。
程序中不能建立一個TPersistent 的實例。聲明一個不是組件的對象時能夠將TPersistent 做爲基類,
可是須要將該對象存儲到一個流中或將它們的屬性指定給其餘的對象。
TPersistent 分支類包括全部從TPersistent 可是不從TComponent 派生的VCL 和CLX 類。這些類
都是持續化對象,即它們能夠將數據保存到表單文件或數據模塊中,也能夠從表單文件或數據模塊中
得到數據,所以運行時它們能夠按照設計時設定的特性顯示。
然而這些類不能獨立存在,也就是說它們只能是組件的屬性(若是它們有擁有者),只能從一個
表單中讀寫。它們的擁有者必須是一個組件。TPersistent 引入了GetOwner 方法,它可讓表單編輯器
決定對象的擁有者。
這些類也是首先引入了發佈屬性的類,發佈屬性能夠自動地裝載和保存。DefineProperties 方法指
明瞭每一個類如何裝載和保存屬性。下面是TPersistent 分支中的一些具備表明性的類。
*Graphics:例如TBrush、TFont 和TPen。
*TBitmap 和TIcon 能夠保存和顯示圖形;TClipboard 包含了應用程序中剪切和拷貝的文本或圖
形。
*字符串列表:如TStringList,它表明了在設計時能夠指定的字符串文本或者列表。
*集合和集合項:它們是從TCollection 或TCollectionItem 中派生,這些類維護了屬於一個組件的
特別定義的索引集合。例如THeaderSections 和THaderSection,或者TLstColumns 和TListColumn。
6.TComponent 類和TComponent 分支類
TComponent 是全部組件的共同祖先類。它不提供任何用戶界面和顯示的特性。這些特性由直接
從TComponent 派生的兩個類提供:QControls 單元中的TControl 是跨平臺的應用程序中「可視化」組
件的基類;Controls 單元中的TControl 是Windows 應用程序中「可視化」組件的基類。
程序中不能建立TComponent 的實例。
TComponent 分支類包含了全部從TComponent 但不是從TControl 派生的全部類,這個分支的對象
是在設計時能夠進行設定可是在運行時不能在用戶界面顯示的組件。它們都是持續化對象,能夠完成數據庫

·416·
下列功能。
*在組件面板中顯示而且能夠在表單中顯示。
*能夠擁有而且管理其餘組件。
*自動裝載和保存。
TComponent 的幾個方法指示了組件在設計時的行爲以及如何得到組件保存的信息。
首先在這個分支中引入「流」,一個對象具備「流」的能力便可以將它的屬性信息保存在一個表
單文件中而且能夠從表單文件中讀取屬性信息。發佈屬性都是持續化的,而且自動成爲「流」。
TComponent 分支類引入了擁有者的概念。它們有兩個屬性支持擁有者的實現:Owner 和
Components。每一個組件都有Owner 屬性,指明瞭將另一個組件做爲它的擁有者。一個組件擁有其餘
組件時,擁有的組件都在Components 屬性中。每一個組件的構造方法都有一個指明新組件的擁有者的
參數。若是參數中傳入的擁有者存在,新組件會加入到擁有者的Components 列表中,這個屬性也會
自動提供給擁有者的析構方法中。若是一個組件有一個擁有者,那麼擁有者被銷燬時,它也會被銷燬。
例如TForm 是TComponent 的後代,當一個表單擁有的全部組件在表單被銷燬時也會被銷燬而且釋放
它們的內存(固然是假設組件設計正確而且析構方法正確地清理)。
若是一個屬性的類型是TComponent 或它的後代,「流」將建立這個類型的一個實例而且讀入它們。
若是一個屬性是TPersistent 但不是TComponent 的後代,「流」將使用現存可用的實例而且讀取該實例
的屬性值。
TComponent 分支類包括的一些表明類以下:
*TActionList:維護一個行爲列表的類,它對程序中對用戶輸入的反應進行了抽象化。
*TMainMenu:提供表單的菜單條以及下拉菜單。
*TOpenDialog、TsaveDialog、TfontDialog、TfindDialog、TcolorDialog 等:這些是從一般使用的
對話框顯示和收集信息的類。
*TScreen:對應用程序建立的表單和數據模塊、活動表單、表單中活動控件、屏幕的大小和解析
度、應用程序使用的光標和字體等進行跟蹤的類。
在須要建立能在組件面板中顯示而且在表單設計器中使用的非可視組件時可使用TComponent
做爲基類。例如想編寫一個相似於TTimer 的組件時,能夠從TComponent 派生。這種類型的組件能夠
顯示在組件面板中,能夠經過代碼執行內部函數,可是運行時不能顯示在用戶界面。
7.TControl 類及TControl 分支類
TControl 是全部運行時可見的組件的基類。控件都是可視的組件,即在程序運行時用戶能夠看見
它而且可能進行交互操做。全部的控件都有描述它們顯示特性的屬性、方法和事件,如控件的位置、
控件相關的光標或提示、繪製或移動控件的方法以及響應用戶行爲的事件。
TComponent 定義了全部組件的行爲,而TControl 定義了全部控件的行爲,包括繪製方法、標準
事件和容器屬性。TControl 引入了不少全部控件都繼承的顯示特性的屬性,包括Caption、Color、Font、
以及HelpContext 或者HelpKeyword。當這些屬性從TControl 繼承時,它們都是published 的,所以可
以顯示在對象編輯器中。可是它的後代能夠決定是否發佈這些屬性,例如TImage 沒有發佈Color 屬性,
所以它的顏色由它顯示的圖形決定。
TControl 分支類包括從TControl 但不是從TWinControl(CLX 應用程序中是TWidgetControl)派
生的組件。一般,全部的控件都有對鼠標動做進行反應的事件,而這個分支的控件不能接受鍵盤輸入。
TControl 也引入了Parent 屬性,它指明瞭這個控件包含在另外一個控件中。
TControl 分支中的控件也叫做圖形控件,它們都從TControl 的直接後代TGraphicControl 中派生
而來。雖然這些控件在運行時顯示在用戶面前,但它們並無本身的底層窗口,只是使用它們的父窗
口。這是由於圖形控件不能接受鍵盤輸入或做爲其餘控件的父控件的限制。因爲它們沒有本身的窗口,
所以圖形控件使用系統資源較少。
第16 章 開發新的VCL 組件
·417·
8.TWinControl/TWidgetControl 類及TWinControl/TWidgetControl 分支類
TWinControl 是能夠顯示在微軟的Windows 屏幕上的全部控件的基類。它提供了顯示在Windows
屏幕上的公用功能,包括如下幾種。
*控件能夠與底層的窗口協同工做,例如若是底層的屏幕對象是一個文本編輯器,控件能夠協同
編輯器管理和顯示緩衝區的文本。
*控件能夠接受用戶的輸入焦點,得到焦點的控件能夠處理鍵盤輸入事件(圖形控件控件只能顯
示數據和對鼠標做出反應)。一些控件在得到輸入焦點時能夠改變它們的外觀,例如按鈕控件在得到焦
點時在它的文字周圍繪製一個矩形。
*控件可以成爲一個或許多子控件的父控件,能夠做爲其餘控件的容器。這個關係能夠由子控件
的Parent 屬性表示。容器控件爲它們的孩子提供重要的服務,包括爲沒有本身畫布的控件提供顯示服
務。容器控件的例子包括Form、Panel 和Toolbar。
*控件有一個句柄(handle),或者說是獨一無二的標識符,這使它們能夠訪問底層的窗口或
Widget。
基於TWinControl 的控件能夠顯示Windows 提供的標準屏幕對象或由VCL 程序員定製的屏幕對
象。每個TWinControl 都有一個Handle 屬性,它提供了訪問Windows 屏幕的窗口句柄。可使用
Handle 屬性調用VCLAPI 以及直接訪問底層窗口。
組件庫中大部分控件派生於TWinControl/TWidgetControl 分支。與圖形控件不一樣,這個分支的控
件擁有本身的窗口或Widget,所以它們有時也叫做窗口控件或Widget 控件。窗口控件都從TWinControl
派生,而TWinControl 是從只用於Windows 版本的TControl 派生的。Widget 控件都從TWidgetControl
派生,而TWidgetControl 是從TControl 的CLX 版本派生。
TWinControl/TWidgetControl 分支包括了能夠自動繪製的控件(如TEdit、TListBox、TCmboBox、
TPgeControl 等) 以及不與單個底層控件或widget 對應的控件。後一種控件包括TStringGrid 和
TDBNavigator,它們必須本身處理繪製的細節。由於這個緣由,它們從TCustomControl 類派生,而
TCustomControl 類引入了Canvas 屬性以繪製它們本身。
當定義一個新的控件類時,TControl 的子類型用於非窗口控件,TWinControl 的子類型則用於窗
口控件。除非特殊須要,通常不直接從TControl 和TWinControl 派生新控件。TWinControl 的後代包
括支持多種用戶界面對象的抽象基類。最重要的後代是TCustomControl,它提供了畫布和處理繪製消
息的代碼。其餘一些重要的抽象後代包括TScrollingWinControl、TButtonControl、TCustomComboBox、
TCustomEdit 和TCustomListBox 等,定義新的控件時能夠考慮這些從TWinControl 直接派生的抽象類,
這樣能夠充分利用原有的屬性、事件和方法,減小不少工做量。
16.1.4 組件和類
簡單地說,組件就是Delphi 的組件庫中的一個類,開發一個新組件其實是從現有類層次中的某
個類派生一個新類加入到類庫中。
16.1.5 開發組件的要求
開發組件對程序員提出了更高的要求,主要體如今如下幾個方面。
1.開發組件是非可視化的
開發組件與開發Delphi 應用程序最明顯的區別是組件開發徹底以代碼的形式進行,即非可視化的。
Delphi 應用程序的可視化設計須要已完成的組件,而開發這些組件就須要使用Object Pascal 代碼編寫。
雖然沒法使用可視化工具來開發組件,可是開發過程能運用Delphi IDE 的全部編程特性,如代碼編輯
器、集成化調試和對象瀏覽。編程

·418·
2.開發組件須要更深刻的有關面向對象編程的知識
開發新組件和使用它們的最大區別在於當開發新組件時,須要從已存在的組件中繼承產生一個新
對象類型,並增長新的屬性和方法。而組件使用者在開發Delphi 應用程序時,只是在設計階段經過改
變組件屬性和描述響應事件的方法來定製已有組件的行爲。所以,組件編寫者必須對面向對象編程有
更深刻的瞭解,這主要體如今如下幾方面:
(1)組件編寫者能夠訪問祖先對象中的更多部分
當繼承產生一個新對象時,程序員有權訪問祖先對象中對最終用戶不可見的部分,即protected 的
屬性、方法。在很大部分的實現上,後代對象也須要調用它們的祖先對象的方法。所以,開發組件者
應至關熟悉面向對象編程特性。
(2)組件編寫者須要設置組件的屬性、方法和事件
不考慮在表單編輯器中的可視化操做,一個組件最明顯的特徵就在於它的屬性、事件和方法。
(3)組件編寫可能還須要封裝圖形
Delphi 經過把各類各樣的圖形工具封裝到一個畫布(Canvas)中而簡化了Windows 的圖形操做。
Canvas 表明了一個窗口或控件的能夠顯示圖形的部分,而且包含了其餘的類。
若是曾經開發過圖形化的Windows 應用程序,應該會對Windows 圖形設備接口(Graphics Device
Interface,GDI)比較熟悉。GDI 限制了可用的設備上下文的數目,並要求在銷燬它們以前將圖形設備
恢復到初始狀態。
使用Delphi 則不用擔憂這些事情。爲了繪製一個表單或組件,可使用組件的Canvas 屬性。如
果定製了Pen 或者Brush,則能夠設置它的顏色和樣式。設置完成後,Delphi 負責分配資源。若是程
序中須要反覆調用這些資源,Delphi 在緩存中保存這些資源以免重複建立。
固然仍然能夠徹底控制Windows GDI,可是使用Delphi 組件中的Canvas 將會使代碼更簡單而且
運行更快。
3.開發組件要遵循更多的規則
開發組件比開發可視化應用程序採用的編程方法更傳統,與使用已有組件相比有更多的規則要遵
循。在開始編寫組件以前,最重要的事莫過於熟練應用Delphi 自帶的組件,以獲得對命名規則以及組
件用戶所指望功能等的直觀認識。組件用戶指望組件作到的最重要的事情莫過於他們在任什麼時候候能對
組件作任何事。編寫知足這些指望的組件並不難,只要預先想到和遵循一些規則。
使組件可用的一個重要方面是減小組件的依賴性。天然地,在應用程序中組件能夠在不一樣的組合、
順序、上下文中協同工做。所以設計組件時應該使其在任何環境下均可以使用,不須要預先設定條件。
減小依賴性的一個例子是TWinControl 的Handle 屬性。若是原來開發過Windows 應用程序,那
麼就能夠知道最困難而且最容易犯錯誤的是確保不要訪問一個沒有調用CreateWindow API 函數的窗
口控件。Delphi 窗口控件使用戶從這裏解放出來,它確保一個窗口句柄在須要調用時永遠是有效的。
控件能夠檢查窗口是否已經建立,若是句柄無效,控件建立一個窗口而且返回句柄。所以不管程序代
碼什麼時候訪問Handle 屬性,它都保證得到一個有效的句柄。
經過減小諸如建立窗口之類的後臺任務,Delphi 組件容許程序員將注意力集中在他們真正想作的
事情上。在將一個窗口句柄傳遞給API 函數以前,程序員不須要驗證句柄是否存在或者建立一個窗口。
應用程序開發者能夠假定一切正常,而不須要常常檢查可能出錯的事物。雖然可能建立沒有依賴性的
組件須要花費不少時間,但一般是值得的。它不只僅減輕了程序開發者的負擔,並且也減小了控件本
身的文檔和技術支持的負擔。
4.組件必須註冊才能使用
在向IDE 安裝組件以前,必須進行註冊。註冊告訴Delphi 把組件放在組件面板的哪一個位置,也可
以定製Delphi 將組件保存在表單文件中的方法。
第16 章 開發新的VCL 組件
·419·
16.1.6 如何選擇新組件的基類
組件幾乎能夠是在應用程序開發者在設計時想要操做的全部的應用程序的元素。開發一個組件意
味着從現存的類中派生一個新類,如表16-3 所示,能夠做爲新開發組件的基類的簡單列表。
表16-3 開發組件的基類選擇
開發組件的起點 可選基類
修改現存的控件 任意現存的組件如TButton、TListBox,或者是一個抽象組件類型,如TCustomListBox
開發窗口控件(或CLX 應用
程序中的基於widget 的控件)
TWinControl(在CLX 應用程序中使用TWidgetControl)
開發圖形控件 TGraphicControl
開發窗口類的子類 任何Windows 控件(在VCL 應用程序中)或基於widget 的控件(CLX 應用程序中)
開發非可視組件 TComponent
也能夠從一個非組件的類中派生,以開發不能在表單中操做的組件,如TRegIniFile 和TFont。
1.修改現存的控件
開發組件最簡單的方法就是定製現存的組件。組件庫中任何一個組件均可以做爲新組件的父類。
例如改變標準控制(如TButton)的默認屬性值。
一些控件,如列表框、表格可能用到許多相同變量。在這種狀況下,組件庫包含了一個抽象類用
於派生定製的版本,抽象類在名字中包含了「Custom」,如TCustomGrid。
例如開發一個去掉標準TListBox 類中一些屬性的新的列表框,可是因爲Delphi 的屬性可見性的
規定,開發過程當中不能刪除(或者隱藏)從祖先類中派生的屬性,所以只能從類層次中TListBox 之上
而不是TListBox 派生新組件。組件庫提供了TCustomListBox 抽象類,它實現了全部的列表框的屬性,
可是全部的屬性都沒有發佈( Publish ), 所以新組件能夠從TCustomListBox 派生。若是沒有
TCustomListBox 抽象類,則新組件必須從TWinControl 類(在CLX 中是TWidgetControl 類)派生然
後從新編寫全部的列表框函數。當從像TCustomListBox 這樣的抽象類派生時,能夠只發布須要在組件
中能夠修改的屬性而把其餘屬性設置爲protected。
2.開發窗口控件
組件庫中基於窗口的控件是在運行時可見而且能夠與用戶進行交互的對象。每一個基於窗口的控件
都有一個能夠經過Handle 屬性訪問的窗口句柄,它可讓操做系統識別和操做該控件。若是使用VCL
控件,句柄容許控件接收輸入焦點而且能傳遞給Windows API 函數。
全部窗口控件都從TWinControl 類(CLX 中是TWidgetControl)派生。這包括了最經常使用的標準窗
口控件,如按鈕、列表框、編輯框。當須要直接從TWinControl(CLX 中是TWidgetControl)中派生
一個新的窗口控件(它與任何現存的控件無關)時,Delphi 提供了TCustomControl 組件。TCustomControl
是一個特別的窗口組件,它能夠更加容易地繪製複雜的圖形。
3.開發圖形控件
若是新建立的控件不須要接受輸入焦點,那麼可使用圖形控件。圖形控件與窗口控件很相似,
可是沒有窗口句柄,所以消耗系統資源較少。像TLabel 這樣的組件就是圖形控件。雖然這些控件不能
接收焦點,可是能夠對鼠標消息做出反應。
能夠經過TGraphicControl 組件建立定製的圖形控件(TGraphicControl 是從TControl 派生的抽象
類)。雖然能夠直接從TControl 派生組件,可是從TGraphicControl 派生更好,由於它提供了Canvas
屬性以在窗口中繪製圖形、處理WM_PAINT 消息,須要作的只是重載Paint 方法。
4.建立窗口類的子類
Windows 中有一種稱之爲窗口類的概念,窗口類是Windows 中相同學口或控件的不一樣實例之間共數組

·420·
享的信息集合。
傳統的Windows 編程中,建立定製的控件須要定義一個新的窗口類而且在Windows 註冊。以已
存在的窗口類爲基礎創建一個新類,即建立窗口類的子類;而後將控件放到動態連接庫中,就像標準
Windows 控件同樣,而且提供訪問界面。也能夠建立一個「包裝」現存的窗口控件的組件。
若是已經有一個在Delphi 中使用的定製的控件庫,能夠建立與控件行爲相似的Delphi 組件,而且
像使用其餘控件同樣從它們派生新的控件。
使用Windows 窗口類的子類技術的例子能夠參見StdCtls 單元中標準Windows 控件的組件的代碼,
如TEdit。CLX 應用程序請參見QStdCtls 單元。
5.開發非可視組件
非可視組件一般用做程序中某些元素(如數據庫)的接口(如TDataSet 和TSQLConnection)、系
統時鐘(如TTimer)以及對話框界面(在VCL 應用程序中是TCommonDialog,在CLX 應用程序中是
TDialog 以及它們的子類)。通常大部分開發的組件都是可視組件,非可視組件能夠直接從全部組件的
基抽象類TComponent 派生。TComponent 定義了組件在FormDesigner 中所需的基本的屬性和方法。
所以,從TComponent 繼承來的任何組件都具有設計能力。
16.1.7 開發新組件的基本步驟
能夠從兩條途徑開發新組件。
*使用組件嚮導開發組件。
*手動開發組件。
這兩種方法均可以開發一個能安裝到組件面板中的最小功能的組件。安裝以後就能夠將組件加入
到一個表單中並在設計時和運行時進行測試。
不管使用哪一種方法進行組件開發都有一些基本步驟,能夠簡要描述以下。
*爲新組件建立一個單元(Unit)。
*從一個現存的組件類型派生新組件。
*加入屬性、方法和事件。
*將組件註冊到IDE 中。
*爲組件建立一個位圖。
*建立一個包(一個特殊的動態連接庫)以便安裝組件到IDE 中。
*建立組件的方法、屬性和事件的幫助文件。
注意:爲組件建立幫助文件是可選的步驟。
組件開發完成以後,徹底的組件包括下列文件。
*一個包(*.bpl)或包集合(*.dpc)文件。
*一個編譯的包文件(*.dcp)。
*一個編譯的單元文件(*.dcu)。
*一個面板位圖文件(*.dcr)。
*一個幫助文件(*.hlp)。
1.使用組件嚮導開發新組件
組件嚮導簡化了開發組件的最開始的步驟,使用組件嚮導時須要指定如下內容。
*新組件的基類。
*新組件的類名。
*新組件顯示在組件面板的哪一頁。
*新開發組件的單元名。
*單元的查找路徑。
第16 章 開發新的VCL 組件
·421·
*組件放置在哪一個包內。
組件嚮導與手動開發組件時完成同樣的功能,包括如下內容。
*建立一個單元。
*派生組件。
*註冊組件。
組件嚮導不能將組件加入到已存在的單元中。必須手動加入。
使用組件嚮導開發組件的基本步驟以下。
(1)啓動新建組件對話框
使用下面兩種方法之一,能夠啓動新建組件對話框。
*從Delphi IDE 菜單中選擇「Component」*「New Component」。
*從Delphi IDE 菜單中選擇「File」*「New」*「Other」並雙擊Component。
啓動以後的新建組件對話框如圖16-2 所示。
圖16-2 新建組件對話框
(2)填寫新建組件對話框中的信息
*在Ancestor Type 下拉選擇框中指定新組件的基類。
注意:在下拉菜單中,許多組件都在不一樣的單元名中列了兩次,其中一個是基於VCL 的,
而另外一個是基於CLX 的。CLX 相關的單元名都以Q 開始(例如QGraphics 代替了
Graphics)。選擇時應保證從正確的組件派生。
*在Class Name 輸入框中填寫新組件的類名。
*在Palette Page 下拉選擇框中指明新組件安裝在組件面板的哪一頁中。
*在Unit file name 輸入框中填寫組件在哪一個單元中聲明。若是單元不在查找路徑中,能夠根據需
要修改Search Path 輸入框中的查找路徑。
(3)填寫完成以後
*單擊「Install」按鈕,則將組件放到一個新的或已經存在的包中。
*單擊「OK」按鈕,IDE 將建立一個單元。
注意:若是從一個名字以Custom 開始的類(例如TCustomControl)派生新組件,那麼在重
載原組件的抽象方法以前,不能將新組件放到表單中。Delphi 不能建立一個有抽象屬
性或方法的實例對象。
此時能夠單擊「View Unit」按鈕查看代碼,若是新建組件對話框已經關閉,那麼在代碼編輯器中
選擇「File|Open」打開單元也能夠查看代碼。Delphi 建立一個包含類聲明和註冊過程的新單元,而且
加入了包含在全部Delphi 單元中的uses 語句。緩存

·422·
單元相似於這樣:
unit MyControl;
interface
uses
Windows, Messages, SysUtils, Types, Classes, Controls;
type
TMyControl = class(TCustomControl)
private
{ Private declarations }
protected
{ Protected declarations }
public
{ Public declarations }
published
{ Published declarations }
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents(’Samples’, [TMyControl]);
end;
end.
2.手動建立組件
最簡單的方法是使用組件嚮導建立新組件,固然也能夠手動執行一樣的步驟。
(1)建立一個單元文件
單元(unit)是Delphi 中能夠獨立編譯的代碼模塊。每一個表單都有本身的單元,許多組件(或相
關的組件組)也有本身的單元。
當建立一個組件時,能夠選擇爲組件建立一個新單元,或將新組件加入到一個現存單元中。
爲組件建立一個新單元的步驟以下。
*從IDE 菜單中選擇「File」*「New」*「Unit」或 「File」*「New」*「Other」以顯示
New Items 對話框,選擇「Unit」,而後單擊「OK」。IDE 建立一個新單元而且在代碼編輯器中打開它。
*使用一個有意義的名字保存單元。
*派生組件類。
打開一個現存單元則須要如下步驟。
*從IDE 菜單中選擇「File」*「Open」並選擇須要加入組件的源代碼單元。
注意:當向一個現存單元中加入組件時,保證單元中只包含組件代碼。向一個包含表單的單
元中加入組件代碼會引發組件面板錯誤。
*派生組件類
(2)派生組件
每一個組件都是從TComponent 派生的一個類,能夠從它的特殊後代(如TControl 或TGraphicControl)
第16 章 開發新的VCL 組件
·423·
或一個現存的組件類派生。
在須要包含組件的單元的interface 部分加入一個對象類型聲明,並指明它的基類。
一個最簡單的組件類是直接從TComponent 派生的非可視類。
(3)註冊組件
註冊是一個告訴IDE 哪些組件加入到它的組件庫、顯示在組件面板的哪一頁的簡單過程。註冊組
件包括兩個步驟。
*在組件單元的interface 部分加入一個名爲Register 的過程。Register 不須要任何參數,所以聲
明很是簡單。
procedure Register;
若是是在已經包含組件的單元中加入組件,則Register 過程應該已經聲明,不須要再進行聲明。
注意:雖然Delphi 自己是大小寫不敏感的,但Register 過程是大小寫敏感的,而且必需以大
寫R 開頭。
*在單元的implementation 部分加入Register 過程的代碼。
在代碼中爲每一個須要註冊的組件調用RegisterComponents 過程。RegisterComponents 有兩個參數:
組件面板頁的名稱和組件類型的集合。若是是在一個現存的註冊過程當中加入一個新組件,則能夠在現
存語句的組件類型集合中加入新組件,也能夠新寫一個調用RegisterComponents 的語句。例如:
RegisterComponents(’Samples’, [TMyControl]);
將組件TMyControl 加入到組件面板的Sample 頁中。一旦註冊完畢,Delphi 自動將組件圖標顯示
在組件面板上。
16.1.8 測試未安裝的組件
在安裝組件到組件面板以前就能夠對組件運行時的行爲進行測試。這在調試一個新建立的組件時
特別有用,但一樣的技術也能夠用於任何組件不論它是否在組件面板中。
測試一個未安裝的組件能夠經過模擬Delphi 中在組件面板中選擇組件並放置到一個表單中的行爲
進行。其步驟以下:
*在表單單元的uses 語句中加入組件單元的名稱。
*在表單中加入一個對象聲明表明組件。
這是本身加入組件和讓Delphi 加入組件的主要區別之一。本身加入組件應在表單的type 聲明的
public 的結束部分加入對象聲明,而Delphi 將會把它加到所管理的類型聲明的部分。
注意:不要在Delphi 管理的表單的類型聲明部分加入對象聲明。類型聲明部分相關的項目存
儲在表單文件中,在表單中加入表單中不存在的組件將會致使表單文件無效。
*在表單的OnCreate 事件處理代碼中建立組件。
當調用組件的構造方法時,必須傳遞組件的擁有者(須要時銷燬該組件的組件)的參數。能夠將
Self 做爲擁有者的參數。在一個方法中,Self 是包含這個方法的對象的引用。在本例中因爲是在表單
的OnCreate 事件處理代碼中,所以Self 是表單的引用。
*設定組件的Parent 屬性的值。
表單一般在設置控件的其餘屬性以前設置Parent,所以設置Parent 屬性值是建立一個控件後應該
作的第一件事。Parent 是顯示時包含控件的組件,一般是控件顯示的表單,可是也能夠是一個GroupBox
或Panel。通常能夠設置Parent 爲Self。
警告:若是組件不是一個控件(也就是說TControl 不是它的祖先),則能夠忽略這一步。如
果設置了表單(不是控件)的Parent 屬性爲Self,將會引發操做系統錯誤。服務器

·424·
*根據須要設置組件的其餘屬性。
16.1.9 測試已安裝的組件
安裝以後測試組件能夠測試開發應用程序時將組件從組件面板中拖到表單中時是否產生異常。
測試已經安裝的組件可使用Delphi 的第2 個IDE 運行實例,即從Delphi IDE 環境中再次啓動
一個Delphi IDE 實例,則在第1 個實例中能夠對第2 個實例進行調試、跟蹤。具體步驟以下:
*打開組件源文件並設置斷點。
*從IDE 菜單中選擇「Run」*「Parameters」並設置Host Application 爲Delphi 的啓動程序。
*單擊「Load」按鈕可調用Delphi 的第2 個運行實例。
*在表單中加入須要測試的組件,這將在運行到設置的源文件的斷點時中斷。
16.2 組件開發過程當中的面向對象編程
從應用程序員的角度看,一個類包括數據和代碼,而且能夠在設計和運行的時候對類進行操做。
可是當開發新組件時,須要從與應用程序員不一樣的角度去處理類。組件開發者須要試圖隱藏組件的內
部工做不讓組件使用者知道。只有恰當地選擇組件的祖先、仔細設計只提供給使用者須要的屬性和方
法的界面,才能開發通用的、可重用的組件。
16.2.1 定義新類
組件的開發者與應用程序員的區別在於組件開發者開發新的類,而應用程序員操做這些類的實
例。
一個類基本上就是一個類型。做爲一個程序員常常要用類型和實例工做,例如定義一個類型(如
整數)的變量。而類經常比這些簡單的數據類型要複雜,可是它們的工做方式是同樣的,即經過給相
同類型的實例模板賦予不一樣的值,就能夠執行不一樣的任務。
例如設計一個包含「OK」和「Cancel」按鈕的表單。每個按鈕都是類TButton 的一個實例,但
是經過對它們的Caption 屬性賦予不一樣的值和對OnClick 事件定義不一樣的事件處理代碼,這樣兩個實
例的行爲就不一樣了。
1.派生新類
派生新類主要有如下兩個方面的緣由。
*改變類的默認值以免重複。
*給類增長新功能。
兩種狀況都是爲了設計可重用的對象。若是在設計組件時就須要時刻記得可重用,那樣之後就可
以省去不少工做。給類定義一個可用的默認值,同時也應該容許定製這些默認值。
(1)改變類的默認值以免重複
許多程序員都試圖避免重複。若是發現本身在反覆重寫同一代碼,能夠將代碼放在一個子程序或
函數中,或者建一個能夠在許多程序中使用的程序庫。對於組件來講一樣如此。若是發現須要改變同
樣的特性或設計一樣的調用方法,就能夠開發默認完成這些行爲的新組件。
例如每設計一個程序,就要增長一個對話框以執行一個特定的操做。而利用改變默認值的方法可
以簡化這些操做。能夠設計一次這個對話框,設置它的屬性,在組件面板上安裝一個封裝這個組件的
新組件。經過使這個對話框變爲一個可重用的組件,不只減小了重複的工做,並且可使程序標準化,
減小每次設計對話框時可能出現的錯誤。
修改一個已存在的組件自己就是改變一個組件默認屬性的一個實例。
注意:若是隻想改變一個已存在的組件中的published 屬性,或者是保存一個或一組組件中
特定的事件處理代碼,則使用組件模板將會更容易。
第16 章 開發新的VCL 組件
·425·
(2)給類增長一些新的功能
開發組件的常見緣由就是增長已有的組件中沒有的新功能。此時能夠從已有的組件或抽象基類
(如TComponent 或TControl)派生來開發新組件。
派生組件時應選擇包含儘量多的所須要的功能的類做爲父類。因爲能夠給類增長新功能,可是
不能取消它們,所以當一個已存在的組件類包含不想引入新組件的屬性時,應該從組件的祖先中派生
新組件。例如須要在一個列表控件上增長一些特性,能夠從TListBox.派生。可是,若是在增長新的功
能的同時又不想包含標準列表框中一些功能時,就必須從TCustomListBox(TListBox 的祖先)派生。
而後就能夠從新開發所要的列表框的功能,增長新特性。
2.聲明一個新的組件類
除標準的組件外,Delphi 提供了許多抽象的類做爲派生新組件的基類。聲明一個新組件類須要在
組件單元文件中增長一個類的聲明。例如:
TNewComponent = class(TComponent)
private
{Private 屬性、方法、事件聲明部分}
protected
{Protected 屬性、方法、事件聲明部分}
public
{Public 屬性、方法、事件聲明部分}
published
{Published 屬性、方法、事件聲明部分}
end;
16.2.2 祖先、後代及類層次
應用程序員理所固然的認爲每個控件都具備Top 和Left 屬性,以便肯定它在表單中的位置。對
他們來講,不用關心全部控件的這些屬性是從共同的祖先TControl 繼承的。然而開發一個組件時,必
須知道從哪一個類派生以便繼承適宜的特性。並且必須知道控件繼承的全部東西,這樣就能夠利用繼承
的特性而不需從新開發。
直接派生組件的類叫做直接祖先。每一個組件從它的直接祖先及直接祖先的直接祖先依此類推繼
承。所以派生一個組件的全部的類都叫它的祖先。組件是它的祖先的後代。
一樣,在一個程序中全部祖先-後代關係就組成了類層次。因爲一個類從它的祖先繼承了全部的
特性,並且增長了新的屬性、方法或者從新定義了已存在的屬性、方法,所以類層次中的每一代比它
的祖先包含了更多的功能。
若是不指定直接祖先,Delphi 從默認的祖先TObject 派生組件。TObject 是對象層次中全部類的最
終祖先。
選擇從哪一個對象中獲取的通常原則很簡單,就是選擇包含新對象想要的最多而不須要的最少的對
象。一般能夠在對象中增長東西,可是不能取消一些東西。
16.2.3 訪問控制
屬性、方法和做用域有5 種水平的訪問控制,也叫可見度。可見度決定了哪些代碼能夠訪問類的
哪一個部分,經過設定可見度能夠定義組件的界面。
如表16-4 所示,列出了訪問控制的不一樣層次,從限制最嚴到最鬆。
表16-4 訪問控制的不一樣層次
可見度 訪問範圍 目的
private 只能在定義類的單元內訪問 隱藏實現細節數據結構

·426·
續表
可見度 訪問範圍 目的
protected 能夠在類單元內部以及被類的後代訪問 定義組件開發者界面
public 全部代碼均可以訪問 定義運行時界面
automated 全部代碼都可訪問,併產生Automation 類型信息 僅用於OLE 自動化對象
published
全部代碼都可訪問,而且能夠經過對象編輯器訪問。保存在一個表單文件

定義設計時界面
只容許在類定義的單元內部訪問的成員定義爲prviate,只容許在類及其後代中可用的成員定義爲
protected。可是記住,若是一個成員定義在一個單元文件中可用,那麼它在整個文件中均可用。因此,
若是在同一個單元中定義兩個類,那麼這兩類就能夠互相訪問對方的private 方法。若是從祖代的不一樣
單元派生類,那麼新單元中的全部類均可以訪問祖先的protected 方法。
1.隱藏實現細節
把類的部分定義爲private 使類單元文件外部的代碼不能見到這些部分,但包含這些聲明的單元內
部的代碼能夠訪問這些部分。
2.定義組件開發者界面
定義一個類的某部分爲protected 使這部分只能被該類及其子類(以及共享單元文件的其餘類)可
見。能夠用protected 定義類中組件開發者的界面。應用程序單元沒法訪問protected 部分,可是派生
類能夠。這意味着組件開發者能夠改變類的做用方式而無需使應用程序員知道細節。
程序員常犯的一個錯誤是試圖從事件處理代碼訪問protected 方法。Windows 編程中,組件一般不
接收事件,因此事件處理過程通常都是表單的方法,而不是組件的方法。因此事件處理過程通常也不
能訪問組件的protected 方法(除非組件與表單在同一單元中被聲明)。
3.定義運行界面
將類的一部分定義爲public,使其對任何能夠訪問這個類的代碼均可見。
public 部分在運行時對全部代碼都是可見的,所以類的public 部分定義了它的運行界面。運行界
面對那些設計時沒有意義或不適當的項目頗有用,好比依賴運行時輸入的信息或只讀的屬性。打算提
供給應用程序開發者的方法也必須是public 的。
4.定義設計界面
定義類的屬性或事件爲published 的,則這些屬性和事件能夠對外發布。同時編譯器也將產生它們
的運行時類型信息,運行時類型信息容許對象編輯器訪問這些屬性或事件。
因爲它們在對象編輯器中顯示,類的published 部分被稱爲類的設計界面。設計界面應該包括應用
程序員在設計時須要定製的全部部分,可是應該排除依賴運行時特定信息的全部屬性。
因爲應用程序員不能直接給它們賦值,只讀屬性不能成爲設計界面的一部分,所以只讀屬性只能定
義爲public,而不能定義爲published。
16.2.4 分派方式
「分派」(Dispatch)是指當遇到一個方法調用時,程序決定一個方法應該從哪兒調用的方式。調
用一個方法的代碼看起來就和其餘過程和函數調用相似。可是類有不一樣的分派方法。
類的3 種分派方式爲靜態的(Static)、虛擬的(Virtual)和動態的(Dynamic)。
1.靜態方法
除非聲明時另外指定,不然全部的方法默認都是靜態的。靜態方法做用相似於常規的過程或函數。
編譯時編譯器就能夠決定方法的確切地址,同時將方法鏈接到可執行代碼中。
靜態方法的主要優勢是分派速度很快。因爲編譯器能夠肯定方法的確切地址,所以能夠直接鏈接
第16 章 開發新的VCL 組件
·427·
方法。相對地,虛擬和動態方法則是運行時經過間接的方法去尋找方法的地址,於是花費的時間較長。
靜態方法在被子類繼承時不改變。若是定義類時包括靜態方法,而後從它派生一個新類,派生類
在相同的地址下共享相同的方法。這就意味着不能重載靜態方法,不管如何調用,靜態方法只作相同
的事情。若是在派生類中定義與祖先類中靜態方法名稱同樣的方法,新方法只是簡單替代派生類中繼
承的方法。
聲明一個靜態方法只需在類定義的部分加入相似下面的代碼:
procedure MyProcedure;
這段代碼聲明瞭一個名稱爲MyProcedure 的靜態方法。
2.虛方法
虛方法與靜態方法相比,其分派機制更復雜,更富有彈性。在子類中虛方法能夠從新定義,但仍
能夠在祖先類中調用。在編譯時虛方法的地址不能被肯定,而是由定義方法的對象在運行時尋找它的
地址。
使一個方法成爲虛方法須要在聲明方法語句後面增長virtual 指令。virtual 指令在對象虛方法列表
(Virtual Method Table,VMT)建立了一個入口。VMT 保留了一個對象類型中全部虛方法的地址。例
如聲明一個虛方法的代碼:
procedure MyProcedure;virtual;
當從已有的類中派生新類時,新類具備本身的VMT,它包括全部的祖代的VMT 及在新類中增長
的全部虛方法。
3.重載方法
重載一個方法就是指擴展或從新定義它,而不是替代它。子類能夠重載全部繼承的虛方法。在子
類中重載某方法,只須要在方法聲明的末尾增長override 指令。聲明一個重載方法的例子以下:
procedure MyProcedure;override;
在下面的狀況下重載一個方法會產生編譯錯誤:
*方法在祖類中不存在。
*這個名字的祖先方法是靜態的。
*方法聲明不一致(包括調用參數的順序、類型不一樣)。
4.動態方法
在分派機制上,動態方法與虛方法有一些細小的差異。由於動態方法在VMT 中沒有入口,這樣
能夠減小對象消耗的內存。然而,分派動態方法一般要比分派常規的虛方法慢。若是某種方法需常常
調用,或者執行時間很是關鍵,就應該把這種方法定義爲虛方法而不是動態方法。
對象必須存儲動態方法的地址。可是與接收VMT 中的一個入口不一樣,動態方法是獨立列出來的。
動態方法列表包含的只是一個特殊類中引入的或重載的方法,與之對比的是,VMT 包括對象全部的虛
方法(繼承的或新引入的)。繼承的動態方法經過搜索每一個祖先的動態方法列表而進行分派,經過向上
查找繼承樹來完成。
使一個方法成爲動態方法須要在方法聲明的後面直接增長dynamic 指令。下面代碼聲明瞭一個動
態方法:
procedure MyProcedure; dynamic;
16.2.5 抽象類成員
當在祖先類中把一種方法聲明爲abstract 時,則必須在程序使用這些組件以前在子組件中實現它
的接口(經過重定義和實現)。Delphi 不能建立包含了抽象成員的類的實例。聲明一個抽象方法的示例
代碼以下:
procedure MyProcedure; abstract;框架

·428·
值得注意的是,若是一個組件中有方法聲明爲抽象方法,則不能在設計時和運行時建立這個組件
的實例。這個組件只能做爲派生其餘組件的父類。
16.2.6 類和指針
每一個類(所以也是每一個組件)實際上就是一個指針。編譯器自動解釋類指針,因此大多時候沒必要
考慮它。但當把類做爲參數時,類做爲指針就顯得很重要。一般應該經過傳值而不是傳引用調用類(即
不須要var 指示),緣由就是類已是指針,即已是引用了。把類做爲引用傳遞就意味着把引用傳遞
給引用。例如一個表單的構造方法中的參數Sender 是一個TObject 對象,不須要使用var 指示:
procedure FormCreate(Sender: TObject);
16.3 建立屬性
屬性(Property)是組件中最特殊的部分,在設計應用程序時被能夠看見和操做,而且在交互過程
中能當即獲得返回結果。一個好的屬性設計可以使組件用戶使用起來更容易,同時也便於開發者本身維
護。
16.3.1 屬性的類型
屬性能夠是任何類型。不一樣類型在對象編輯器中顯示不一樣,它們能夠在設計時驗證所設置的屬性。
對象編輯器支持的屬性類型,如表16-5 所示。
表16-5 對象編輯器支持的屬性類型
屬性類型 對象編輯器中的處理
簡單類型 Numeric、character 和string 屬性以數字、字符和串顯示,應用程序員能夠直接編輯
枚舉類型
枚舉類型屬性(包括布爾值)顯示爲可編輯的字符串。程序員能夠經過雙擊鼠標取得可能的取值或經過
下拉式列表框顯示全部的可能取值
集合類型
集合類型屬性的設置就如同一個集合。經過在屬性列雙擊,程序員能夠展開集合,將每個元素做爲一
個布爾值,若是集合中含有它則爲True
對象類型
對象類型屬性自己有屬性編輯器。若是一個靠屬性支撐的類有本身的published 屬性,對象編輯器能夠通
過鼠標雙擊讓程序員展開包含這些屬性的列表並單獨編輯它們。對象屬性必須從TPersistent 繼承
界面類型
只要值是一個組件實現的界面,界面類型的屬性就能夠顯示在對象編輯器中。界面屬性經常有本身的屬
性編輯器
數組類型
數組類型屬性必須有本身的屬性編輯器,對象編輯器中沒有內嵌對數組屬性編輯的支持。註冊組件時可
指定屬性編輯器
16.3.2 發佈繼承的屬性
全部組件都從祖先類繼承屬性。當從已有組件派生新組件時,新組件將繼承祖先類型的全部屬性。
若是繼承的是抽象類,則繼承的屬性是protected 或public,但不是published。
若是想在設計時在對象編輯器訪問protected 或public 屬性,必須將該屬性重定義爲published,即
對子類繼承的屬性從新聲明。
16.3.3 定義屬性
1.屬性的聲明
屬性應該在定義組件類時聲明,聲明屬性須要描述如下3 個內容。
*屬性名稱;
*屬性類型;
*屬性值讀/寫的方法。若是沒有聲明寫方法,那麼屬性就是隻讀的。
第16 章 開發新的VCL 組件
·429·
若是要使屬性能在設計時在對象編輯器中被編輯,應當在published 部分聲明該屬性。表單中組件
的published 屬性的值與組件一塊兒保存在表單文件中。定義在組件對象聲明的public 部分的組件屬性,
能夠在運行時訪問以及在程序代碼中讀或賦值。
2.內部數據存儲
Delphi 沒有特別規定如何存儲屬性的數據值,一般Delphi 組件遵循下列慣例。
*屬性數據存儲在類的數據域處。
*用於存儲屬性值的數據域是private,只能被組件自身訪問。派生的組件只應使用繼承的屬性,
不須要直接訪問內部數據存儲。
*屬性對象域的標識符以F 開頭,例如定義在TControl 中的屬性Widthd 的值存儲在FWidth 域中。
這些慣例的基本原則是隻有屬性的實現方法能夠訪問這些數據。若是一個方法或另一個屬性需
要更改數據,那麼都要經過屬性,而不是直接訪問已存儲的數據,這樣就保證能夠改變繼承屬性的實
現而不影響派生的組件。
3.直接訪問
獲得屬性數據最簡單的辦法是直接訪問。屬性聲明的read 和write 部分描述了怎樣不經過調用訪
問方法來給內部數據域賦值。當須要在對象編輯器中使用屬性而且能夠改變它的值而不觸發其餘過程
時,可採用直接訪問。
在屬性聲明中,通常都在讀取時進行直接訪問,而在寫入時使用方法訪問,這樣組件的狀態就會
隨着屬性值而改變。
4.訪問方法
能夠在屬性聲明的read 和write 部分描述訪問方法。訪問方法應該是protected,一般被定義爲虛
擬的,這樣子組件能夠重載屬性的實現。
應該避免訪問方法爲public,由於使它爲protected 能夠保證應用程序員在調用這些方法時不改變
屬性。
(1)讀方法
屬性的讀方法是不帶參數的函數(下面註明的除外),而且返回與屬性相同類型的值。一般讀函
數的名字是「Get」後加屬性名,例如屬性Count 的讀方法是GetCount。須要時讀方法能夠經過處理
內部存儲的數據產生屬性值。
帶參數的讀方法的唯一屬性是數組屬性,屬性用索引描述,並將索引值做爲參數。
若是不定義read 方法,則屬性是隻寫的。只寫屬性不多使用。
(2)寫方法
屬性的寫方法老是隻帶一個參數的過程(下面註明的除外)。參數能夠是引用或值,能夠任意取
名。一般寫方法名是「Set」加屬性名。例如屬性Count 的寫方法名是SetCount。參數的值將設置爲屬
性的新值,所以寫方法須要對內部存儲數據中進行寫操做。
寫方法也可能不僅帶一個參數,這種狀況就是數組屬性,數組屬性用索引描述,並將索引值做爲
寫方法的第2 個參數。
一般在改變屬性前寫方法要檢測新值是否與當前值不一樣。例以下面是一個簡單的整數屬性Count
的寫方法,它的存儲域是FCount.。若是沒有聲明寫方法,那麼屬性是隻讀的。
procedure TMyComponent.SetCount(Value: Integer);
begin
if Value <> FCount then
begin
FCount := Value;
Update;編輯器

·430·
end;
end;
5.屬性的默認值
聲明一個屬性的同時能夠指定屬性的默認值。VCL 用默認值決定是否在表單文件中存儲屬性。如
果不指定屬性默認值,VCL 將存儲屬性值。
指定屬性的默認值的方法是在屬性聲明或重聲明的後面直接加default 指令,再跟默認值。例如:
property Cool Boolean read GetCool write SetCool default True;
注意:聲明默認值並非要求將屬性設置爲該值。
組件的構造方法能夠初始化屬性的值。
6.不指定屬性的默認值
當重聲明一個屬性時,即便繼承的屬性在祖先類中已指定了默認值,也能夠不指定屬性的默認值。
指定屬性無默認值的方法是直接在屬性聲明後面加nodefault 指令,如:
property FavoriteFlavor string nodefault;
若是是第1 次聲明屬性,則沒有必要加nodefault 指令,由於沒有聲明默認值即指無默認值。
7.建立數組屬性
許多屬性像數組同樣對本身進行索引。例如TMemo 的Lines 屬性就是一個索引列表,可將其看做
是數組。在數據量較大時,Lines 爲特殊的元素(一個字符串)提供了很天然的訪問。
數組屬性的聲明與像其餘屬性相同,除非聲明時包括一個或多個特殊的索引。索引能夠是任何類
型。可是若是須要指定屬性的讀和寫部分,則它們必須是方法,不能是數據域。
對於數組屬性的讀和寫方法採用附加的參數,其與索引相對應。在聲明中參數必須與聲明中索引
的順序和類型相同。下面是聲明一個數組屬性的例子:
property Day: Integer index 3 read GetDateElement write SetDateElement;
property Month: Integer index 2 read GetDateElement write SetDateElement;
property Year: Integer index 1 read GetDateElement write SetDateElement;
數組屬性和數組有許多不一樣之處。如索引不一樣,數組屬性的指數沒必要是整型,能夠用字符串給一
個屬性創建索引。另外,程序員只能引用數組屬性的單個元素,而不能是整個數組。
8.建立屬性的子組件
當一個屬性值是另一個組件時,一般能夠將另外一個組件的實例增長到表單或數據模塊中,而後
將該組件做爲屬性值。可是組件能夠建立本身的對象實例來實現屬性值,這樣的組件就叫子組件。
子組件能夠是任何持續化的對象(TPersistent 的任一後代)。與獨立組件做爲屬性值不一樣,子組件
的published 屬性保存在建立它們的組件中。可是爲了保證能運行,必須處理好下面的狀況。
子組件的Owner 必須是建立它的組件,並將它做爲published 屬性。若是子組件是TComponent
的後代,可設置子組件的Owner 屬性爲父組件。對於其餘子組件,必須重載持續化對象的GetOwner
方法,以便返回建立它的組件。
若是子組件是TComponent 的後代,則設置子組件是經過調用SetSubComponent 方法來完成的。
這個方法可在建立子組件時或在子組件的構造方法中調用。
通常來講,若是一個屬性的值是子組件,那麼它應該是隻讀的。若是容許對它賦值,那麼當設置
另外一個組件爲屬性值時,屬性設置者必須釋放子組件。另外,當屬性設置爲nil 時,組件會常常初始
化子組件。並且一旦屬性改變爲另外一組件,設計時子組件將不能恢復。下面的代碼介紹瞭如何給一個
屬性值爲TTimer 對象的屬性賦值的過程:
procedure TDemoComponent.SetTimerProp(Value: TTimer);
begin
第16 章 開發新的VCL 組件
·431·
if Value <> FTimer then
begin
if Value <> nil then
begin
if Assigned(FTimer) and (FTimer.Owner = Self) then
FTimer.Free;
FTimer := Value;
FTimer.FreeNotification(self);
end
else //空值
begin
if Assigned(FTimer) and (FTimer.Owner <> Self) then
begin
FTimer := TTimer.Create(self);
FTimer.Name := ’Timer’; //可選,可使結果更友好
FTimer.SetSubComponent(True);
FTimer.FreeNotification(self);
end;
end;
end;
end;
注意上面代碼中,屬性原來的子組件調用了FreeNotification 方法。這樣保證了在它即將銷燬時可
以發送一個消息。發送消息經過調用Notification 方法實現。可重載Notification 方法來處理這個調用,
下面是一個例子:
procedure TDemoComponent.Notification(AComponent: TComponent; Operation: TOperation);
begin
inherited Notification(AComponent, Operation);
if (Operation = opRemove) and (AComponent = FTimer) then
FTimer := nil;
end;
9.爲接口建立屬性
接口能夠做爲published 屬性的值。可是,組件從接口接收消息的機制是不一樣的。在建立子組件屬
性時,屬性設置者調用被設置爲屬性值的組件的FreeNotification 方法。這樣當釋放做爲屬性值的子組
件時,組件能夠更新。然而當屬性值是接口時,程序不能訪問實現接口的組件,所以就不能調用它的
FreeNotification 方法。
爲處理這種狀況,可調用組件的ReferenceInterface 方法。
procedure TDemoComponent.SetMyIntfProp(const Value: IMyInterface);
begin
ReferenceInterface(FIntfField, opRemove);
FIntfField := Value;
ReferenceInterface(FIntfField, opInsert);
end;
調用一個指定接口的ReferenceInterface 與調用另外一組件的FreeNotification 方法相同。所以在從屬
性設置器中調用了ReferenceInterface 後,可重載Notification 方法以便處理來自接口實現的消息:

·432·
procedure TDemoComponent.Notification(AComponent: TComponent; Operation: TOperation);
begin
inherited Notification(AComponent, Operation);
if (Assigned(MyIntfProp)) and (AComponent.IsImplementorOf(MyInftProp)) then
MyIntfProp := nil;
end;
注意Notification 代碼分配nil 給屬性MyIntfProp 而不是給私有的數據域FintfField。這保證
Notification 調用屬性設置者,而後調用ReferenceInterface 以刪除在之前設置屬性值時創建的請求。所
有賦值給接口屬性的過程必須經過屬性設置。
16.3.4 存儲和裝載屬性
Delphi 能夠在表單文件(在VCL 中的爲*.dfm,在CLX 中爲*.xfm)中存儲窗體和組件。一個表
單文件可存儲表單和它的組件。當Delphi 開發者向表單增長組件並保存時,組件必須具備把屬性寫入
表單文件的功能,一樣當裝載Delphi 或執行應用程序時,組件必須從表單文件中恢復。
因爲存儲和裝載能力是從組件繼承,所以大多時候程序員沒必要作什麼就可使表單文件中的組件
工做。可是若是想改變組件存儲方式或裝載時的初始化方式,那就必須瞭解內部機制。
1.存儲和裝載機制的運用
表單描述由表單屬性列表組成,表單中每一個組件中的描述也是類似的。每一個組件,包括表單自身,
負責存儲和裝載自身的描述。
存儲時,組件可寫入與默認值不一樣的全部published 屬性的值。裝載時,組件首先建立本身,設置
全部的屬性爲默認值,而後讀出存儲的非默認屬性值。
這個默認機制知足大多組件的須要,所以通常並不須要修改存儲組件到表單文件的方法。然而有
幾種方法能夠定製適合特殊組件要求的存儲和裝載過程。
2.指定默認值
只要屬性值不一樣於默認值,Delphi 組件就會保存這些值。若是不指定,Delphi 就假定屬性無默認
值,也就是無論取什麼值組件都要存儲屬性值。
指定屬性的默認值能夠經過在屬性聲明最後添加default 指令和新的默認值來完成,也能夠在屬性
重聲明中指定默認值。實際上,屬性重聲明的一個緣由就是要設置不一樣的默認值。
注意:建立對象時指定的默認值並不自動賦給屬性。
所以必須確定組件構造方法能賦給屬性須要的值。組件構造方法沒有設置的屬性值都被假定爲0
值,也就是不管屬性類型是什麼,其存儲內存都是0。這樣數值型的默認值爲0,布爾型爲False,指
針爲nil 等。若是有問題,則要在構造方法中指定屬性值。
3.決定存儲內容
Delphi 是否存儲組件的每一個屬性是能夠控制的。默認地,在類聲明的published 部分的屬性要存儲。
也能夠選擇根本不存儲已有的屬性,或設定由一個函數動態決定是否存儲屬性。爲控制Delphi 是否存
儲屬性,能夠在屬性聲明後面添加stored 指令,後面跟True、False 或一個布爾函數。
4.裝載後的初始化
在組件從存儲中讀取全部的屬性值後,將調用名爲Loaded 的虛方法,它能夠執行要求的初始化。
Loaded 的調用發生在表單和控件顯示以前,因此沒必要考慮由初始化產生的屏幕閃爍。
爲了在裝載完屬性值後初始化組件,能夠重載Loaded 方法。
注意:在全部Loaded 方法中首先要調用繼承的Loaded 方法,保證在初始化本身的組件前正
第16 章 開發新的VCL 組件
·433·
確初始化全部繼承的屬性。
下面代碼來自TDatabase 組件。在裝載後,Database 要從新創建存儲時打開的鏈接並指明如何處
理鏈接中出現的異常。
procedure TDatabase.Loaded;
begin
inherited Loaded; {首先調用繼承方法}
try
if FStreamedConnected then Open {重建鏈接}
else CheckSessionName(False);
except
if csDesigning in ComponentState then {設計時}
Application.HandleException(Self) {由Delphi 處理異常}
else raise; {不然拋出異常}
end;
end;
5.存儲和裝載非published 屬性
在默認狀況下,只有組件的published 屬性能夠裝載和保存到表單文件中。實際上非published 屬
性也能夠保存到表單文件中,可是須要告訴Delphi 如何裝載和保存屬性值。須要保存到表單文件的非
published 屬性一般有兩種狀況:一種是不但願出如今對象編輯器可是是持續化的屬性;另外一種是因爲
保存和裝載「太複雜」,致使Delphi 不知如何讀寫的屬性,例如當一個屬性是TString 對象時,則它不
能依賴Delphi 自動存儲和裝載它表明的字符串,必須使用這種機制。
(1)建立存儲和裝載屬性值的方法
爲存儲和裝載非published 屬性,必須首先建立存儲和裝載屬性值的方法。可有兩種選擇。
*建立TWriterProc 類型的方法保存屬性值,建立TReaderProc 類型的方法裝載屬性值。
這樣能夠充分利用Delphi 內建的保存和裝載簡單類型的功能。若是建立的屬性值不是Delphi 已知
的能夠自動保存和裝載的屬性,能夠採用這個辦法。
*建立兩個TStreamProc 類型的方法。一個用於存儲,一個用於裝載屬性值。TStreamProc 將一個
stream 做爲參數,可經過stream 的方法去讀寫屬性值。
例如在運行時建立表明某組件的一個屬性,因爲組件不是表單設計者建立的,即便Delphi 知道如
何寫值,也不能自動寫值。因爲stream 能夠存儲和保存組件,所以能夠採用第1 種方法。
下面是裝載和存儲一個動態建立的、具備名爲MyCompProperty 的屬性的組件的方法:
procedure TSampleComponent.LoadCompProperty(Reader: TReader);
begin
if Reader.ReadBoolean then
MyCompProperty := Reader.ReadComponent(nil);
end;
procedure TSampleComponent.StoreCompProperty(Writer: TWriter);
begin
Writer.WriteBoolean(MyCompProperty <> nil);
if MyCompProperty <> nil then
Writer.WriteComponent(MyCompProperty);
end;

·434·
(2)重載DefineProperties 方法
一旦建立方法去存儲和裝載屬性值,就要重載組件DefineProperties 方法。當存儲和裝載組件時,
Delphi 就會調用此方法。在DefineProperties 方法中,必須調用當前文件對象的DefineProperty 或
DefineBinaryProperty 方法, 並將它傳給裝載和存儲屬性值的方法。若是裝載和存儲的方法是
TWriterProc 和TReaderProc 類型的,那麼調用的是文件對象的DefineProperty 方法。若是建立的是
TStreamProc 類型的方法,那麼調用的是DefineBinaryProperty。
不管選哪一種方法去定義屬性,都必須將它傳給存儲和裝載屬性值的方法,並使用一個布爾值指明
屬性值是否須要寫(若是值能夠被繼承或有一個默認值,則不須要寫)。
例如採用TReaderProc 中的LoadCompProperty 方法和TWriterProc 中的StoreCompProperty 方法,
能夠按照下面方法去重載DefineProperties:
procedure TSampleComponent.DefineProperties(Filer: TFiler);
function DoWrite: Boolean;
begin
if Filer.Ancestor <> nil then {檢查祖先的繼承值}
begin
if TSampleComponent(Filer.Ancestor).MyCompProperty = nil then
Result := MyCompProperty <> nil
else if MyCompProperty = nil or
TSampleComponent(Filer.Ancestor).MyCompProperty.Name <> MyCompProperty.Name
then
Result := True
else Result := False;
end
else {沒有繼承值,檢查默認值}
Result := MyCompProperty <> nil;
end;
begin
inherited; {容許基類定義屬性}
Filer.DefineProperty(’MyCompProperty’, LoadCompProperty, StoreCompProperty, DoWrite);
end;
16.4 建立事件
事件是系統發生的事件與組件響應的該系統事件的一段代碼之間的鏈接。響應代碼被稱爲事件處
理過程(event handler),它幾乎老是由應用程序員來編寫。經過使用事件,應用程序員不須要改變組
件自己就能定製組件的行爲,這能夠理解爲一種受權。
一般的用戶行爲(例如鼠標的行動)都已經內建到全部的標準組件之中,可是組件開發者也能夠
定義新的事件。事件的實現與屬性相似,所以在建立或改變組件的事件以前應熟悉如何建立屬性。
16.4.1 事件定義
事件是聯接發生的事情與某些代碼的機制,更明確的說,是方法指針,一個指向特定對象實例的
特定方法的指針。
從應用程序員的角度看,事件只是與系統事件(如OnClick)有關的名稱,能給該事件賦特定的
方法供調用,或者把事件看做是由應用程序員編寫的代碼,而事件發生時由系統調用的處理辦法。例
如按鈕Buttonl 有OnClick 方法。默認狀況下,當指定一個值給OnClick 事件時,表單編輯器產生一個
ButtonlClick 事件處理過程,並將其賦給OnClick。當在按鈕上發生Click 事件時,按鈕調用賦給OnClick
第16 章 開發新的VCL 組件
·435·
的方法ButtonlClick。
可是,從組件開發者角度看,事件有更多的含義。最重要的是提供了一個讓用戶編寫代碼響應特
定事情的場所。
1.事件是方法指針
Delphi 使用方法指針實現事件。一個方法指針是指向特定對象實例的特定方法的特定指針。做爲
組件開發者,能將方法指針做爲一個代理。一發現事件發生,就調用由應用程序員定義的該事件的方
法。
方法指針的工做方式與其餘的過程類型相似,但它們保持一個隱含的指向對象實例的指針。當應
用程序員將一個事件處理過程賦值給一個組件的事件,這個賦值不僅是一個具備特定名字的方法,而
且是一個特定類的實例的方法(那個實例一般是包含組件的表單,但不是必定的)。
例如調用Click 事件的處理過程。全部的控件都繼承了一個名爲Click 的動態方法,以處理Click
事件。它的聲明以下:
procedure Click; dynamic;
Click 方法調用應用程序員的Click 事件處理過程。若是應用程序員給控件的OnClick 事件賦予了
處理過程(Handle),那麼當鼠標單擊控件時將致使方法被調用。
2.事件是屬性
組件採用屬性的形式實現事件。與大多數其餘屬性不一樣,事件不能使用方法來實現read 和write
部分。事件屬性使用了相同類型的私有對象域做爲屬性。習慣上,域名在屬性名前加「F」。例如OnClick
方法的指針,保存在TNotifyEvent 類型FOnClick 域中。OnClick 事件屬性的聲明以下:
type
TControl = class(TComponent)
private
FOnClick: TNotifyEvent; {聲明保存方法指針的域}
...
protected
property OnClick: TNotifyEvent read FOnClick write FOnClick;
end;
與其餘類型的屬性同樣,運行時能夠設置和改變事件的值。將事件作成屬性的主要好處是應用程
序員能在設計時使用對象編輯器設置事件處理過程。
3.事件類型是方法指針類型
由於一個事件是指向事件處理過程的指針,所以事件屬性必須是方法指針類型。類似地,全部被
用做事件處理過程的代碼,必須是相應的對象的方法。
全部的事件方法都是過程。爲了與所給類型的事件兼容,一個事件處理過程必須有相同數目、類
型和順序的參數,並以相同的方式傳遞。
Delphi 定義了它自己全部標準事件處理過程的方法類型。當建立本身的事件時,可使用已有的
事件類型,也能夠建立新的。
4.事件處理方法的類型都是過程
雖然編譯器容許聲明事件指針類型爲函數,但不該該這樣聲明事件處理方法。由於空函數的返回
值是不肯定的,因此若是空的事件處理方法是函數的話,則事件處理不必定是有效的。正由於如此,
全部的事件和與它們相關的事件處理方法都應該是過程。
雖然不能用函數作事件處理過程,但能夠用var 參數獲得返回信息。可是這樣處理時必須在調用
事件處理方法以前給參數賦予有效的值,以保證應用程序代碼不必定要改變這個值。
在事件處理過程當中傳遞var 參數的典型例子是TKeyPressEvent 類型的KeyPressed 事件。

·436· TKeyPressEvent 定義中含有兩個參數。一個指示哪一個對象產生該事件,另外一個指示哪一個鍵被按下。 type TKeyPressEvent = procedure(Sender: TObject; var Key: Char) of object; 一般Key 參數包含用戶按下鍵的字符。在某些狀況下,應用程序員可能想改變字符值。例如在編 輯器中強制全部字符爲大寫,在這種狀況下,應用程序員能定義下列的事件處理過程: procedure TForm1.Edit1KeyPressed(Sender: TObject; var Key: Char); begin Key := UpCase(Key); end; 5.事件處理過程是可選的 在爲組件建立事件時,要記住應用程序員可能並不編寫該事件的處理過程。這意味着組件不能因 爲組件用戶沒有編寫特定事件的處理代碼而出錯。 這種事件處理過程的可選性表如今以下兩個方面。 (1)組件用戶並不須要處理全部事件 事件老是不斷地發生在GUI 應用程序中。例如在可視組件上方移動鼠標就引發Windows 發送大 量的鼠標移動消息給組件,組件將鼠標移動消息轉換爲OnMouseMove 事件。在大多數狀況下,應用 程序員不須要關心鼠標移動事件,由於組件不依賴鼠標事件的處理過程。一樣,組件也不能依賴於它 們的事件處理過程。 (2)組件用戶能在事件處理過程當中加入任意的代碼 更進一步,應用程序員能夠在事件處理過程當中加入任何代碼。Delphi 組件庫的組件都支持這種方 式以使所寫代碼產生錯誤的可能性最小。顯然,不能防止用戶代碼出現邏輯錯誤,可是能夠保證在調 用事件以前數據結構獲得初始化,以使應用程序員不能訪問無效數據。

相關文章
相關標籤/搜索