Delphi中的容器类(3)
TBucketList和TObjectBucketList類
從Delphi6開始,VCL的Contnrs單元中又增加了兩個新的容器類TBucketList和TObjectBucketList。TBucketList實際上也是一個簡單基于哈希表的指針-指針對列表。接口定義如下:
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;
類的Add方法現在接受兩個參數AItem和AData,我們可以把它看成是指針版的Map實現(從容器類來看, Delphi從語言的靈活性來說不如C++,為了實現不同類型的哈希Map容器,Delphi需要派生很多的類,而C++的Map是基于模版技術來實現的,容器元素的類型只要簡單的聲明一下就能指定了,使用起來非常方便。而從簡單性來說,則不如Java的容器類,因為Delphi中的String是原生類型,而不是類,并且Delphi還提供對指針的支持,因此要為指針和字符串提供不同的Map派生類),類中的Exists和Find等方法都是通過哈希表來實現快速數據定位的。同時,同一般的列表容器類不同,TBucketList不提供通過整數下標獲取列表中的元素的功能,不過我們可以使用ForEach方法來遍歷容器內的元素。
TObjectBucketList是從TBucketList派生的基類,沒有增加任何新的功能,唯一的不同之處就是容器內的元素不是指針而是對象了,實現了更強的類型檢查而已。
其它容器類
TThreadList類
TThreadList類實際上就是一個線程安全的TList類,每次添加或者刪除容易中指針時,TThreadList會調用EnterCriticalSection函數進入線程阻塞狀態,這時其它后續發生的對列表的操作都會阻塞在那里,直到TThreadList調用UnLockList釋放對列表的控制后才會被依次執行。在多線程開發中,我們需要使用TThreadList來保存共享的資源以避免多線程造成的混亂和沖突。還要注意的是TThreadList有一個Duplicates布爾屬性,默認為True,表示列表中不能有重復的指針。設定為False將允許容器內有重復的元素。
TInterfaceList類
在Classes單元中,VCL還定義了一個可以保存接口的列表類。我們可以向列表中添加接口類型,這個類的操作方法同其它的列表類沒有什么區別,只不過在內部使用TThreadList作為容器實現了線程安全。
擬容器類TBits類
在Classes.pas還有一個特殊的TBits類,接口定義如下:
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;
它可以按位儲存布爾值,因此可以看成是一個原生的Boolean值的容器類,但是它缺少列表類的很多方法和特性,不能算是一個完整的容器,因此我們稱它為擬容器類。
在我們開發過程中,經常需要表示一些類似于開關的二元狀態,這時我們用TBits來表示一組二元狀態非常方便,同時TBits類的成員函數主要是用匯編語言寫的,位操作的速度非常快。二元狀態組的大小通過設定TBits類的Size屬性來動態的調整,存取Boolean值可以通過下標來存取TBits類的Bits屬性來實現。至于OpenBit函數,它返回第一個不為True的Boolean值的下標。從接口定義可以看出,TBits類接口非常簡單,提供的功能也很有限,我猜測這只是Borland的研發隊伍滿足內部開發有限需要的類,并不是作為一個通用類來設計的,比如它沒有開放內部數據存取的接口,無法獲得內部數據的表達,進而無法實現對狀態的保存和加載等更高的需求。
TCollection類
前面我們提到了Delphi的IDE能夠自動將字符串列表保存在DFM文件中,并能在運行時將設計期編輯的字符串列表加載進內存(也就是我們通常所說的類的可持續性)。TStrings這種特性比較適合于保存一個對象同多個字符串數據之間關聯,比較類似于現實生活中一個人同多個Email賬戶地址之間的關系。但是,TStrings類型的屬性有一個很大的局限那就是,它只能用于設計時保存簡單的字符串列表,而不能保存復雜對象列表。而一個父對象同多個子對象之間的聚合關系可能更為常見,比如一列火車可能有好多節車廂構成,每節車廂都有車廂號,車廂類型(臥鋪,還是硬座),車廂座位數,車廂服務員名稱等屬性構成。如果我們想在設計期實現對火車的車廂定制的功能,并能保存車廂的各個屬性到窗體文件中,則車廂集合屬性定義為TStrings類型的屬性是行不通的。
對于這個問題,Delphi提供了TCollection容器類屬性這樣一個解決方案。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類是一個比較復雜特殊的容器類。但是初看上去,它就是一個TCollectionItem對象的容器類,同列表類TList類似,TCollection類也維護一個TCollectionItem對象索引數組,Count屬性表示容器中包含的TCollectionItem的數目,同時也提供了Add和Delete方法來添加和刪除TCollectionItem對象以及通過下標存取TCollectionItem的屬性。看上去和容器類區別不大,但是在VCL內部用于保存和加載組件的TReader和TWriter類提供了兩個特殊的方法WriteCollection和ReadCollection用于加載和保存TCollection類型的集合屬性。IDE就是通過這兩個方法實現對TCollection類型屬性的可持續性。
假設現在需要設計一個火車組件TTrain,TTrain組件有一個TCollection類型的屬性Carriages表示多節車廂構成的集合屬性,每個車廂則對應于集合屬性的元素,從TCollectionItem類繼承,有車廂號,車廂類型(臥鋪,還是硬座),車廂座位數,車廂服務員名稱等屬性,下面是我設計的組件的接口:
type
//車廂類型,硬座、臥鋪
TCarriageType = (ctHard, ctSleeper);
//車廂類
TCarriageCollectionItem = class(TCollectionItem)
…
published
//車廂號碼
property CarriageNum: Integer read FCarriageNum write FCarriageNum;
//座位數
property SeatCount: Integer read FSeatCount write FSeatCount;
//車廂類型
property CarriageType: TCarriageType read FCarriageType write FCarriageType;
//服務員名稱
property ServerName: string read FServerName write FServerName;
end;
TTrain=class;
//車廂容器屬性類
TCarriageCollection = class(TCollection)
*******
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;
//火車類
TTrain = class(TComponent)
*******
FItems: TCarriageCollection;
procedure SetItems(Value: TCarriageCollection);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property Carriages: TCarriageCollection read FItems write SetItems;
end;
其中車廂類的定義非常簡單,只是定義了四個屬性。而車廂集合類重定義了靜態的Add方法以及Items屬性,其返回結果類型改為了TCarriageCollectionItem,下面是車廂集合類的實現代碼:
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都非常簡單,就是調用基類的方法,然后將基類的方法的返回結果重新映射為TCollectionItem類型。而構造函數中將TTrain組件作為父組件傳入,并重載GetOwner方法,返回TTrain組件,這樣處理的原因是IDE會在保存集合屬性時調用集合類的GetOwner確認屬性的父控件是誰,這樣才能把集合屬性寫到DFM文件中時,才能存放到正確的位置下面,建立正確的聚合關系。
而火車組件的實現也非常簡單,只要定義一個Published Carriages屬性就可以了,方法實現代碼如下:
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;
下面將我們的組件注冊到系統面板上之后,就可以在窗體上放上一個TTrain組件,然后然后選中Object Inspector,然后雙擊Carriages屬性,會顯示系統默認的集合屬性編輯器,使用Add按鈕向列表中添加兩個車廂,修改一下屬性,如下圖所示意:
?
從上面的屬性編輯器我們,可以看到默認情況下,屬性編輯器列表框是按項目索引加上一個橫杠來顯示車廂的名稱,看起來不是很自然。要想修改顯示字符串,需要重載TCarriageCollectionItem的GetDisplayName方法。修改后的GetDisplayName方法顯示車廂加車廂號碼:
function TCarriageCollectionItem.GetDisplayName: string;
begin
Result:='車廂'+IntToStr(CarriageNum);
end;
示意圖:
?
保存一下文件,使用View As Text右鍵菜單命令察看一下DFM文件,我們會看到我們設計的車廂類的屬性確實都被寫到了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開始,VCL增加了一個TOwnedCollection類,它是TCollection類的子類,如果我們的TCarriageCollection類是從TOwnedCollection類繼承的,這時我們就不再需要向上面重載GetOwner方法并返回父控件給IDE,以便TCarriageCollection屬性能出現在Object Inspector中了。
總結
本章中我介紹了幾乎所有VCL中重要的容器類,其中TList及其子類相當于通用的容器類,雖然不如C++和Java功能那么強大,但是用好了已經足以滿足我們90%的開發需要,而TStrings及其子類,還有TCollection則是實現所見即所得設計的關鍵類,對于開發靈活強大的自定義組件來說是必不可少的。
轉載于:https://www.cnblogs.com/hackpig/archive/2010/02/15/1668527.html
總結
以上是生活随笔為你收集整理的Delphi中的容器类(3)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C# 读写ACCESS的OLE对象,演示
- 下一篇: 微软输入法2010下载使用-IME201