| 後一頁 前一頁 回目錄 回首頁 |
2.1.10.7 將庫單元加入工程 將庫單元加入工程是比較簡單的。無論是您自己建立的庫單元還是Delphi建立的與窗體有關的庫單元,如果已經完成,則先打開您想加入庫單元的工程(可以用Open Project打開工程);再選用File|Open File,然後選擇您想加入的原始程式(.PAS文件),並選擇OK即可。則庫單元被加入到應用程式中。 2.2 用Delphi的物件進行程式設計 Delphi是基於面向物件程式設計的先進開發環境。面向物件的程式設計(OOP)是結構化語言的自然延伸。OOP的先進程式設計方法,會產生一個清晰而又容易擴展及維護的程式。一旦您為您的程式建立了一個物件,您和其他的程式員可以在其他的程式中使用這個物件,完全不必重新編制繁復的代碼。物件的重複使用可以大大地節省開發時間,切實地提高您和其他人的工作效率。 2.2.1 什麼是物件 一個物件是一個數據型式。物件就象記錄一樣,是一種數據結構。按最簡單的理解,我們可以將物件理解成一個記錄。但實際上,物件是一種定義不確切的術語,它常用來定義抽象的事務,是構成應用程式的項目,其內涵遠比記錄要豐富。在本書中,物件可被理解為可視化部件如按鈕、標籤、表等。 了解物件,最關鍵的是掌握物件的特性。一個物件,其最突出的特征有三個:封裝性、繼承性、多態性。 2.2.1.1 物件的封裝性 對物件最基本的理解是把數據和代碼群群組合在同一個結構中,這就是物件的封裝特性。將物件的數據欄位封閉在物件的內部,使得外部程式必需而且只能使用正確的方法才能對要讀寫的數據欄位進行存取。封裝性意味著數據和代碼一起出現在同一結構中,如果需要的話,可以在數據周圍砌上“圍牆”,只有用物件類的方法才能在“圍牆”上打開缺口。 2.2.1.2 物件的繼承性 繼承性的含義直接而且顯然。它是指把一個新的物件定義成為已存在物件的後代;新物件繼承了舊類的一切東西。在往新物件中加入任何新內容以前,父類的每一個欄位和方法都已存在於子類中,父類是建立子類的基石。 2.2.1.3 物件的多態性 多態性是在物件體系中把設想和實現分開的手段。如果說繼承性是系統的佈局手段,多態性就是其功能實現的方法。多態性意味著某種概括的動作可以由特定的方式來實現,這取決於執行該動作的物件。多態性允許以類似的方式處理類體系中類似的物件。根據特定的任務,一個應用程式被分解成許多物件,多態性把進階設計處理的設想如新物件的建立、物件在螢幕上的重顯、程式執行的其它抽象描述等,留給知道該如何完美的處理它們的物件去實現。 2.2.1.4 通過Delphi實例了解物件 讓我們結合Delphi的實例討論物件的概念: 當您要建立一個新工程時,Delphi 將顯示一個窗體作為設計的基礎。在程式編輯器中,Delphi將這個窗體敘述為一個新的物件型式,並同時在與窗體相關聯的庫單元中產生了建立這個新窗體物件的程式代碼。 unit Unit1; interface uses SysUtils, Windows, Messages, Classes, Graphics, Controls, Forms, Dialogs; type TForm1 = class(TForm) {窗體的型式敘述開始} private { Private declarations } public { Public declarations } end; {窗體的型式敘述結束} var Form1: TForm1; {敘述一個窗體變數} implementation {$R *.DFM} end. 新的窗體型式是TForm1,它是從TForm繼承下來的一個物件。它具有物件的特征:含有欄位或方法。由於您未給窗體加入任何部件,所以它只有從TForm類中繼承的欄位和方法,在窗體物件的型式敘述中,您是看不到任何欄位、方法的敘述的。Form1稱為TForm1型式的實例(instance)。您可以敘述多個物件型式的實例,例如在多文檔介面(MDI)中管理多個子視窗時就要進行這樣的敘述。每一個實例都有自己的敘述,但所有的實例卻共用相同的代碼。 假設您向窗體中加入了一個按鈕部件,並對這個按鈕建立了一個OnClick事件處理過程。再查看Unit1的原始程式,會發現TForm1的型式敘述部分如下: type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } end; 現在TForm1物件有了一個名為Button1的欄位:它是您在窗體中加入的按鈕。TButton是一個物件型式,Button1是Tbutton的一個實例。它被TForm1物件所包含,作為它的數據欄位。每當您在窗體中加入一個部件時,部件的標簽就會作為TFom1的欄位加入到型式敘述中來。在Delphi中,您所編寫的事件處理過程都是窗體物件的方法。每當您建立一個事件處理過程,就會在窗體的物件型式中敘述一個方法。 當您使用Object Inspector來改變物件(部件)的標簽時,這個標簽的改變會反映到程式中。例如,在Object Inspector中將Form1的Name屬性命名為ColorBox,您會發現在型式敘述部分,會將前文的TForm1改為: TColorBox=class(TForm); 並且在變數敘述部分,會敘述ColorBox為TColorBox型式的變數,由Delphi自動產生的事件處理過程標簽會自動改為TColorBox.Button1Click;但您自行編寫的實現部分的代碼卻不會被自動修改。因此,如果您在改變Name屬性前編寫了程式,則您必須將事件處理過程中的物件標簽進行改變。所以,原先的Form1.Color要改為ColorBox.Color。 2.2.2 從一個物件中繼承數據和方法 前面的TForm1型式是很簡單的,因為它只含有欄位Button1和方法Button1Click。但是在這個窗體上,您可以改變窗體的大小、加入或解除窗體的最大最小化按鈕,或設定這個窗體為MDI介面。對於一個只包含一個欄位和方法的物件來講,您並沒有看到顯式的支援程式。在窗體上按一下滑鼠或用Object Inspector的上端的Object Selector選中Form1物件,按動F1查閱它的在線輔助敘述,您會在Properties和Method中找到它的繼承到的全部屬性和方法。這些是在TForm型式中敘述的,TForm1是TForm的子類,直接繼承了它所有的欄位、方法、屬性和事件。例如窗體的顏色屬性Color就是在TForm中敘述的。當您在工程中加入一個新窗體時,就等於加入了一個基本模型。通過不斷地在窗體中加入部件,您就自行定義了一個新的窗體。要自定義任何物件,您都將從已經存在的物件中繼承欄位和方法,建立一個該種物件的子類。例如物件TForm1就被敘述為物件TForm的子類,擁有一個窗體部件的基本屬性或方法。只有當您在窗體中加入了部件或編寫了事件處理過程時,Form1才成為您自己的型式。 一個比較特殊的物件是從一個範圍較廣或較一般的物件中繼承下來的,它是這個特別物件的祖先,這個物件則稱為祖先的後代。一個物件只能有一個直接的祖先,但是它可以有許多後代。TForm是TForm1型式的祖先,所有的窗體物件都是TForm的後代。 用F1查閱窗體的在線輔助敘述時,您會發現TForm被稱為component(部件)。這是因為所有的部件都是物件。 在這個結構中所有的部件都是物件。部件型式TComponent從TObject型式中繼承數據和程式代碼,並具有額外的可以用作特殊用途的屬性、方法、事件,所以部件可以直接和用戶打交道,記錄它的狀態並存貯到文件中等等。控制型式TControl從TComponent中繼承而來,又增加了新的功能,如它可以顯示一個物件。在上圖中,雖然TCheckBox不是直接由TObject繼承來的,但是它仍然有任何物件所擁有的屬性,因為在VCL結構中,TCheckBox終究還是從TObject 中繼承了所有功能的特殊物件,但它還有些自行定義的獨到的功能,如可以選擇記錄狀態等。 2.2.3 物件的範圍 2.2.3.1 關於物件的範圍 一個物件的範圍決定了它的數據欄位、屬性值、方法的活動範圍和存取範圍。在一個物件的敘述部分敘述的數據欄位、屬性值、方法都只是在這個物件的範圍中,而且只有這個物件和它的後代才能擁有它們。雖然這些方法的實際程式代碼可能是在這個物件之外的程式庫單元中,但這些方法仍然在這個物件的範圍內,因為它們是在這個物件的敘述部分中敘述的。 當您在一個物件的事件處理過程中編寫程式代碼來存取這個物件的屬性值、方法或欄位時,您不需要在這些標識符之前加上這個物件變數的標簽。例如,如果您在一個新窗體上加入一個按鈕和一個編輯框,並為這個按鈕編寫OnClick事件處理過程: procedure TForm1.Button1Click(Sender:Tobject); begin Color :=clFuchsia; Edit1.Color :=clLime; end; 其中的第一行語句是為整個窗體Form1著色。您也可以編寫如下:
Form1.Color :=clFuchsia; 但您可以不必加上Form1.,因為Button1Click方法是在TForm1物件的範圍�堙C當您在一個物件的範圍中時,您可以省略所有這個物件中的屬性值、方法、欄位之前的物件標識符。但是當您編寫第二個語句改變編輯框的底色時,因為此時您想存取的是TEdit1物件的Color屬性,而不是TForm1型式的,所以您需要通過在屬性前面加上編輯框的標簽來指明Color屬性值的範圍。如果不指明,Delphi會象第一個語句一樣,將窗體的顏色變成綠色。因為Edit1部件是在窗體中的,它是窗體的一個數據欄位,所以您同樣不必指明其從屬關係。 如果Edit1是在其他窗體中,那麼您需要在編輯框之前加上這個船體物件的標簽了。例如,如果Edit1是在Form2之中,那它是Form2敘述的一個數據欄位,並位於Form2的範圍中,那麼您需要將第二句改為: Form2.Edit1.Color := clLime; 而且需要把Unit2加入Unit1的uses子句中。 一個物件的範圍擴展到這個物件的所有後代。TForm的所有屬性值、方法和事件都在TForm1的範圍中,因為TForm1是TForm的後代。您的應用程式不能敘述和祖先的數據欄位重名的型式、變數等。如果Delphi顯示了一個標識符被重複定義的資訊,就有可能是一個數據欄位和其祖先物件(例如TForm)的一個數據欄位有了相同的標簽。可以嘗試改變這個標識符的標簽。 2.2.3.2 重載一個方法 您可以重載(Override)一個方法。通過在後代物件中敘述一個與祖先物件重名的方法,就可以重載一個方法。如果想使這個方法在後代物件中作和祖先物件中一樣的工作但是使用不同的方式時,您就可以重載這個方法。Delphi不推薦您經常重載方法,除非您想建立一個新的部件。重載一個方法,Delphi編譯器不會給出錯誤或警告提示資訊。 2.2.4 物件公有欄位和私有欄位的敘述 當使用Delphi的環境來建立應用程式時,您可以在一個TForm的後代物件中加入數據欄位和方法,也可以通過直接修改物件型式敘述的方法來為一個物件加上欄位和方法,而不是把一個部件加入窗體或事件處理過程中。 您可以在物件的Public或Private部分加入新的數據欄位和方法。Public和Private是Object Pascal的保留字。當您在工程中加入新的窗體時,Delphi開始建立這個新窗體物件。每一個新的物件都包含public和private指示,以便您在代碼中加入數據欄位和方法。在public部分中敘述其它庫單元中物件的方法也可以存取的數據欄位或方法。在private部分的敘述有存取的限制。如果您在private中敘述欄位和方法,那麼它在敘述這個物件的庫單元外是不透明的,而且不能被存取。private中可以敘述只能被本庫單元方法存取的數據欄位和本庫單元物件存取的方法。過程或函數的程式代碼可以放在庫單元的implementation部分。 2.2.5 存取物件的欄位和方法 當您想要改變一個窗體物件的一個欄位的某個屬性,或是呼叫它的一個方法時,您必須在這個屬性標簽或呼叫方法之前加上這個物件的標簽。例如,如果您的窗體上有一個編輯框部件,而您需要在執行中改變它的Text屬性,需要編寫下列的代碼: Edit1.Text := 'Welcome to Delphi'; 同樣,清除編輯框部件中選中的文本,可以呼叫TEdit部件的相應方法: Edit1.ClearSelection; 如果您想改變一個窗體物件中一個物件欄位的多個屬性或呼叫多個方法時,使用with語句可以簡化您的程式。with語句在物件中可以和在記錄中一樣方便地使用。下面的事件處理過程在響應OnClick事件時,會對一個列示方塊作多個調整: procedure TForm1.Button1Click(Sender:TObject); begin ListBox1.Clear; ListBox1.MultiSelect :=True; ListBox1.Item.Add('One'); ListBox1.Item.Add('Two'); ListBox1.Item.Add('Three'); ListBox1.Sorted :=Ture; ListBox1.FontStyle :=[fsBold]; ListBox1.Font.Color :=clPurple; ListBox1.Font.Name :='Times New Roman'; ListBox1.ScaleBy(125,100); end; 如果使用了With語句,則程式如下: procedure TForm1.Button1Click(Sender:TObject); begin with (ListBox1) do begin Clear; MultiSelect :=True; Item.Add('One'); Item.Add('Two'); Item.Add('Three'); Sorted :=Ture; FontStyle :=[fsBold]; Font.Color :=clPurple; Font.Name :='Times New Roman'; ScaleBy(125,100); end; end; 使用with語句,您不必在每一個屬性或方法前加上ListBox1標識符,在With語句之內,所有的屬性或呼叫方法對於ListBox這個物件而言都是在它的範圍內的。 2.2.6 物件變數的賦值 如果兩個變數型式相同或相容,您可以把其中一個物件變數賦給另一個物件變數。例如,物件TForm1和TForm2都是從TForm繼承下來的型式,而且Form1和Form2已被敘述過,那麼您可以把Form1賦給Form2: Form2 :=Form1; 只要賦值的物件變數是被賦值的物件變數的祖先型式,您就可以將一個物件變數賦給另一個物件變數。例如,下面是一個TDataForm的型式敘述,在變數敘述部分一共敘述了兩個變數:AForm和DataForm。 type TDataForm = class(TForm) Button1:TButton; Edit1:TEdit; DataGrid1:TDataGrid; Database1:TDatabase; TableSet1:TTableSet; VisibleSession1:TVisibleSession; private {私有欄位敘述} public {公有欄位敘述} end; var AForm:TForm; DataForm:TDataForm; 因為TDataForm是TForm型式的後代,所以Dataform是AForm的後代,因此下面的賦值語句是合法的: AForm :=DataForm; 這一點在Delphi中是極為重要的。讓我們來看一下應用程式呼叫事件處理過程的過程,下面是一個按鈕部件的OnClick事件處理過程: procedure TForm1.Button1Click(Sender:TObject); begin end; 您可以看到TObject類在Delphi的Visual Component Library的頂部,這就意味著所有的Delphi物件都是TObject的後代。因為Sender是TObject型式,所以任何物件都可以賦值給它。雖然您沒有看見賦值的程式代碼,但事實上發生事件的部件或控制部件已經賦給Sender了,這就是說Sender的值是響應發生事件的部件或控制部件的。 您可以使用保留字is來測試Sender以便找到呼叫這個事件處理過程的部件或控制部件的型式。Delphi中的一個顯示drag-and-drop的DRAGDROP.DPR工程。加載它,可以查閱到DROPFONT.PAS庫單元的代碼,在Memo1DragOver方法中檢查了一個物件變數的型式。在這種情形下,參數是Source而不是Sender。 procrdure TForm1.Memo1DragOver(SenderSource:TObject;X,Y:integer; State:TDragState;var Accept:Boolean); begin Accept :=Source is TLabel; end; Source參數也是TObject型式,Source被賦值為那個被拖曳的物件。用Memo1DragOver方法的目的是確保只有標籤可以被拖曳。Accept是布爾型參數,如果Accept為True,那麼用戶選擇的部件可以被拖曳;反之當Accept的值為False時,用戶就不可以拖曳選擇控制部件。is保留字檢查Source是否TLabel的型式,所以Accept只有在用戶拖曳一個標籤時才為真,並作為變參輸出到函數之外。 下面的drag-and-drop展示的Memo1DragDrop事件處理過程中也使用了Source參數。這個方法是為了把Memo部件的字型改變成和放入這個備註控制部件的標籤一樣的字型: procedure TForm1.Memo1DragDrop(SenderSource:TObject; X,Y:Integer); begin Memo1.Font := (Source as TLabel).Font; end; 當您在這個事件處理過程中編寫賦值語句時,開發人員並不知道用戶會放入哪一個標籤,只有通過參考這個標籤的標簽(Source as TLabel)用戶才能知道,並把標籤型式賦給Memo1.TFont。Source包含了用戶拖放控制部件的標簽,只有當Source是一個標籤時,這個事件處理過程才允許這個賦值發生。 2.2.7 建立非可視化物件 您在Delphi中使用的大部分物件都是您在設計和執行期間可以看見的部件,例如編輯框、按鈕等;一些部件,如一般的對話方塊(Common dialog box)等,在設計時看不見,而在執行時可以看見;另外有些部件,例如計時器(Timer)、數據源(Data Source)部件等,在程式的執行期間沒有任何可視化的顯示,但您卻可以在您的應用程式中使用它們。 2.2.7.1敘述一個非可視化物件 下面,通過一個簡單的例子講述如何建立自己的非可視化物件: 您可以用如下的方法,建立一個自己的TEmployee非可視化物件: type Temployee = class(TObject); Name := String[25]; Title := String[25]; HourlyPayRate : Double; function CalculatePayAmount:Double; end; 在這種情況下,TEmployee從TObject繼承下來,且包含三個欄位和一個方法。把您建立的型式敘述放在庫單元中的敘述部分,並和窗體敘述放在一起。在這個程式庫單元的變數敘述部分,敘述一個新型式的變數: var Employee : TEmployee; 2.2.7.2用Create方法建立物件實例 TEmployee只是一個物件型式。除非通過一個構造函數的呼叫從而被實例取代或建立,否則一個物件並不存儲在記憶體中。構造函數是一個方法,它為新物件組態記憶體並且指向這個新的物件。這個新的物件也被稱為這個物件型式的一個實例。 建立一個物件的實例,需要呼叫Create方法,然後構造函數把這個實例賦給一個變數。如果您想敘述一個TEmployee型式的實例,在您存取這個物件的任何欄位之前,您的程式代碼必須呼叫Create。 Employee := TEmployee.Create; Create方法並沒有在TEmployee型式中敘述,它繼承自TObject型式。因為TEmployee是TObject的子類,所以它可以呼叫Create方法而建立一個TEmployee實例。然後把它賦給Employee變數。在建立了一個這樣的物件後,您就可以象使用其他的Delphi物件一樣存取Employee物件了。 2.2.7.3 撤銷物件 當您使用完物件後,您應該及時撤銷它,以便把這個物件佔用的記憶體釋放出來。您可以通過呼叫一個登出方法來撤銷您的物件,它會釋放分配給這個物件的記憶體。 Delphi的登出方法有兩個:Destroy和Free。Delphi建議使用Free,因為它比Destroy更為安全,同時呼叫Free會產生效率更高的代碼。 您可以用下列的語句釋放用完的Employee物件: Employee.Free; 和Create方法一樣,Free方法也是TEmployee從TObject中繼承過來的。把您的登出放在try…finally程式模群組的finally部分,而把物件的程式代碼放在try部分是程式設計的好習慣。這樣,即使您的程式代碼在使用物件時發生了異常事件,也會確保您為這個物件分配的記憶體會被釋放。關於異常處理和try…finally程式模群組的資訊以及建立非可視化物件的例子,在後文中還將仔細講述。 |
後一頁 前一頁 回目錄 回首頁 |