今天遇到个难题,安装AlphaControls后,CxGrid并没有跟着必变,网上找了很久也没有结果,最好发现AlphaControls本身是支持DEV组件的,只是默认是不改变,不过在inc文件中已经预定义了。有一个关键性文件sDefs.inc:

   

{ ---- Definitions by Serge V. Goncharov ---- }

// -- 3rdparty support start -- //
{.$DEFINE DEVEX} // DevExpress, LookAndFeel.NativeStyle property must be False
{.$DEFINE USEPNG} // Support of TPngImageList from PngComponents
{.$DEFINE TNTUNICODE} // Enable Unicode support by TntControls
// -- 3rdparty support finish -- //

// -- Experimental features -- //
{.$DEFINE USEAERO} // Use system shadows in Aero
{.$DEFINE NOSLOWDETAILS} // Simplified output without slow effects

{ ------------------------------------ }

{.$DEFINE DISABLEPREVIEWMODE} // If key is enabled then preview code is excluded
{.$DEFINE NOWNDANIMATION} // Disable animation effects for dialogs and forms
{.$DEFINE CHANGEFORMSINDESIGN} // Changing the forms colors in design-time
{.$DEFINE SKININDESIGN} // Skin control in frames in design-time
{.NOACPNG} // Do not use AlphaControls Png loader
{.$DEFINE ALITE} // Free Lite Edition

{ ---- End defs by Serge V. Goncharov ---- }

 

=------------这是文件的部分内容,以下是改过的内容:

 

{ ---- Definitions by Serge V. Goncharov ---- }

// -- 3rdparty support start -- //
{$DEFINE DEVEX} // 《==============将前面的一个小点"."删除就OK
{.$DEFINE TNTUNICODE} // Enable Unicode support by TntControls
// -- 3rdparty support finish -- //

// -- Experimental features -- //
{.$DEFINE USEAERO} // Use system shadows in Aero
{.$DEFINE NOSLOWDETAILS} // Simplified output without slow effects

{ ------------------------------------ }

{.$DEFINE DISABLEPREVIEWMODE} // If key is enabled then preview code is excluded
{.$DEFINE NOWNDANIMATION} // Disable animation effects for dialogs and forms
{.$DEFINE CHANGEFORMSINDESIGN} // Changing the forms colors in design-time
{.$DEFINE SKININDESIGN} // Skin control in frames in design-time
{.NOACPNG} // Do not use AlphaControls Png loader
{.$DEFINE ALITE} // Free Lite Edition

{ ---- End defs by Serge V. Goncharov ---- }

 

     AlphaControls在acLFPainter单元中已经对DEV皮肤进行了覆盖。

    

unit acLFPainter;
{$I sDefs.inc}  //这里将预定义字符编译进单元

// WARNING! This unit is compatible with Devexpress version 2011
// for older versions the acLFPainter6 unit should be used

{$DEFINE VER653}
{$DEFINE VER650}  
{$DEFINE VER645}  
{$DEFINE VER640}

{$IFDEF VER653}
  {$DEFINE VER650}
{$ENDIF}

{$IFDEF VER650}
  {$DEFINE VER645}
{$ENDIF}

{$IFDEF VER645}
  {$DEFINE VER640}
{$ENDIF}

{$IFNDEF VER645}
  {$UNDEF VER650}
{$ENDIF}

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs{$IFNDEF DELPHI5}, Types{$ENDIF},
  sSkinManager, sStyleSimply, sMaskData, cxLookAndFeelPainters, cxGraphics, cxClasses, ImgList, dxCore;

type

  TsDevExProvider = class(TComponent)
  end;

  TcxACLookAndFeelPainter = class(TcxStandardLookAndFeelPainter)
  public
    function LookAndFeelName: string; override;
    // colors
    function DefaultContentColor: TColor; override;
    function DefaultContentEvenColor: TColor; override;
    function DefaultContentOddColor: TColor; override;
    function DefaultContentTextColor: TColor; override;
    function DefaultEditorBackgroundColor(AIsDisabled: Boolean): TColor; override;
    function DefaultEditorBackgroundColorEx(AKind: TcxEditStateColorKind): TColor; override;
    function DefaultEditorTextColor(AIsDisabled: Boolean): TColor; override;
    function DefaultEditorTextColorEx(AKind: TcxEditStateColorKind): TColor; override;
    function DefaultFilterBoxColor: TColor; override;
    function DefaultFilterBoxTextColor: TColor; override;
    function DefaultFixedSeparatorColor: TColor; override;
    function DefaultFooterColor: TColor; override;
    function DefaultFooterTextColor: TColor; override;
    function DefaultGridDetailsSiteColor: TColor; override;
    function DefaultGridLineColor: TColor; override;
    function DefaultGroupByBoxColor: TColor; override;
    function DefaultGroupByBoxTextColor: TColor; override;
    function DefaultGroupColor: TColor; override;
    function DefaultGroupTextColor: TColor; override;
    function DefaultHeaderBackgroundColor: TColor; override;
    function DefaultHeaderBackgroundTextColor: TColor; override;
    function DefaultHeaderColor: TColor; override;
    function DefaultHeaderTextColor: TColor; override;
    function DefaultHyperlinkTextColor: TColor; override;
    function DefaultInactiveColor: TColor; override;
    function DefaultInactiveTextColor: TColor; override;
    function DefaultPreviewTextColor: TColor; override;
    function DefaultRecordSeparatorColor: TColor; override;
    function DefaultSizeGripAreaColor: TColor; override;

    function DefaultVGridCategoryColor: TColor; override;
    function DefaultVGridCategoryTextColor: TColor; override;
    function DefaultVGridLineColor: TColor; override;
    function DefaultVGridBandLineColor: TColor; override;

    function DefaultDateNavigatorHeaderColor: TColor; override;
    function DefaultDateNavigatorSelectionColor: TColor; override;
    function DefaultDateNavigatorSelectionTextColor: TColor; override;

    function DefaultSchedulerBackgroundColor: TColor; override;
    function DefaultSchedulerTextColor: TColor; override;
    function DefaultSchedulerBorderColor: TColor; override;
    function DefaultSchedulerControlColor: TColor; override;
    function DefaultSchedulerNavigatorColor: TColor; override;
    function DefaultSchedulerViewContentColor: TColor; override;
    function DefaultSchedulerViewSelectedTextColor: TColor; override;
    function DefaultSchedulerViewTextColor: TColor; override;
    function DefaultSelectionColor: TColor; override;
    function DefaultSelectionTextColor: TColor; override;
    function DefaultSeparatorColor: TColor; override;
    function DefaultTabColor: TColor; override;
    function DefaultTabTextColor: TColor; override;
    function DefaultTabsBackgroundColor: TColor; override;

    ........................................

 

 

         效果非常漂亮!

 

posted @ 2012-01-21 23:57 墨者工作室 阅读(75) 评论(0) 编辑

现今所有的软件中都应用了设计模式,模式除了可以解决很多实际问题外,还给开发者带来非常顺畅的心情,本人是Delphi Fans现在关于这门语言的资料越来越少了。在这里给大家推荐两本书,一本是《敏捷软件开发》和《Delphi模式编程.刘艺》。我的笔记来自这两本书.

1.单一职责原则(SRP).一个类而言,应该公仅有一个引起它变化的原因,所为“职责”就是变化的原因,如果能够想到多于一个的动机去改变一个类,那个这  个类就具有多一个职责。

2.   开放-封闭原则(OCP)  软件实体(类.模块.函数等)应该是可以扩展的,但是不可修改的。对于扩展是开发的,对于更改是封闭的。要点:关键是抽象,模块可以操作一个抽像体,其依赖于一个固定的抽象体,所以它对于更改可以是关闭的,同时通过从这个抽像体派生,也可以扩展此模块的行为。例:

        type

          IServer = interface //抽像接口
             ['{3E91264F-BBC0-44DF-8272-BD8EA9B5846C}']
           procedure View(o: TObject);
         end;

        TServerDB=Class(IServer) //具体实现类

          procedure View(o:TObject);

        end;

       TClientUser=class //客户类

         private

           FSDB:IServer;

         public

           procedure SetDB;

      end;

       在TClientUser中,只是依赖IServer,IServer是一个固定不变的抽像体,其它模块或函数升级或优化都对引用些接口的类或过程等没有影响,反之也可以

      从此接口继承,重新定义亲的类或进行扩展!这种设计方法是遵守了OCP原则,OCP的核心是“抽象”,这个原则可以带来面向对对对象技术的灵活性,可重用性及维护性,同样这种原则也不能泛用,正确做法是对程序中呈现出频繁变化的部分做出抽像。拒绝不成熟的抽像和抽像本身一样重要。

                     .......下待续

   

 


 

posted @ 2012-01-15 23:02 墨者工作室 阅读(915) 评论(0) 编辑
Delphi 的RTTI机制浅探

作者:Savetime:savetime2k@yahoo.com 转贴自:Delphibbs.com
目录
===========================================================
⊙ RTTI 简介
⊙ 类(class) 和 VMT 的关系
⊙ 类(class)、类的类(class of class)、类变量(class variable) 的关系
⊙ TObject.ClassType 和 TObject.ClassInfo
⊙ is 和 as 运算符的原理
⊙ TTypeInfo – RTTI 信息的结构
⊙ 获取类(class)的属性(property)信息
⊙ 获取方法(method)的类型信息
⊙ 获取有序类型(ordinal)、集合(set)类型的 RTTI 信息
⊙ 获取其它数据类型的 RTTI 信息
===========================================================
本文排版格式为:
正文由窗口自动换行;所有代码以 80 字符为边界;中英文字符以空格符分隔。

(作者保留对本文的所有权利,未经作者同意请勿在在任何公共媒体转载。)


正文
===========================================================
⊙ RTTI 简介
===========================================================

RTTI(Run-Time Type Information) 翻译过来的名称是“运行期类型信息”,也就是说可以在运行期获得数据类型或类(class)的信息。这个 RTTI 到底有什么用处,我现在也说不清楚。我是在阅读 Delphi 持续机制的代码中发现了很多 RTTI 的运用,只好先把 RTTI 学习一遍。下面是我的学习笔记。如果你发现了错误请告诉我。谢谢!

Delphi 的 RTTI 主要分为类(class)的 RTTI 和一般数据类型的 RTTI,下面从类(class)开始。

===========================================================
⊙ 类(class) 和 VMT 的关系
===========================================================

一个类(class),从编译器的角度来看就是一个指向 VMT 的指针(在后文用 VMTptr 表示)。在类的 VMTptr 的负地址方向存储了一些类信息的指针,这些指针的值和指针所指的内容在编译后就确定了。比如 VMTptr - 44 的内容是指向类名称(ClassName)的指针。不过一般不使用数值来访问这些类信息,而是通过 System.pas 中定义的以 vmt 开头的常量,如 vtmClassName、vmtParent 等来访问。

类的方法有两种:对象级别的方法和类级别的方法。两者的 Self 指针意义是不同的。在对象级别的方法中 Self 指向对象地址空间,因此可以用它来访问对象的成员函数;在类级别的方法中 Self 指向类的 VMT,因此只能用它来访问 VMT 信息,而不能访问对象的成员字段。

===========================================================
⊙ 类(class)、类的类(class of class)、类变量(class variable) 的关系
===========================================================

上面说到类(class) 就是 VMTptr。在 Delphi 中还可以用 class of 关键字定义类的类,并且可以使用类的类定义类变量。从语法上理解这三者的关键并不难,把类当成普通的数据类型来考虑就可以了。在编译器级别上表现如何呢?

为了简化讨论,我们使用 TObject、TClass 和 TMyClass 来代表上面说的三种类型:

type
TClass = class of TObject;
var
TMyClass: TClass;
MyObject: TObject;
begin
TMyClass := TObject;
MyObject := TObject.Create;
MyObject := TClass.Create;
MyObject := TMyClass.Create;
end;

在上面的例子中,三个 TObject 对象都被成功地创建了。编译器的实现是:TObject 是一个 VMTPtr 常量。TClass 也是一个 VMTptr 常量,它的值就是 TObject。TMyClass 是一个 VMTptr 变量,它被赋值为 TObject。TObject.Create 与 TClass.Create 的汇编代码完全相同。但 TClass 不仅缺省代表一个类,而且还(主要)代表了类的类型,可以用它来定义类变量,实现一些类级别的操作。

===========================================================
⊙ TObject.ClassType 和 TObject.ClassInfo
===========================================================

function TObject.ClassType: TClass;
begin
Pointer(Result) := PPointer(Self)^;
end;

TObject.ClassType 是对象级别的方法,Self 的值是指向对象内存空间的指针,对象内存空间的前 4 个字节是类的 VMTptr。因此这个函数的返回值就是类的 VMTptr。

class function TObject.ClassInfo: Pointer;
begin
Result := PPointer(Integer(Self) + vmtTypeInfo)^;
end;

TObject.ClassInfo 使用 class 关键字定义,因此是一个类级别的方法。该方法中的 Self 指针就是 VMTptr。所以这个函数的返回值是 VMTptr 负方向的 vmtTypeInfo 的内容。

TObject.ClassInfo 返回的 Pointer 指针,实际上是指向类的 RTTI 结构的指针。但是不能访问 TObject.ClassInfo 指向的内容(TObject.ClassInfo 返回值是 0),因为 Delphi 只在 TPersistent 类及 TPersistent 的后继类中产生 RTTI 信息。(从编译器的角度来看,这是在 TPersistent 类的声明之前使用 {$M+} 指示字的结果。)

TObject 还定义了一些获取类 RTTI 信息的函数,列举在下,就不一一分析了:

TObject.ClassName: ShortString; 类的名称
TObject.ClassParent: TClass; 对象的父类
TObject.InheritsFrom: Boolean; 是否继承自某类
TObject.InstanceSize: Longint; 对象实例的大小

===========================================================
⊙ is 和 as 运算符的原理
===========================================================

我们知道可以在运行期使用 is 关键字判断一个对象是否属于某个类,可以使用 as 关键字把某个对象安全地转换为某个类。在编译器的层次上,is 和 as 的操作是由 System.pas 中两个函数完成的。

{ System.pas }
function _IsClass(Child: TObject; Parent: TClass): Boolean;
begin
Result := (Child <> nil) and Child.InheritsFrom(Parent);
end;

_IsClass 很简单,它使用 TObject 的 InheritsForm 函数判断该对象是否是从某个类或它的父类中继承下来的。每个类的 VMT 中都有一项 vmtParent 指针,指向该类的父类的 VMT。TObject.InheritsFrom 实际上是通过[递归]判断父类 VMT 指针是否等于自己的 VMT 指针来判断是否是从该类继承的。

{ System.pas }
class function TObject.InheritsFrom(AClass: TClass): Boolean;
var
ClassPtr: TClass;
begin
ClassPtr := Self;
while (ClassPtr <> nil) and (ClassPtr <> AClass) do
ClassPtr := PPointer(Integer(ClassPtr) + vmtParent)^;
Result := ClassPtr = AClass;
end;

as 操作符实际上是由 System.pas 中的 _AsClass 函数完成的。它简单地调用 is 操作符判断对象是否属于某个类,如果不是就触发异常。虽然 _AsClass 返回值为 TObject 类型,但编译器会自动把返回的对象改变为 Parent 类,否则返回的对象没有办法使用 TObject 之外的方法和数据。

{ System.pas }
function _AsClass(Child: TObject; Parent: TClass): TObject;
begin
Result := Child;
if not (Child is Parent) then
Error(reInvalidCast); // loses return address
end;

===========================================================
⊙ TTypeInfo – RTTI 信息的结构
===========================================================

RTTI 信息的结构定义在 TypInfo.pas 中:

TTypeInfo = record // TTypeInfo 是 RTTI 信息的结构
Kind: TTypeKind; // RTTI 信息的数据类型
Name: ShortString; // 数据类型的名称
{TypeData: TTypeData} // RTTI 的内容
end;

TTypeInfo 就是 RTTI 信息的结构。TObject.ClassInfo 返回指向存放 class TTypeInfo 信息的指针。Kind 是枚举类型,它表示 RTTI 结构中所包含数据类型。Name 是数据类型的名称。注意,最后一个字段 TypeData 被注释掉了,这说明该处的结构内容根据不同的数据类型有所不同。

TTypeKind 枚举定义了可以使用 RTTI 信息的数据类型,它几乎包含了所有的 Delphi 数据类型,其中包括 tkClass。

TTypeKind = (tkUnknown, tkInteger, tkChar, tkEnumeration, tkFloat,
tkString, tkSet, tkClass, tkMethod, tkWChar, tkLString, tkWString,
tkVariant, tkArray, tkRecord, tkInterface, tkInt64, tkDynArray);

TTypeData 是个巨大的记录类型,在此不再列出,后文会根据需要列出该记录的内容。

===========================================================
⊙ 获取类(class)的属性(property)信息
===========================================================

这一段是 RTTI 中最复杂的部分,努力把本段吃透,后面的内容都是非常简单的。

下面是一个获取类的属性的例子:

procedure GetClassProperties(AClass: TClass; AStrings: TStrings);
var
PropCount, I: SmallInt;
PropList: PPropList;
PropStr: string;
begin
PropCount := GetTypeData(AClass.ClassInfo).PropCount;
GetPropList(AClass.ClassInfo, PropList);
for I := 0 to PropCount - 1 do
begin
case PropList^.PropType^.Kind of
tkClass : PropStr := '[Class] ';
tkMethod : PropStr := '[Method]';
tkSet : PropStr := '[Set] ';
tkEnumeration: PropStr := '[Enum] ';
else
PropStr := '[Field] ';
end;
PropStr := PropStr + PropList^.Name;
PropStr := PropStr + ': ' + PropList^.PropType^.Name;
AStrings.Add(PropStr);
end;
FreeMem(PropList);
end;

你可以在表单上放置一个 TListBox ,然后执行以下语句观察执行结果:

GetClassProperties(TForm1, ListBox1.Items);

该函数先使用 GetTypeData 函数获得类的属性数量。GetTypeData 是 TypInfo.pas 中的一个函数,它的功能是返回 TTypeInfo 的 TypeData 数据的指针:

{ TypInfo.pas }
function GetTypeData(TypeInfo: PTypeInfo): PTypeData; assembler;

class 的 TTypeData 结构如下:

TTypeData = packed record
case TTypeKind of
tkClass: (
ClassType: TClass; // 类 (VMTptr)
ParentInfo: PPTypeInfo; // 父类的 RTTI 指针
PropCount: SmallInt; // 属性数量
UnitName: ShortStringBase; // 单元的名称
{PropData: TPropData}); // 属性的详细信息
end;

其中的 PropData 又是一个大小可变的字段。TPropData 的定义如下:

TPropData = packed record
PropCount: Word; // 属性数量
PropList: record end; // 占位符,真正的意义在下一行
{PropList: array[1..PropCount] of TPropInfo}
end;

每个属性信息在内存中的结构就是 TPropInfo,它的定义如下:

PPropInfo = ^TPropInfo;
TPropInfo = packed record
PropType: PPTypeInfo; // 属性类型信息指针的指针
GetProc: Pointer; // 属性的 Get 方法指针
SetProc: Pointer; // 属性的 Set 方法指针
StoredProc: Pointer; // 属性的 StoredProc 指针
Index: Integer; // 属性的 Index 值
Default: Longint; // 属性的 Default 值
NameIndex: SmallInt; // 属性的名称索引(以 0 开始计数)
Name: ShortString; // 属性的名称
end;

为了方便访问属性信息,TypInfo.pas 中还定义了指向 TPropInfo 数组的指针:

PPropList = ^TPropList;
TPropList = array[0..16379] of PPropInfo;

我们可以使用 GetPropList 获得所有属性信息的指针数组,数组用完以后要记得用 FreeMem 把数组的内存清除。

{ TypInfo.pas }
function GetPropList(TypeInfo: PTypeInfo; out PropList: PPropList): Integer;

GetPropList 传入类的 TTypeInfo 指针和 TPropList 的指针,它为 PropList 分配一块内存后把该内存填充为指向 TPropInfo 的指针数组,最后返回属性的数量。

上面的例子演示了如何获得类的所有属性信息,也可以根据属性的名称单独获得属性信息:

{ TypInfo.pas }
function GetPropInfo(TypeInfo: PTypeInfo; const PropName: string): PPropInfo;

GetPropInfo 根据类的 RTTI 指针和属性的名称字符串,返回属性的信息 TPropInfo 的指针。如果没有找到该属性,则返回 nil。GetPropInfo 很容易使用,举个例子:

ShowMessage(GetPropInfo(TForm, 'Name')^.PropType^.Name);

这句调用显示了 TForm 类的 Name 属性的类型名称:TComponentName。

===========================================================
⊙ 获取方法(method)的类型信息
===========================================================

所谓方法就是以 of object 关键字声明的函数指针,下面的函数可以显示一个方法的类型信息:

procedure GetMethodTypeInfo(ATypeInfo: PTypeInfo; AStrings: TStrings);
type
PParamData = ^TParamData;
TParamData = record // 函数参数的数据结构
Flags: TParamFlags; // 参数传递规则
ParamName: ShortString; // 参数的名称
TypeName: ShortString; // 参数的类型名称
end;
function GetParamFlagsName(AParamFlags: TParamFlags): string;
var
I: Integer;
begin
Result := '';
for I := Integer(pfVar) to Integer(pfOut) do begin
if I = Integer(pfAddress) then Continue;
if TParamFlag(I) in AParamFlags then
Result := Result + ' ' + GetEnumName(TypeInfo(TParamFlag), I);
end;
end;
var
MethodTypeData: PTypeData;
ParamData: PParamData;
TypeStr: PShortString;
I: Integer;
begin
MethodTypeData := GetTypeData(ATypeInfo);
AStrings.Add('---------------------------------');
AStrings.Add('Method Name: ' + ATypeInfo^.Name);
AStrings.Add('Method Kind: ' + GetEnumName(TypeInfo(TMethodKind),
Integer(MethodTypeData^.MethodKind)));
AStrings.Add('Params Count: '+ IntToStr(MethodTypeData^.ParamCount));
AStrings.Add('Params List:');
ParamData := PParamData(@MethodTypeData^.ParamList);
for I := 1 to MethodTypeData^.ParamCount do
begin
TypeStr := Pointer(Integer(@ParamData^.ParamName) +
Length(ParamData^.ParamName) + 1);
AStrings.Add(Format(' [%s] %s: %s',[GetParamFlagsName(ParamData^.Flags),
ParamData^.ParamName, TypeStr^]));
ParamData := PParamData(Integer(ParamData) + SizeOf(TParamFlags) +
Length(ParamData^.ParamName) + Length(TypeStr^) + 2);
end;
if MethodTypeData^.MethodKind = mkFunction then
AStrings.Add('Result Value: ' + PShortString(ParamData)^);
end;

作为实验,在表单上放置一个 TListBox,然后执行以下代码,观察执行结果:

type
TMyMethod = function(A: array of Char; var B: TObject): Integer of object;
procedure TForm1.FormCreate(Sender: TObject);
begin
GetMethodTypeInfo(TypeInfo(TMyMethod), ListBox1.Items);
GetMethodTypeInfo(TypeInfo(TMouseEvent), ListBox1.Items);
GetMethodTypeInfo(TypeInfo(TKeyPressEvent), ListBox1.Items);
GetMethodTypeInfo(TypeInfo(TMouseWheelEvent), ListBox1.Items);
end;

由于获取方法的类型信息比较复杂,我尽量压缩代码也还是有这么长,让我们看看它的实现原理。GetMethodTypeInfo 的第一个参数是 PTypeInfo 类型,表示方法的类型信息地址。第二个参数是一个字符串列表,可以使用任何实现 TStrings 操作的对象。我们可以使用 System.pas 中的 TypeInfo 函数获得任何类型的 RTTI 信息指针。TypeInfo 函数像 SizeOf 一样,是内置于编译器中的。

GetMethodTypeInfo 还用到了 TypInfo.pas 中的 GetEnumName 函数。这个函数通过枚举类型的整数值得到枚举类型的名称。

function GetEnumName(TypeInfo: PTypeInfo; Value: Integer): string;

与获取类(class)的属性信息类似,方法的类型信息也在 TTypeData 结构中

TTypeData = packed record
case TTypeKind of
tkMethod: (
MethodKind: TMethodKind; // 方法指针的类型
ParamCount: Byte; // 参数数量
ParamList: array[0..1023] of Char // 参数详细信息,见下行注释
{ParamList: array[1..ParamCount] of
record
Flags: TParamFlags; // 参数传递规则
ParamName: ShortString; // 参数的名称
TypeName: ShortString; // 参数的类型
end;
ResultType: ShortString}); // 返回值的名称
end;

TMethodKind 是方法的类型,定义如下:

TMethodKind = (mkProcedure, mkFunction, mkConstructor, mkDestructor,
mkClassProcedure, mkClassFunction,
{ Obsolete }
mkSafeProcedure, mkSafeFunction);

TParamsFlags 是参数传递的规则,定义如下:

TParamFlag = (pfVar, pfConst, pfArray, pfAddress, pfReference, pfOut);
TParamFlags = set of TParamFlag;

由于 ParamName 和 TypeName 是变长字符串,不能直接取用该字段的值,而应该使用指针步进的方法,取出参数信息,所以上面的代码显得比较长。

===========================================================
⊙ 获取有序类型(ordinal)、集合(set)类型的 RTTI 信息
===========================================================

讨论完了属性和方法的 RTTI 信息之后再来看其它数据类型的 RTTI 就简单多了。所有获取 RTTI 的原理都是通过 GetTypeData 函数得到 TTypeData 的指针,再通过 TTypeInfo.TypeKind 来解析 TTypeData。任何数据类型的 TTypeInfo 指针可以通过 TypeInfo 函数获得。

有序类型的 TTypeData 定义如下:

TTypeData = packed record
tkInteger, tkChar, tkEnumeration, tkSet, tkWChar: (
OrdType: TOrdType; // 有序数值类型
case TTypeKind of
case TTypeKind of
tkInteger, tkChar, tkEnumeration, tkWChar: (
MinValue: Longint; // 类型的最小值
MaxValue: Longint; // 类型的最大值
case TTypeKind of
tkInteger, tkChar, tkWChar: ();
tkEnumeration: (
BaseType: PPTypeInfo; // 指针的指针,它指向枚举的 PTypeInfo
NameList: ShortStringBase; // 枚举的名称字符串(不能直接取用)
EnumUnitName: ShortStringBase)); // 所在的单元名称(不能直接取用)
tkSet: (
CompType: PPTypeInfo)); // 指向集合基类 RTTI 指针的指针
end;

下面是一个获取有序类型和集合类型的 RTTI 信息的函数:

procedure GetOrdTypeInfo(ATypeInfo: PTypeInfo; AStrings: TStrings);
var
OrdTypeData: PTypeData;
I: Integer;
begin
OrdTypeData := GetTypeData(ATypeInfo);
AStrings.Add('------------------------------------');
AStrings.Add('Type Name: ' + ATypeInfo^.Name);
AStrings.Add('Type Kind: ' + GetEnumName(TypeInfo(TTypeKind),
Integer(ATypeInfo^.Kind)));
AStrings.Add('Data Type: ' + GetEnumName(TypeInfo(TOrdType),
Integer(OrdTypeData^.OrdType)));
if ATypeInfo^.Kind <> tkSet then begin
AStrings.Add('Min Value: ' + IntToStr(OrdTypeData^.MinValue));
AStrings.Add('Max Value: ' + IntToStr(OrdTypeData^.MaxValue));
end;
if ATypeInfo^.Kind = tkSet then
GetOrdTypeInfo(OrdTypeData^.CompType^, AStrings);
if ATypeInfo^.Kind = tkEnumeration then
for I := OrdTypeData^.MinValue to OrdTypeData^.MaxValue do
AStrings.Add(Format(' Value %d: %s', [I, GetEnumName(ATypeInfo, I)]));
end;

在表单上放置一个 TListBox,运行以下代码查看结果:

type TMyEnum = (EnumA, EnumB, EnumC);
procedure TForm1.FormCreate(Sender: TObject);
begin
GetOrdTypeInfo(TypeInfo(Char), ListBox1.Items);
GetOrdTypeInfo(TypeInfo(Integer), ListBox1.Items);
GetOrdTypeInfo(TypeInfo(TFormBorderStyle), ListBox1.Items);
GetOrdTypeInfo(TypeInfo(TBorderIcons), ListBox1.Items);
GetOrdTypeInfo(TypeInfo(TMyEnum), ListBox1.Items);
end;

(如果枚举元素没有按缺省的 0 基准定义,那么将不能产生 RTTI 信息,为什么?)

===========================================================
⊙ 获取其它数据类型的 RTTI 信息
===========================================================

上面讨论了几个典型的 RTTI 信息的运行,其它的数据类型的 RTTI 信息的获取方法与上面类似。由于这些操作更加简单,就不一一讨论。下面概述其它类型的 RTTI 信息的情况:

LongString、WideString 和 Variant 没有 RTTI 信息;
ShortString 只有 MaxLength 信息;
浮点数类型只有 FloatType: TFloatType 信息;
TFloatType = (ftSingle, ftDouble, ftExtended, ftComp, ftCurr);
Int64 只有最大值和最小值信息(也是 64 位整数表示);
Interface 和动态数组不太熟悉,就不作介绍了。

===========================================================
⊙ 结束
===========================================================

posted @ 2011-06-05 18:08 墨者工作室 阅读(59) 评论(0) 编辑
TStrings类

    出于效率的考虑,Delphi并没有象C++和Java那样将字符串定义为类,因此TList本身不能直接存储字符串,而字符串列表又是使用非常广泛的,为此Borland提供了TStrings类作为存储字符串的基类,应该说是它除了TList类之外另外一个最重要的Delphi容器类。要注意的是TStrings类本身包含了很多抽象的纯虚的方法,因此不能实例化后直接使用,必须从TStrings类继承一个基类实现所有的抽象的纯虚方法来进行实际的字符串列表管理。虽然TStrings类本身是一个抽象类,但是它应该说是一个使用了Template模式的模版类,提供了很多事先定义好的算法来实现添加添加、删除列表中的字符串,按下标存取列表中的字符串,对列表中的字符串进行排序,将字符串保存到流中。将每个字符串同一个对象关联起来,提供了键-值对的关联等等。
    因为TStrings类本身是个抽象类,无法实例化,因此Delphi提供了一个TStringList的TStrings的子类提供了TStrings类的默认实现,通常在实际使用中,我们都应该使用TStringList类存储字符串列表,代码示意如下:
var   TempList: TStrings;      
begin
   TempList := TStringList.Create; 
   try    
    TempList.Add(‘字符串1’);
      …
   finally    
    TempList.Free;      
   end;
end;

    TStrings类的应用非常广泛,很多VCL类的属性都是TStrings类型,比如TMemo组件的Lines属性,TListBox的Items属性等等。下面将介绍一下TStrings类的常见用法。

TStrings类的常见的用法

根据下标存取列表中的字符串是最常见的一种操作,用法示意如下:
   StringList1.Strings[0] := '字符串1';
     注意在Delphi中,几乎所有的列表的下标都是以0为底的,也就是说Strings[0]是列表中的第一个字符串。另外,由于Strings属性是字符串列表类的默认属性,因此可以省略Strings,直接用下面的简便方法存取字符串:
   StringList1[0] := '字符串1';



定位一个列表中特定的字符串的位置,可以使用IndexOf方法,IndexOf方法将会返回在字符串列表中的第一个匹配的字符串的索引值,如果没有匹配的字符串则返回-1。比如我们可以使用IndexOf方法来察看特定文件是否存在于文件列表框中,代码示意如下:



if FileListBox1.Items.IndexOf('TargetFileName') > -1 ...



有一点不方便的是TStrings类没有提供一个方法可以查找除了第一个匹配字符串外其他同样匹配的字符串的索引,只能是自己遍历字符串列表来实现,这点不如C++中的模版容器类以及相关的模版算法强大和方便。下面是一个遍历字符串列表的示意,代码遍历列表框中的所有字符串,并将其全部转化为大写的字符串:



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');



如果要想将一个字符串列表中的所有字符串都添加到另一个字符串列表中,可以使用AddStrings方法,用法如下:

StringList1.AddStrings(StringList2);  



要想克隆一个字符串列表的所有内容,可以使用Assign方法,例如下面的方法将Combox1中的字符串列表复制到了Memo1中:

Memo1.Lines.Assign(ComboBox1.Items);

要注意的是使用了Assign方法后,目标字符串列表中原有的字符串会全部丢失。



同对象关联



前面说了我们可以将字符串同对象绑定起来,我们可以使用AddObject或者InsertObject方法向列表添加同字符串关联的对象,也可以通过Objects属性直接将对象同特定位置的字符串关联。此外TStrings类还提供了IndexOfObject方法返回指定对象的索引,同样的Delete,Clear和Move等方法也可以作用于对象。不过要注意的是我们不能向字符串中添加一个没有同字符串关联的对象。



同视图交互



刚刚学习使用Delphi的人都会为Delphi IDE的强大的界面交互设计功能所震惊,比如我们在窗体上放上一个ListBox,然后在object Inspector中双击它的Items属性(TStrings类型),在弹出的对话框中,见下图,我们输入一些字符串后,点击确定,关闭对话框,就会看到窗体上的ListBox中出现了我们刚才输入的字符串。



可以我们在TStrings和默认的实现类TStringList的源代码中却找不到同ListBox相关的代码,那么这种界面交互是如何做到的呢?



秘密就在于TListBox的Items属性类型实际上是TStrings的基类TListBoxStrings类,我们看一下这个类的定义:



   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类实现了TStrings类的所有抽象方法,同时在内部有一个ListBox的私有变量。我们再看一下TListBoxStrings的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在内部并没有保存添加的字符串,而是直接向Windows的原生列表盒控件发送消息实现的代码添加,而Windows的原生列表盒是一个MVC的组件,当内部的数据发生变化时,会自动改变视图显示,这就是为什么我们在设计器中输入的字符串会立刻显示在窗体列表框中的原因了。



于是我们也就知道为什么Borland将TStrings设计为一个抽象的类而没有提供一个默认的存储方式,就是因为很多的界面组件在内部对数据的存储有很多不同的方式,Borland决定针对不同的组件提供不同的存储和交互方式。同样的我们要编写的组件如果有TStrings类型的属性,同时也要同界面或者其它资源交互的话,不要使用TStringList来实现,而应该从TStrings派生出新类来实现更好的交互设计。



还有一点要说明的是,Delphi的IDE只在使用Delphi的流机制保存组件到窗体设计文件DFM文件中的时,做了一些特殊的处理,能够自动保存和加载Published的TStrings类型的属性,下面就是一个ListBox储存在窗体设计文件DFM中文本形式示意(在窗体设计阶段,我们可以直接使用View As Text右键菜单命令看到下面的文本),我们可以注意到在设计时我们输入的Items的两个字符串被保存了起来:



   object ListBox1: TListBox

     Left = 64

     Top = 40

     Width = 145

     Height = 73

     ItemHeight = 16

     Items.Strings = (

       'String1'

       'String2')

     TabOrder = 1

   end

随后如果运行程序时,VCL库会使用流从编译进可执行文件的DFM资源中将Items.Strings列表加载到界面上,这样就实现了设计是什么样,运行时也是什么样的所见即所得。



键-值对



在实际开发过程中,我们经常会碰到类似于字典的定位操作的通过键查找相应值的操作,比如通过用户名查找用户相应的登陆密码等。在C++和Java中,标准模版库和JDK都提供了Map类来实现键-值机制,但是Delphi的VCL库却没有提供这样的类,但是TStrings类提供了一个简易的Map替代的实现,那就是Name-Value对。



对于TStrings来说,所谓的Name-Value对,实际上就是’Key=Value’这样包含=号的分割的字符串,等号左边的部分就是Name,等号右边的部分就是Value。TStrings类提供了IndexOfName和Values等属性方法来操作Name-Value对。下面是用法示意:



var

   StringList1:TStrings;

Begin

   StringList1:=TStringList.Create;

   //添加用户名-密码对

   StringList1.Add(‘hubdog=aaa’);

   StringList1.Add(‘hubcat=bbb’);

   ….

   //根据用户名hubdog查找密码

   Showmessage(StringList1.Values[StringList1.IndexOfName(‘hubdog’)]);

End;



从Delphi7开始,TStrings类增加了一个NameValueSeparator属性,我们可以通过这个属性修改默认的Name-Value分割符号为=号以外的其它符号了。还要说明的是,TStrings的Name-Value对中的Name可以不唯一,这有点类似于C++中的MultiMap,这时通过Values[Names[IndexOfName]]下标操作取到的值不一定是我们所需要的,另外TStrings类的Name-Value对的查找定位是采用的遍历的方式,而不同于Java和C++中的Map是基于哈希表或者树的实现,因此查找和定位的效率非常低,不适用于性能要求非常高的场景。不过从Delphi6开始,VCL库中在IniFiles单元中提供了一个基于哈希表的字符串列表类THashedStringList类可以极大的提高查找定位的速度。



THashedStringList类



一般来说,通过键来查找值最简单的办法是遍历列表对列表中的键进行比较,如果相等则获取相应的键值。但是这种简单的办法也是效率最差的一种办法,当列表中的项目比较少时,这种办法还可以接受,但是如果列表中项目非常多的话,这种方法会极大的影响软件的运行速度。 这时我们可以使用哈希表来快速的通过键值来存取列表中的元素。由于本书并不是一本数据结构和算法的书,因此我无意在这里讨论哈希表背后的理论知识,我们只要知道哈希可以通过键快速定位相应的值就可以了,对此感兴趣的非计算机专业的人可以去察看相关的书,这里就不赘述了。



Delphi6中提供的THashedStringList类没有提供任何的新的方法,只是对IndexOf和IndexOfName函数通过哈希表进行了性能优化,下面这个例子演示了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
//初始化系统
     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过程,采用了新的THashedStringList类来实现的查找,而Iterate过程中使用了原来的TStringList类的IndexOfName来实现的查找。采用GpProfile(注:GpProfile的用法参见工具篇的性能分析工具GpProfile章节)对两个过程进行了性能比较后,从下图可以看到Hash执行同样查找动作只用了0.7%的时间,而Iterate方法则用了99.3%的时间,可以看到在字符串列表项目数在几千的数量级别时,基于哈希表的查询速度是原有方法的100多倍。



不过要说明的是,THashedStringList同TStringList类相比,虽然查找的速度大大提高了,但是在添加、删除字符串后再次进行查找操作时,需要重新计算哈希函数,所以如果频繁的进行删除或者添加同查找的复合操作,执行的速度很有可能比TStringList还要慢,这是使用时需要注意的。



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)
   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;
  
   //火车类
   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;


其中车厢类的定义非常简单,只是定义了四个属性。而车厢集合类重定义了静态的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则是实现所见即所得设计的关键类,对于开发灵活强大的自定义组件来说是必不可少的。

posted @ 2011-04-15 10:13 墨者工作室 阅读(386) 评论(1) 编辑

Delphi中的容器类
作者 陈省
  从Delphi 5开始VCL中增加了一个新的Contnrs单元,单元中定义了8个新的类,全部都是基于标准的TList 类。
 

   TList 类
    TList 类实际上就是一个可以存储指针的容器类,提供了一系列的方法和属性来添加,删除,重排,定位,存取和排序容器中的类,它是基于数组的机制来实现的容器,比较类似于C++中的Vector和Java中的ArrayList,TList 经常用来保存一组对象列表,基于数组实现的机制使得用下标存取容器中的对象非常快,但是随着容器中的对象的增多,插入和删除对象速度会直线下降,因此不适合频繁添加和删除对象的应用场景。下面是TList类的属性和方法说明:

    属性描述
      Count: Integer; //返回列表中的项目数
      Items[Index: Integer]: Pointer; default 通过以0为底的索引下标直接存取列表中的项目
    方法类型描述
      Add(Item: Pointer): Integer;//函数用来向列表中添加指针
      Clear;//过程清空列表中的项目
      Delete(Index: Integer);//过程删除列表中对应索引的项目
      IndexOf(Item: Pointer): Integer;//函数返回指针在列表中的索引
      Insert(Index: Integer; Item: Pointer);//过程将一个项目插入到列表中的指定位置
      Remove(Item: Pointer): Integer;//函数从列表中删除指针
   名称类型描述
      property Capacity: Integer;//可以用来获取或设定列表可以容纳的指针数目
      function Extract(Item: Pointer): Pointer;//Extract 类似于Remove 可以将指针从列表中删除,不同的是返回被删除的指针。  
      procedure Exchange(Index1, Index2: Integer); //交换列表中两个指针

      function First: Pointer;;//返回链表中的第一个指针
      function Last: Pointer;// 返回链表中最后一个指针
      function Move(CurIndex NewIndex: Integer);// 将指针从当前位置移动到新的位置

      procedure Pack;// 从列表中删除所有nil指针
      procedure  Sort(Compare: TListSortCompare);//用来对链表中的项目进行排序,可以设定Compare参数为用户定制的排序函数 

  TObjectList 类
    TObjectList 类直接从TList 类继承,可以作为对象的容器。TObjectList类定义如下:  
       TObjectList = class(TList) 
         ...
    public
     constructor Create; overload;
     constructor Create(AOwnsObjects: Boolean); overload;
     function Add(AObject: TObject): Integer; 
     function Remove(AObject: TObject): Integer; 
     function IndexOf(AObject: TObject): Integer; 
     function FindInstanceOf(AClass: TClass; AExact: Boolean = True; AStartAt: Integer = 0):Integer; 
     procedure Insert(Index: Integer; AObject: TObject); 
     property OwnsObjects: Boolean; 
     property Items[Index: Integer]: TObject; default;
 end;

不同于TList类,TObjectList类的Add, Remove, IndexOf, Insert等方法都需要传递TObject对象作为参数,由于有了编译期的强类型检查,使得TObjectList比TList更适合保存对象。此外TObjectList对象有OwnsObjects属性。当设定为True (默认值),同TList类不同,TObjectList对象将销毁任何从列表中删除的对象。无论是调用Delete, Remove, Clear 方法,还是释放TObjectList对象,都将销毁列表中的对象。有了TObjectList类,我们就再也不用使用循环来释放了对象。这就避免了释放链表对象时,由于忘记释放链表中的对象而导致的内存泄漏。 另外要注意的是OwnsObjects属性不会影响到Extract方法,TObjectList的Extract方法行为类似于TList,只是从列表中移除对象引用,而不会销毁对象。

  

TObjectList 对象还提供了一个FindInstanceOf 函数,可以返回只有指定对象类型的对象实例在列表中的索引。如果AExact 参数为True,只有指定对象类型的对象实例会被定位,如果AExact 对象为False,AClass 的子类实例也将被定位。AStartAt 参数可以用来找到列表中的多个实例,只要每次调用FindInstanceOf 函数时,将起始索引加1,就可以定位到下一个对象,直到FindInstanceOf 返回-1。下面是代码示意:

  

var

    idx: Integer;

begin

    idx := -1;

     repeat

      idx := ObjList.FindInstanceOf(TMyObject, True, idx+1);

       if idx >= 0 then

        ...

     until(idx < 0);

end;

  

TComponentList 类

Contnrs单元中还定义了TComponentList 类,类定义如下:



TComponentList = class(TObjectList)

    ...

public

     function Add(AComponent: TComponent): Integer;

     function Remove(AComponent: TComponent): Integer;

     function IndexOf(AComponent: TComponent): Integer;

     procedure Insert(Index: Integer; AComponent: TComponent);

     property Items[Index: Integer]: TComponent; default;

end;

注意TComponentList 是从TObjectList类继承出来的,它的Add, Remove, IndexOf, Insert和 Items 方法调用都使用TComponent 类型的参数而不再是TObject类型,因此适合作为TComponent对象的容器。TComponentList 类还有一个特殊的特性,就是如果链表中的一个组件被释放的话,它将被自动的从TComponentList 链表中删除。这是利用TComponent的FreeNotification方法可以在组件被销毁时通知链表,这样链表就可以将对象引用从链表中删除的。   

TClassList 类

Contnrs单元中还定义了TClassList类,类定义如下:



TClassList = class(TList)

protected

     function GetItems(Index: Integer): TClass;

     procedure SetItems(Index: Integer; AClass: TClass);

public

     function Add(aClass: TClass): Integer;

     function Remove(aClass: TClass): Integer;

     function IndexOf(aClass: TClass): Integer;

     procedure Insert(Index: Integer; aClass: TClass);

     property Items[Index: Integer]: TClass

       read GetItems write SetItems; default;

end;

不同于前面两个类,这个类继承于TList的类只是将Add, Remove, IndexOf, Insert和Items 调用的参数从指针换成了TClass元类类型。  

TOrderedList, TStack和TQueue 类

Contnrs单元还定义了其它三个类:TOrderedList, TStack和TQueue,类型定义如下:



TOrderedList = class(TObject)

private

    FList: TList;

protected

     procedure PushItem(AItem: Pointer); virtual; abstract;

    ...

public

     function Count: Integer;

     function AtLeast(ACount: Integer): Boolean;

     procedure Push(AItem: Pointer);

     function Pop: Pointer;

     function Peek: Pointer;

end;

  

TStack = class(TOrderedList)

protected

     procedure PushItem(AItem: Pointer); override;

end;

  

TQueue = class(TOrderedList)

protected

     procedure PushItem(AItem: Pointer); override;

end;

要注意虽然TOrderedList 并不是从TList继承的,但是它在内部的实现时,使用了TList来储存指针。另外注意TOrderedList类的PushItem 过程是一个抽象过程,所以我们无法实例化 TOrderedList 类,而应该从TOrderedList继承新的类,并实现抽象的PushItem方法。TStack 和 TQueue 正是实现了PushItem抽象方法的类, 我们可以实例化TStack 和TQueue类作为后进先出的堆栈 (LIFO)和先进先出的队列(FIFO)。下面是这两个的的方法使用说明: 

·                       Count 返回列表中的项目数。

·                       AtLeast 可以用来检查链表的大小,判断当前列表中的指针数目是否大于传递的参数值,如果为True表示列表中的项目数大于传来的参数。 

·                       对于TStack类Push 方法将指针添加到链表的最后,对于TQueue类Push 方法则将指针插入到链表的开始。

·                       Pop返回链表的末端指针,并将其从链表中删除。 

·                       Peek返回链表的末端指针,但是不将其从链表中删除。 

  

TObjectStack和TObjectQueue类

Contnrs单元中最后两个类是TObjectStack和TObjectQueue类,类的定义如下:

TObjectStack = class(TStack)

public

     procedure Push(AObject: TObject);

     function Pop: TObject;

     function Peek: TObject;

end;

  

TObjectQueue = class(TQueue)

public

     procedure Push(AObject: TObject);

     function Pop: TObject;

     function Peek: TObject;

end;

这两个类只是TStack和TQueue 类的简单扩展,在链表中保存的是TObject的对象引用,而不是简单的指针。



TIntList 类

到目前为止,我们看到的容器类中保存的都是指针或者对象引用(对象引用其实也是一种指针)。

那么我们能不能在链表中保存原生类型,如Integer,Boolean或者Double等呢。下面的我们定义的类TIntList 类就可以在链表中保存整数,这里我们利用了整数和指针都占用4个字节的存储空间,所以我们可以直接将指针映射为整数。



unit IntList;

  

interface

  

uses

    Classes;

  

type

    TIntList = class(TList)

     protected

       function GetItem(Index: Integer): Integer;

       procedure SetItem(Index: Integer;

         const Value: Integer);

     public                               

       function Add(Item: Integer): Integer;

       function Extract(Item: Integer): Integer;

       function First: Integer;

       function IndexOf(Item: Integer): Integer;

       procedure Insert(Index, Item: Integer);

       function Last: Integer;

       function Remove(Item: Integer): Integer;

       procedure Sort;

       property Items[Index: Integer]: Integer

         read GetItem write SetItem; default;

     end;

  

implementation

  

{ TIntList }

function TIntList.Add(Item: Integer): Integer;

begin

    Result := inherited Add(Pointer(Item));

end;

  

function TIntList.Extract(Item: Integer): Integer;

begin

    Result := Integer(inherited Extract(Pointer(Item)));

end;

  

function TIntList.First: Integer;

begin

    Result := Integer(inherited First);

end;

  

function TIntList.GetItem(Index: Integer): Integer;

begin

    Result := Integer(inherited Items[Index]);

end;

  

function TIntList.IndexOf(Item: Integer): Integer;

begin

    Result := inherited IndexOf(Pointer(Item));

end;

  

procedure TIntList.Insert(Index, Item: Integer);

begin

     inherited Insert(Index, Pointer(Item));

end;

  

function TIntList.Last: Integer;

begin

    Result := Integer(inherited Last);

end;

  

function TIntList.Remove(Item: Integer): Integer;

begin

    Result := inherited Remove(Pointer(Item));

end;

  

procedure TIntList.SetItem(Index: Integer;

     const Value: Integer);

begin

     inherited Items[Index] := Pointer(Value);

end;

  

function IntListCompare(Item1, Item2: Pointer): Integer;

begin

     if Integer(Item1) < Integer(Item2) then

      Result := -1

     else if Integer(Item1) > Integer(Item2) then

      Result := 1

     else

      Result := 0;

end;                         

  

procedure TIntList.Sort;

begin

     inherited Sort(IntListCompare);

end;

  

end.



扩展TList,限制类型的对象列表  

Begin Listing Two - TMyObjectList

TMyObject = class(TObject)

public

     procedure DoSomething;

end;

  

TMyObjectList = class(TObjectList)

protected

     function GetItems(Index: Integer): TMyObject;

     procedure SetItems(Index: Integer; AMyObject: TMyObject);

public

     function Add(aMyObject: TMyObject): Integer;

     procedure DoSomething;

     function Remove(aMyObject: TMyObject): Integer;

     function IndexOf(aMyObject: TMyObject): Integer;

     procedure Insert(Index: Integer; aMyObject: TMyObject);

     property Items[Index: Integer]: TMyObject

       read GetItems write SetItems; default;

end;

...

{ TMyObjectList }

function TMyObjectList.Add(AMyObject: TMyObject): Integer;

begin

    Result := inherited Add(AMyObject);

end;

  

procedure TMyObjectList.DoSomething;

var

    i: Integer;

begin

     for i := 0 to Count-1 do

      Items[i].DoSomething;

end;

  

function TMyObjectList.GetItems(Index: Integer): TMyObject;

begin

    Result := TMyObject(inherited Items[Index]);

end;

  

function TMyObjectList.IndexOf(AMyObject: TMyObject):

    Integer;

begin

    Result := inherited IndexOf(AMyObject);

end;

  

procedure TMyObjectList.Insert(Index: Integer;

    AMyObject: TMyObject);

begin

     inherited Insert(Index, AMyObject);

end;

  

function TMyObjectList.Remove(AMyObject: TMyObject):

    Integer;

begin

    Result := inherited Remove(AMyObject);

end;

  

procedure TMyObjectList.SetItems(Index: Integer;

    AMyObject: TMyObject);

begin

     inherited Items[Index] := AMyObject;

end;

End Listing Two

posted @ 2011-04-14 23:05 墨者工作室 阅读(215) 评论(0) 编辑
      理论:     //适用于实现不是某一特定过程或函数
          type
               TNotifyEvent = procedure(Sender: TObject) of object; 
      首先:procedure 也是类型,可以理解为过程类型,定义过程的参数结构,而具体的实现可以动态赋值 
 onclick那样例子:
     声明:  onclick= procedure(Sender: TObject,a :integer) of object;以后你就可以把TNotifyEvent作为过程用了,而不用考虑它实现什么功能,你想onclik  里用户写了什么,都是一样的
        if assign(onclick) then
          onclick;
     这样只要TNotifyEvent可以执行,程序就去执行它,控件就是这样实现事件的
     procedure 指针占4个字节,保存在过程的地址. procedure of object占8个字节,保存过程的地址和类的地址
     procedure of object 是类过程(类函数),用法如下:
         1、与普通函数相同点:可以像一般的函数和过程一样在类的实例里作为对象方法调用;
         2、与普通函数不同点:可以不通过对象,而是通过类来直接调用。
    也就是说,一般函数和过程必须通过类的实例(对象)来调用,而类过程(类函数)则可以不必通过该类的实例来调用。通过类来调用函数或者过程,可以定义和实现一些不能或者不适合作为某一特定对象行为的方法。 
  应用:
     delphi中经常见到以下两种定义
     Type 
         TMouseProc = procedure (X,Y:integer);  //一种普通的过程
         TMouseEvent = procedure (X,Y:integer) of Object;//一种对象方法的类型

      两者样子差不多但实际意义却不一样,两者的区别就在于TMouseEvent类型的方法必须在一个对象里。类方法存在一个隐藏参数self,也就是说两者形参不一样,所以不能相互转换 .
   TMouseProc只是单一的函数指针类型;
   TMouseEvent是对象的函数指针,也就是对象/类的函数/方法
 procedure   TForm1.BBB(I:   Integer);
begin
    showmessage( 'BBB: '   +   IntToStr(I));
end;

procedure   TForm1.CCC(I:   Integer);
begin
    showmessage( 'CCC: '   +   IntToStr(I));
end;

procedure   TForm1.Button1Click(Sender:   TObject);
    type
        Taaa   =   procedure   (i:integer)   of   object   ;
var
    ap:   Taaa;
begin
    ap   :=   BBB; //这里有一个隐含Self,完整格式: self.BBB;这里把BBB这个方法赋给ap这个变量,注意BBB与ap的声明原型要一样,不然会有错
    ap(1);
    ap   :=   CCC;
    ap(2);
end;
posted @ 2011-04-14 22:37 墨者工作室 阅读(384) 评论(0) 编辑
摘要: 有没有使用过Adobe Photoshop?如果用过,你就会对插件的概念比较熟悉。对外行人来说,插件仅仅是从外部提供给应用程序的代码块而已(举个例子来说,在一个DLL中)。一个插件和一个普通DLL之间的差异在于插件具有扩展父应用程序功能的能力。例如,Photoshop本身并不具备进行大量的图像处理功能。插件的加入使其获得了产生诸如模糊、斑点,以及其他所有风格的奇怪效果,而其中任何一项功能都不是父应...阅读全文
posted @ 2010-10-17 17:24 墨者工作室 阅读(269) 评论(0) 编辑
摘要: by Joanna Carter译文:skyblue(转载请注明作者)在一篇已经发表的文章中做了微小改动;可以从这篇文章中看到关于Model View Presenter(模型-视图-推荐者)的概念更胜于Model View Controller(模型-视图-控制器)。“但是我还不知道什么是模型-视图-控制器!”,你可能会说。好,在本文得最后篇章中我希望你得问题或者其他更多...阅读全文
posted @ 2010-08-22 17:29 墨者工作室 阅读(734) 评论(0) 编辑
摘要: 服务端:添加ADOConnection,ADOQuery1,ADOQuery2。设置ADOQuery1为主表,ADOQuery2为从表。(通过 ADOQuery2.SQL.Text:= 'select * from 从表 where FKID=:主表PKID'设置)。添加DataSetProvider1和DataSetProvider2分别连接到ADOQuery1和ADOQuery2。客户端:添加...阅读全文
posted @ 2010-08-22 16:34 墨者工作室 阅读(298) 评论(0) 编辑
摘要: 一个DataSnap的应用程序由两个层组成:DataSnap服务器,它有一个带有一个或者更多DataSetProvider组件的远程数据模块;DataSnap客户端,它带有一个xxxConnection组件和一个或者多个连接到DataSetProviders的ClientDataSets。你可以不使用ClientDataSet而换用XMLBroker组件,后者是专门用在InternetExpres...阅读全文
posted @ 2010-08-22 16:29 墨者工作室 阅读(564) 评论(0) 编辑