xiaojinet 的 delphi Tstrings 類(lèi)
根據(jù)下標(biāo)存取列表中的字符串是最常見(jiàn)的一種操作,用法示意如下:
StringList1.Strings[0] := '字符串1';
注意在Delphi中,幾乎所有的列表的下標(biāo)都是以0為底的,也就是說(shuō)Strings[0]是列表中的第一個(gè)字符串。另外,由于Strings屬性是字符串列表類(lèi)的默認(rèn)屬性,因此可以省略Strings,直接用下面的簡(jiǎn)便方法存取字符串:
StringList1[0] := '字符串1';
定位一個(gè)列表中特定的字符串的位置,可以使用IndexOf方法,IndexOf方法將會(huì)返回在字符串列表中的第一個(gè)匹配的字符串的索引值,如果沒(méi)有匹配的字符串則返回-1。比如我們可以使用IndexOf方法來(lái)察看特定文件是否存在于文件列表框中,代碼示意如下:
if FileListBox1.Items.IndexOf('TargetFileName') > -1
...
有一點(diǎn)不方便的是TStrings類(lèi)沒(méi)有提供一個(gè)方法可以查找除了第一個(gè)匹配字符串外其他同樣匹配的字符串的索引,只能是自己遍歷字符串列表來(lái)實(shí)現(xiàn),這點(diǎn)不如C++中的模版容器類(lèi)以及相關(guān)的模版算法強(qiáng)大和方便。下面是一個(gè)遍歷字符串列表的示意,代碼遍歷列表框中的所有字符串,并將其全部轉(zhuǎn)化為大寫(xiě)的字符串:
procedure TForm1.Button1Click(Sender: TObject);var Index:
Integer;
begin
for Index := 0 to ListBox1.Items.Count - 1 do
ListBox1.Items[Index] := UpperCase(ListBox1.Items[Index]);
end;
前面我們看到了,要想向字符串列表中添加字符串,直接使用Add方法就可以了,但是Add方法只能將字符串加入到列表的末尾,要想在列表的指定位置添加字符串,需要使用Insert方法,下面代碼在列表的索引為2的位置添加了字符串:
StringList1.Insert(2, 'Three');
如果要想將一個(gè)字符串列表中的所有字符串都添加到另一個(gè)字符串列表中,可以使用AddStrings方法,用法如下:
StringList1.AddStrings(StringList2);
要想克隆一個(gè)字符串列表的所有內(nèi)容,可以使用Assign方法,例如下面的方法將Combox1中的字符串列表復(fù)制到了Memo1中:
Memo1.Lines.Assign(ComboBox1.Items);
要注意的是使用了Assign方法后,目標(biāo)字符串列表中原有的字符串會(huì)全部丟失。
同對(duì)象關(guān)聯(lián)
前面說(shuō)了我們可以將字符串同對(duì)象綁定起來(lái),我們可以使用AddObject或者InsertObject方法向列表添加同字符串關(guān)聯(lián)的對(duì)象,也可以通過(guò)Objects屬性直接將對(duì)象同特定位置的字符串關(guān)聯(lián)。此外TStrings類(lèi)還提供了IndexOfObject方法返回指定對(duì)象的索引,同樣的Delete,Clear和Move等方法也可以作用于對(duì)象。不過(guò)要注意的是我們不能向字符串中添加一個(gè)沒(méi)有同字符串關(guān)聯(lián)的對(duì)象。
同視圖交互
剛剛學(xué)習(xí)使用Delphi的人都會(huì)為Delphi
IDE的強(qiáng)大的界面交互設(shè)計(jì)功能所震驚,比如我們?cè)诖绑w上放上一個(gè)ListBox,然后在object
Inspector中雙擊它的Items屬性(TStrings類(lèi)型),在彈出的對(duì)話(huà)框中,見(jiàn)下圖,我們輸入一些字符串后,點(diǎn)擊確定,關(guān)閉對(duì)話(huà)框,就會(huì)看到窗體上的ListBox中出現(xiàn)了我們剛才輸入的字符串。
可以我們?cè)赥Strings和默認(rèn)的實(shí)現(xiàn)類(lèi)TStringList的源代碼中卻找不到同ListBox相關(guān)的代碼,那么這種界面交互是如何做到的呢?
秘密就在于TListBox的Items屬性類(lèi)型實(shí)際上是TStrings的基類(lèi)TListBoxStrings類(lèi),我們看一下這個(gè)類(lèi)的定義:
TListBoxStrings = class(TStrings)
private
ListBox: TCustomListBox;
protected
…
public
function Add(const S: string): Integer; override;
procedure Clear; override;
procedure Delete(Index: Integer); override;
procedure Exchange(Index1, Index2: Integer); override;
function IndexOf(const S: string): Integer; override;
procedure Insert(Index: Integer; const S: string); override;
procedure Move(CurIndex, NewIndex: Integer); override;
end;
可以看到TListBoxStrings類(lèi)實(shí)現(xiàn)了TStrings類(lèi)的所有抽象方法,同時(shí)在內(nèi)部有一個(gè)ListBox的私有變量。我們?cè)倏匆幌耇ListBoxStrings的Add方法:
function TListBoxStrings.Add(const S: string): Integer;
begin
Result := -1;
if ListBox.Style in [lbVirtual, lbVirtualOwnerDraw] then
exit;
Result := SendMessage(ListBox.Handle, LB_ADDSTRING, 0,
Longint(PChar(S)));
if Result < 0 then raise
EOutOfResources.Create(SInsertLineError);
end;
可以看到TListBoxStrings在內(nèi)部并沒(méi)有保存添加的字符串,而是直接向Windows的原生列表盒控件發(fā)送消息實(shí)現(xiàn)的代碼添加,而Windows的原生列表盒是一個(gè)MVC的組件,當(dāng)內(nèi)部的數(shù)據(jù)發(fā)生變化時(shí),會(huì)自動(dòng)改變視圖顯示,這就是為什么我們?cè)谠O(shè)計(jì)器中輸入的字符串會(huì)立刻顯示在窗體列表框中的原因了。
于是我們也就知道為什么Borland將TStrings設(shè)計(jì)為一個(gè)抽象的類(lèi)而沒(méi)有提供一個(gè)默認(rèn)的存儲(chǔ)方式,就是因?yàn)楹芏嗟慕缑娼M件在內(nèi)部對(duì)數(shù)據(jù)的存儲(chǔ)有很多不同的方式,Borland決定針對(duì)不同的組件提供不同的存儲(chǔ)和交互方式。同樣的我們要編寫(xiě)的組件如果有TStrings類(lèi)型的屬性,同時(shí)也要同界面或者其它資源交互的話(huà),不要使用TStringList來(lái)實(shí)現(xiàn),而應(yīng)該從TStrings派生出新類(lèi)來(lái)實(shí)現(xiàn)更好的交互設(shè)計(jì)。
還有一點(diǎn)要說(shuō)明的是,Delphi的IDE只在使用Delphi的流機(jī)制保存組件到窗體設(shè)計(jì)文件DFM文件中的時(shí),做了一些特殊的處理,能夠自動(dòng)保存和加載Published的TStrings類(lèi)型的屬性,下面就是一個(gè)ListBox儲(chǔ)存在窗體設(shè)計(jì)文件DFM中文本形式示意(在窗體設(shè)計(jì)階段,我們可以直接使用View
As Text右鍵菜單命令看到下面的文本),我們可以注意到在設(shè)計(jì)時(shí)我們輸入的Items的兩個(gè)字符串被保存了起來(lái):
object ListBox1: TListBox
Left = 64
Top = 40
Width = 145
Height = 73
ItemHeight = 16
Items.Strings = (
'String1'
'String2')
TabOrder = 1
end
隨后如果運(yùn)行程序時(shí),VCL庫(kù)會(huì)使用流從編譯進(jìn)可執(zhí)行文件的DFM資源中將Items.Strings列表加載到界面上,這樣就實(shí)現(xiàn)了設(shè)計(jì)是什么樣,運(yùn)行時(shí)也是什么樣的所見(jiàn)即所得。
鍵-值對(duì)
在實(shí)際開(kāi)發(fā)過(guò)程中,我們經(jīng)常會(huì)碰到類(lèi)似于字典的定位操作的通過(guò)鍵查找相應(yīng)值的操作,比如通過(guò)用戶(hù)名查找用戶(hù)相應(yīng)的登陸密碼等。在C++和Java中,標(biāo)準(zhǔn)模版庫(kù)和JDK都提供了Map類(lèi)來(lái)實(shí)現(xiàn)鍵-值機(jī)制,但是Delphi的VCL庫(kù)卻沒(méi)有提供這樣的類(lèi),但是TStrings類(lèi)提供了一個(gè)簡(jiǎn)易的Map替代的實(shí)現(xiàn),那就是Name-Value對(duì)。
對(duì)于TStrings來(lái)說(shuō),所謂的Name-Value對(duì),實(shí)際上就是’Key=Value’這樣包含=號(hào)的分割的字符串,等號(hào)左邊的部分就是Name,等號(hào)右邊的部分就是Value。TStrings類(lèi)提供了IndexOfName和Values等屬性方法來(lái)操作Name-Value對(duì)。下面是用法示意:
var
StringList1:TStrings;
Begin
StringList1:=TStringList.Create;
//添加用戶(hù)名-密碼對(duì)
StringList1.Add(‘hubdog=aaa’);
StringList1.Add(‘hubcat=bbb’);
….
//根據(jù)用戶(hù)名hubdog查找密碼
Showmessage(StringList1.Values[StringList1.IndexOfName(‘hubdog’)]);
End;
從Delphi7開(kāi)始,TStrings類(lèi)增加了一個(gè)NameValueSeparator屬性,我們可以通過(guò)這個(gè)屬性修改默認(rèn)的Name-Value分割符號(hào)為=號(hào)以外的其它符號(hào)了。還要說(shuō)明的是,TStrings的Name-Value對(duì)中的Name可以不唯一,這有點(diǎn)類(lèi)似于C++中的MultiMap,這時(shí)通過(guò)Values[Names[IndexOfName]]下標(biāo)操作取到的值不一定是我們所需要的,另外TStrings類(lèi)的Name-Value對(duì)的查找定位是采用的遍歷的方式,而不同于Java和C++中的Map是基于哈希表或者樹(shù)的實(shí)現(xiàn),因此查找和定位的效率非常低,不適用于性能要求非常高的場(chǎng)景。不過(guò)從Delphi6開(kāi)始,VCL庫(kù)中在IniFiles單元中提供了一個(gè)基于哈希表的字符串列表類(lèi)THashedStringList類(lèi)可以極大的提高查找定位的速度。
THashedStringList類(lèi)
一般來(lái)說(shuō),通過(guò)鍵來(lái)查找值最簡(jiǎn)單的辦法是遍歷列表對(duì)列表中的鍵進(jìn)行比較,如果相等則獲取相應(yīng)的鍵值。但是這種簡(jiǎn)單的辦法也是效率最差的一種辦法,當(dāng)列表中的項(xiàng)目比較少時(shí),這種辦法還可以接受,但是如果列表中項(xiàng)目非常多的話(huà),這種方法會(huì)極大的影響軟件的運(yùn)行速度。
這時(shí)我們可以使用哈希表來(lái)快速的通過(guò)鍵值來(lái)存取列表中的元素。由于本書(shū)并不是一本數(shù)據(jù)結(jié)構(gòu)和算法的書(shū),因此我無(wú)意在這里討論哈希表背后的理論知識(shí),我們只要知道哈??梢酝ㄟ^(guò)鍵快速定位相應(yīng)的值就可以了,對(duì)此感興趣的非計(jì)算機(jī)專(zhuān)業(yè)的人可以去察看相關(guān)的書(shū),這里就不贅述了。
Delphi6中提供的THashedStringList類(lèi)沒(méi)有提供任何的新的方法,只是對(duì)IndexOf和IndexOfName函數(shù)通過(guò)哈希表進(jìn)行了性能優(yōu)化,下面這個(gè)例子演示了TStringList和THashedStringList之間的性能差異:
unit CHash;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,
Forms,
Dialogs, StdCtrls, Inifiles;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
HashedList: THashedStringList;
DesList: TStringList;
List: TStringList;
public
{ Public declarations }
procedure Hash;
procedure Iterate;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
I:Integer;
begin
Screen.Cursor := crHourGlass;
try
//初始化系統(tǒng)
for I := 0 to 5000 do
begin
HashedList.Add(IntToStr(i));
List.Add(IntToStr(i));
end;
Hash;
DesList.Clear;
Iterate;
finally
Screen.Cursor := crDefault;
end;
end;
procedure TForm1.Hash;
var
I, J: Integer;
begin
//基于哈希表的定位
for I := 3000 to 4000 do
begin
DesList.Add(IntToStr(HashedList.IndexOf(IntToStr(I))));
end;
end;
procedure TForm1.Iterate;
var
I, J: Integer;
begin
//基于遍歷方式定位
for I := 3000 to 4000 do
begin
DesList.Add(IntToStr(List.IndexOf(IntToStr(I))));
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
HashedList := THashedStringList.Create;
DesList := TStringList.Create;
List := TStringList.Create;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
HashedList.Free;
DesList.Free;
List.Free;
end;
end.
上面代碼中的Hash過(guò)程,采用了新的THashedStringList類(lèi)來(lái)實(shí)現(xiàn)的查找,而Iterate過(guò)程中使用了原來(lái)的TStringList類(lèi)的IndexOfName來(lái)實(shí)現(xiàn)的查找。采用GpProfile(注:GpProfile的用法參見(jiàn)工具篇的性能分析工具GpProfile章節(jié))對(duì)兩個(gè)過(guò)程進(jìn)行了性能比較后,從下圖可以看到Hash執(zhí)行同樣查找動(dòng)作只用了0.7%的時(shí)間,而Iterate方法則用了99.3%的時(shí)間,可以看到在字符串列表項(xiàng)目數(shù)在幾千的數(shù)量級(jí)別時(shí),基于哈希表的查詢(xún)速度是原有方法的100多倍。
不過(guò)要說(shuō)明的是,THashedStringList同TStringList類(lèi)相比,雖然查找的速度大大提高了,但是在添加、刪除字符串后再次進(jìn)行查找操作時(shí),需要重新計(jì)算哈希函數(shù),所以如果頻繁的進(jìn)行刪除或者添加同查找的復(fù)合操作,執(zhí)行的速度很有可能比TStringList還要慢,這是使用時(shí)需要注意的。
TBucketList和TObjectBucketList類(lèi)
從Delphi6開(kāi)始,VCL的Contnrs單元中又增加了兩個(gè)新的容器類(lèi)TBucketList和TObjectBucketList。TBucketList實(shí)際上也是一個(gè)簡(jiǎn)單基于哈希表的指針-指針對(duì)列表。接口定義如下:
TBucketList = class(TCustomBucketList)
…
public
destructor Destroy; override;
procedure Clear;
function Add(AItem, AData: Pointer): Pointer;
function Remove(AItem: Pointer): Pointer;
function ForEach(AProc: TBucketProc; AInfo: Pointer = nil):
Boolean;
procedure Assign(AList: TCustomBucketList);
function Exists(AItem: Pointer): Boolean;
function Find(AItem: Pointer; out AData: Pointer): Boolean;
property Data[AItem: Pointer]: Pointer read GetData write
SetData; default;
end;
類(lèi)的Add方法現(xiàn)在接受兩個(gè)參數(shù)AItem和AData,我們可以把它看成是指針版的Map實(shí)現(xiàn)(從容器類(lèi)來(lái)看,
Delphi從語(yǔ)言的靈活性來(lái)說(shuō)不如C++,為了實(shí)現(xiàn)不同類(lèi)型的哈希Map容器,Delphi需要派生很多的類(lèi),而C++的Map是基于模版技術(shù)來(lái)實(shí)現(xiàn)的,容器元素的類(lèi)型只要簡(jiǎn)單的聲明一下就能指定了,使用起來(lái)非常方便。而從簡(jiǎn)單性來(lái)說(shuō),則不如Java的容器類(lèi),因?yàn)镈elphi中的String是原生類(lèi)型,而不是類(lèi),并且Delphi還提供對(duì)指針的支持,因此要為指針和字符串提供不同的Map派生類(lèi)),類(lèi)中的Exists和Find等方法都是通過(guò)哈希表來(lái)實(shí)現(xiàn)快速數(shù)據(jù)定位的。同時(shí),同一般的列表容器類(lèi)不同,TBucketList不提供通過(guò)整數(shù)下標(biāo)獲取列表中的元素的功能,不過(guò)我們可以使用ForEach方法來(lái)遍歷容器內(nèi)的元素。
TObjectBucketList是從TBucketList派生的基類(lèi),沒(méi)有增加任何新的功能,唯一的不同之處就是容器內(nèi)的元素不是指針而是對(duì)象了,實(shí)現(xiàn)了更強(qiáng)的類(lèi)型檢查而已。
其它容器類(lèi)
TThreadList類(lèi)
TThreadList類(lèi)實(shí)際上就是一個(gè)線(xiàn)程安全的TList類(lèi),每次添加或者刪除容易中指針時(shí),TThreadList會(huì)調(diào)用EnterCriticalSection函數(shù)進(jìn)入線(xiàn)程阻塞狀態(tài),這時(shí)其它后續(xù)發(fā)生的對(duì)列表的操作都會(huì)阻塞在那里,直到TThreadList調(diào)用UnLockList釋放對(duì)列表的控制后才會(huì)被依次執(zhí)行。在多線(xiàn)程開(kāi)發(fā)中,我們需要使用TThreadList來(lái)保存共享的資源以避免多線(xiàn)程造成的混亂和沖突。還要注意的是TThreadList有一個(gè)Duplicates布爾屬性,默認(rèn)為T(mén)rue,表示列表中不能有重復(fù)的指針。設(shè)定為False將允許容器內(nèi)有重復(fù)的元素。
TInterfaceList類(lèi)
在Classes單元中,VCL還定義了一個(gè)可以保存接口的列表類(lèi)。我們可以向列表中添加接口類(lèi)型,這個(gè)類(lèi)的操作方法同其它的列表類(lèi)沒(méi)有什么區(qū)別,只不過(guò)在內(nèi)部使用TThreadList作為容器實(shí)現(xiàn)了線(xiàn)程安全。
擬容器類(lèi)TBits類(lèi)
在Classes.pas還有一個(gè)特殊的TBits類(lèi),接口定義如下:
TBits = class
…
public
destructor Destroy; override;
function OpenBit: Integer;
property Bits[Index: Integer]: Boolean read GetBit write SetBit;
default;
property Size: Integer read FSize write SetSize;
end;
它可以按位儲(chǔ)存布爾值,因此可以看成是一個(gè)原生的Boolean值的容器類(lèi),但是它缺少列表類(lèi)的很多方法和特性,不能算是一個(gè)完整的容器,因此我們稱(chēng)它為擬容器類(lèi)。
在我們開(kāi)發(fā)過(guò)程中,經(jīng)常需要表示一些類(lèi)似于開(kāi)關(guān)的二元狀態(tài),這時(shí)我們用TBits來(lái)表示一組二元狀態(tài)非常方便,同時(shí)TBits類(lèi)的成員函數(shù)主要是用匯編語(yǔ)言寫(xiě)的,位操作的速度非???。二元狀態(tài)組的大小通過(guò)設(shè)定TBits類(lèi)的Size屬性來(lái)動(dòng)態(tài)的調(diào)整,存取Boolean值可以通過(guò)下標(biāo)來(lái)存取TBits類(lèi)的Bits屬性來(lái)實(shí)現(xiàn)。至于OpenBit函數(shù),它返回第一個(gè)不為T(mén)rue的Boolean值的下標(biāo)。從接口定義可以看出,TBits類(lèi)接口非常簡(jiǎn)單,提供的功能也很有限,我猜測(cè)這只是Borland的研發(fā)隊(duì)伍滿(mǎn)足內(nèi)部開(kāi)發(fā)有限需要的類(lèi),并不是作為一個(gè)通用類(lèi)來(lái)設(shè)計(jì)的,比如它沒(méi)有開(kāi)放內(nèi)部數(shù)據(jù)存取的接口,無(wú)法獲得內(nèi)部數(shù)據(jù)的表達(dá),進(jìn)而無(wú)法實(shí)現(xiàn)對(duì)狀態(tài)的保存和加載等更高的需求。
TCollection類(lèi)
前面我們提到了Delphi的IDE能夠自動(dòng)將字符串列表保存在DFM文件中,并能在運(yùn)行時(shí)將設(shè)計(jì)期編輯的字符串列表加載進(jìn)內(nèi)存(也就是我們通常所說(shuō)的類(lèi)的可持續(xù)性)。TStrings這種特性比較適合于保存一個(gè)對(duì)象同多個(gè)字符串?dāng)?shù)據(jù)之間關(guān)聯(lián),比較類(lèi)似于現(xiàn)實(shí)生活中一個(gè)人同多個(gè)Email賬戶(hù)地址之間的關(guān)系。但是,TStrings類(lèi)型的屬性有一個(gè)很大的局限那就是,它只能用于設(shè)計(jì)時(shí)保存簡(jiǎn)單的字符串列表,而不能保存復(fù)雜對(duì)象列表。而一個(gè)父對(duì)象同多個(gè)子對(duì)象之間的聚合關(guān)系可能更為常見(jiàn),比如一列火車(chē)可能有好多節(jié)車(chē)廂構(gòu)成,每節(jié)車(chē)廂都有車(chē)廂號(hào),車(chē)廂類(lèi)型(臥鋪,還是硬座),車(chē)廂座位數(shù),車(chē)廂服務(wù)員名稱(chēng)等屬性構(gòu)成。如果我們想在設(shè)計(jì)期實(shí)現(xiàn)對(duì)火車(chē)的車(chē)廂定制的功能,并能保存車(chē)廂的各個(gè)屬性到窗體文件中,則車(chē)廂集合屬性定義為T(mén)Strings類(lèi)型的屬性是行不通的。
對(duì)于這個(gè)問(wèn)題,Delphi提供了TCollection容器類(lèi)屬性這樣一個(gè)解決方案。TCollection以及它的容器元素TCollectionItem的接口定義如下:
TCollection = class(TPersistent)
…
protected
procedure Added(var Item: TCollectionItem); virtual;
deprecated;
procedure Deleting(Item: TCollectionItem); virtual;
deprecated;
property NextID: Integer read FNextID;
procedure Notify(Item: TCollectionItem; Action:
TCollectionNotification); virtual;
{ Design-time editor support }
function GetAttrCount: Integer; dynamic;
function GetAttr(Index: Integer): string; dynamic;
function GetItemAttr(Index, ItemIndex: Integer): string;
dynamic;
procedure Changed;
function GetItem(Index: Integer): TCollectionItem;
procedure SetItem(Index: Integer; Value: TCollectionItem);
procedure SetItemName(Item: TCollectionItem); virtual;
procedure Update(Item: TCollectionItem); virtual;
property PropName: string read GetPropName write FPropName;
property UpdateCount: Integer read FUpdateCount;
public
constructor Create(ItemClass: TCollectionItemClass);
destructor Destroy; override;
function Owner: TPersistent;
function Add: TCollectionItem;
procedure Assign(Source: TPersistent); override;
procedure BeginUpdate; virtual;
procedure Clear;
procedure Delete(Index: Integer);
procedure EndUpdate; virtual;
function FindItemID(ID: Integer): TCollectionItem;
function GetNamePath: string; override;
function Insert(Index: Integer): TCollectionItem;
property Count: Integer read GetCount;
property ItemClass: TCollectionItemClass read FItemClass;
property Items[Index: Integer]: TCollectionItem read GetItem write
SetItem;
end;
TCollectionItem = class(TPersistent)
…
protected
procedure Changed(AllItems: Boolean);
function GetOwner: TPersistent; override;
function GetDisplayName: string; virtual;
procedure SetCollection(Value: TCollection); virtual;
procedure SetIndex(Value: Integer); virtual;
procedure SetDisplayName(const Value: string); virtual;
public
constructor Create(Collection: TCollection); virtual;
destructor Destroy; override;
function GetNamePath: string; override;
property Collection: TCollection read FCollection write
SetCollection;
property ID: Integer read FID;
property Index: Integer read GetIndex write SetIndex;
property DisplayName: string read GetDisplayName write
SetDisplayName;
end;
TCollection類(lèi)是一個(gè)比較復(fù)雜特殊的容器類(lèi)。但是初看上去,它就是一個(gè)TCollectionItem對(duì)象的容器類(lèi),同列表類(lèi)TList類(lèi)似,TCollection類(lèi)也維護(hù)一個(gè)TCollectionItem對(duì)象索引數(shù)組,Count屬性表示容器中包含的TCollectionItem的數(shù)目,同時(shí)也提供了Add和Delete方法來(lái)添加和刪除TCollectionItem對(duì)象以及通過(guò)下標(biāo)存取TCollectionItem的屬性??瓷先ズ腿萜黝?lèi)區(qū)別不大,但是在VCL內(nèi)部用于保存和加載組件的TReader和TWriter類(lèi)提供了兩個(gè)特殊的方法WriteCollection和ReadCollection用于加載和保存TCollection類(lèi)型的集合屬性。IDE就是通過(guò)這兩個(gè)方法實(shí)現(xiàn)對(duì)TCollection類(lèi)型屬性的可持續(xù)性。
假設(shè)現(xiàn)在需要設(shè)計(jì)一個(gè)火車(chē)組件TTrain,TTrain組件有一個(gè)TCollection類(lèi)型的屬性Carriages表示多節(jié)車(chē)廂構(gòu)成的集合屬性,每個(gè)車(chē)廂則對(duì)應(yīng)于集合屬性的元素,從TCollectionItem類(lèi)繼承,有車(chē)廂號(hào),車(chē)廂類(lèi)型(臥鋪,還是硬座),車(chē)廂座位數(shù),車(chē)廂服務(wù)員名稱(chēng)等屬性,下面是我設(shè)計(jì)的組件的接口:
type
//車(chē)廂類(lèi)型,硬座、臥鋪
TCarriageType = (ctHard, ctSleeper);
//車(chē)廂類(lèi)
TCarriageCollectionItem = class(TCollectionItem)
…
published
//車(chē)廂號(hào)碼
property CarriageNum: Integer read FCarriageNum write
FCarriageNum;
//座位數(shù)
property SeatCount: Integer read FSeatCount write FSeatCount;
//車(chē)廂類(lèi)型
property CarriageType: TCarriageType read FCarriageType write
FCarriageType;
//服務(wù)員名稱(chēng)
property ServerName: string read FServerName write
FServerName;
end;
TTrain=class;
//車(chē)廂容器屬性類(lèi)
TCarriageCollection = class(TCollection)
private
FTrain:TTrain;
function GetItem(Index: Integer): TCarriageCollectionItem;
procedure SetItem(Index: Integer; const Value:
TCarriageCollectionItem);
protected
function GetOwner: TPersistent; override;
public
constructor Create(ATrain: TTrain);
function Add: TCarriageCollectionItem;
property Items[Index: Integer]: TCarriageCollectionItem read
GetItem
write SetItem; default;
end;
//火車(chē)類(lèi)
TTrain = class(TComponent)
private
FItems: TCarriageCollection;
procedure SetItems(Value: TCarriageCollection);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property Carriages: TCarriageCollection read FItems write
SetItems;
end;
其中車(chē)廂類(lèi)的定義非常簡(jiǎn)單,只是定義了四個(gè)屬性。而車(chē)廂集合類(lèi)重定義了靜態(tài)的Add方法以及Items屬性,其返回結(jié)果類(lèi)型改為了TCarriageCollectionItem,下面是車(chē)廂集合類(lèi)的實(shí)現(xiàn)代碼:
function TCarriageCollection.Add: TCarriageCollectionItem;
begin
Result:=TCarriageCollectionItem(inherited Add);
end;
constructor TCarriageCollection.Create(ATrain: TTrain);
begin
inherited Create(TCarriageCollectionItem);
FTrain:=ATrain;
end;
function TCarriageCollection.GetItem(
Index: Integer): TCarriageCollectionItem;
begin
Result := TCarriageCollectionItem(inherited GetItem(Index));
end;
function TCarriageCollection.GetOwner: TPersistent;
begin
Result:=FTrain;
end;
procedure TCarriageCollection.SetItem(Index: Integer;
const Value: TCarriageCollectionItem);
begin
inherited SetItem(Index, Value);
end;
其中Add,GetItem和SetItem都非常簡(jiǎn)單,就是調(diào)用基類(lèi)的方法,然后將基類(lèi)的方法的返回結(jié)果重新映射為T(mén)CollectionItem類(lèi)型。而構(gòu)造函數(shù)中將TTrain組件作為父組件傳入,并重載GetOwner方法,返回TTrain組件,這樣處理的原因是IDE會(huì)在保存集合屬性時(shí)調(diào)用集合類(lèi)的GetOwner確認(rèn)屬性的父控件是誰(shuí),這樣才能把集合屬性寫(xiě)到DFM文件中時(shí),才能存放到正確的位置下面,建立正確的聚合關(guān)系。
而火車(chē)組件的實(shí)現(xiàn)也非常簡(jiǎn)單,只要定義一個(gè)Published Carriages屬性就可以了,方法實(shí)現(xiàn)代碼如下:
constructor TTrain.Create(AOwner: TComponent);
begin
inherited;
FItems := TCarriageCollection.Create(Self);
end;
destructor TTrain.Destroy;
begin
FItems.Free;
inherited;
end;
procedure TTrain.SetItems(Value: TCarriageCollection);
begin
FItems.Assign(Value);
end;
下面將我們的組件注冊(cè)到系統(tǒng)面板上之后,就可以在窗體上放上一個(gè)TTrain組件,然后然后選中Object
Inspector,然后雙擊Carriages屬性,會(huì)顯示系統(tǒng)默認(rèn)的集合屬性編輯器,使用Add按鈕向列表中添加兩個(gè)車(chē)廂,修改一下屬性,如下圖所示意:
從上面的屬性編輯器我們,可以看到默認(rèn)情況下,屬性編輯器列表框是按項(xiàng)目索引加上一個(gè)橫杠來(lái)顯示車(chē)廂的名稱(chēng),看起來(lái)不是很自然。要想修改顯示字符串,需要重載TCarriageCollectionItem的GetDisplayName方法。修改后的GetDisplayName方法顯示車(chē)廂加車(chē)廂號(hào)碼:
function TCarriageCollectionItem.GetDisplayName: string;
begin
Result:='車(chē)廂'+IntToStr(CarriageNum);
end;
示意圖:
保存一下文件,使用View As
Text右鍵菜單命令察看一下DFM文件,我們會(huì)看到我們?cè)O(shè)計(jì)的車(chē)廂類(lèi)的屬性確實(shí)都被寫(xiě)到了DFM文件中,并且Carriages屬性的父親就是Train1:
object Train1: TTrain
Carriages = <
item
CarriageNum = 1
SeatCount = 100
CarriageType = ctHard
ServerName = '陳省'
end
item
CarriageNum = 2
SeatCount = 200
CarriageType = ctHard
ServerName = 'hubdog'
end>
Left = 16
Top = 8
End
TOwnedCollection
從Delphi4開(kāi)始,VCL增加了一個(gè)TOwnedCollection類(lèi),它是TCollection類(lèi)的子類(lèi),如果我們的TCarriageCollection類(lèi)是從TOwnedCollection類(lèi)繼承的,這時(shí)我們就不再需要向上面重載GetOwner方法并返回父控件給IDE,以便TCarriageCollection屬性能出現(xiàn)在Object
Inspector中了。
總結(jié)
本章中我介紹了幾乎所有VCL中重要的容器類(lèi),其中TList及其子類(lèi)相當(dāng)于通用的容器類(lèi),雖然不如C++和Java功能那么強(qiáng)大,但是用好了已經(jīng)足以滿(mǎn)足我們90%的開(kāi)發(fā)需要,而TStrings及其子類(lèi),還有TCollection則是實(shí)現(xiàn)所見(jiàn)即所得設(shè)計(jì)的關(guān)鍵類(lèi),對(duì)于開(kāi)發(fā)靈活強(qiáng)大的自定義組件來(lái)說(shuō)是必不可少的