zoukankan      html  css  js  c++  java
  • 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则是实现所见即所得设计的关键类,对于开发灵活强大的自定义组件来说是必不可少的。

  • 相关阅读:
    [APM] OneAPM 云监控部署与试用体验
    Elastic Stack 安装
    xBIM 综合使用案例与 ASP.NET MVC 集成(一)
    JQuery DataTables Selected Row
    力导向图Demo
    WPF ViewModelLocator
    Syncfusion SfDataGrid 导出Excel
    HTML Table to Json
    .net core 2.0 虚拟目录下载 Android Apk 等文件
    在BootStrap的modal中使用Select2
  • 原文地址:https://www.cnblogs.com/hackpig/p/1668527.html
Copyright © 2011-2022 走看看