运行期类型信息(RTTI)是一种语言特征,能使应用程序在运行时得到关于对 象的信息。RTTI是Delphi的组件能够融合到IDE中的关键。它在IDE中不仅仅是一 个纯学术的过程。 由于对象都是从TObject继承下来的,因此,对象都包含一个指向它们的 RTTI的指针以及几个内建的方法。下面的表列出了TObject的一些方法,用这些 方法能获得某个对象实例的信息。
函数 返回类型 返回值 ClassName( ) string 对象的类名 ClassType() boolean 对象的类型 InheritsFrom boolean 判断对象是否继承于一个指定的类 ClassParent() TClass 对象的祖先类型 Instancesize() word 对象实例的长度(字节数) ClassInfo() Pointer 指向RTTI的指针
第一部分:关于as 和 is
Object Pascal提供了两个运算符as和is,用它们通过RTTI能对对象进行比较和强制类型转换。 关键字as是类型转换的一种新的形式。它能把一个基层的对象强制类型转换成它的派生类,如果转换不合法就产生一个异常。假定有一个过程,想让它能够传递任何类型的对象,它应该这样定义: Procedure Foo(AnObject :Tobject); 在这个过程如果要对AnObject进行操作,要把它转换为一个派生对象。假定把AnObject看成是一个TEdit派生类型,并想要改变它所包含的文本,用下列代码: (AnObject as Tedit).text := 'wudi_1982'; 能用比较运算符来判断两个对象是否是相兼容的类型,用is运算符把一个未知的对象和一个已知类型或实例进行比较,确定这个未知对象的属性和行为。例如,在对(AnObject 进行强制类型转换前,确定(AnObject 和TEdit是否指针兼容:
if (AnObject is Tedit) then Tedit(AnObjject).text := 'wudi_1982'; 注意在这个例子中不要再使用as进行强制类型转换,这是因为它要大量使用RTTI,另外还因为,在第一行已经判断Foo就是TEdit,可以通过在第2行进行指针转换来优化。
这两个操作符最典型的应用我想应该是在程序需要的部分清空窗体上所有 edit的text属性
procedure TForm1.ClearEdit(Acontrl: TWinControl); var i : integer; begin for i : = 0 to Acontrl.ControlCount - 1 do begin if Acontrl.Controls[i] is TEdit then ((Acontrl.Controls[i]) as TEdit).Text : = '' ; if Acontrl.Controls[i] is TCustomControl then ClearEdit( (Acontrl.Controls[i] as TCustomControl)) end; end;
第二部分:RTTI
上文中已经多次提到了RTTI,但好像并没有看到RTTI出现。那么RTTI是如何表现自己的呢?你将发现, RTTI至少在两个地方对你有用。第一个地方是DELPHI的IDE,这在前面已提到过。通过RTTI,IDE就会知道你正在使用的对象和组件的 任何事情。实际上,不只是RTTI,但为了这个讨论,我们只谈RTTI方面。其实上 面的as,is操作都间接的使用了RTTI。 还是用个例子来演示吧。在观看此例子之时,建议你看看typinfo.pas中的 内容(DELPHI安装目录下/source/rtl/common/TypInfo.pas); 下面的例子主要分为两部分,界面上半部分,主要演示通过rtti来显示用户 选择类型的信息。(有3个TListBox)。 下面的部分主要通过RTTI来完成通过配置信息对控件进行属性的赋值操作, 这里将演示文本类型和事件类型的赋值。 窗体文件如下: 代码如下:
object Form1: TForm1 Left = 150 Top = 161 Width = 639 Height = 372 Caption = ' Form1 ' Color = clBtnFace Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = - 11 Font.Name = ' Tahoma ' Font.Style = [] OldCreateOrder = False OnCreate = FormCreate PixelsPerInch = 96 TextHeight = 13 object Panel1: TPanel Left = 0 Top = 0 Width = 631 Height = 185 Align = alTop TabOrder = 0 object GroupBox1: TGroupBox Left = 1 Top = 1 Width = 185 Height = 183 Align = alLeft Caption = ' 在这里选择要查看类型的信息 ' TabOrder = 0 object ListBox1: TListBox Left = 2 Top = 15 Width = 181 Height = 166 Align = alClient ItemHeight = 13 TabOrder = 0 OnClick = ListBox1Click end end object GroupBox2: TGroupBox Left = 368 Top = 1 Width = 262 Height = 183 Align = alRight Caption = ' 属性信息 ' TabOrder = 1 object ListBox3: TListBox Left = 2 Top = 15 Width = 258 Height = 166 Align = alClient ItemHeight = 13 TabOrder = 0 end end object GroupBox3: TGroupBox Left = 186 Top = 1 Width = 182 Height = 183 Align = alClient Caption = ' 基本信息 ' TabOrder = 2 object ListBox2: TListBox Left = 2 Top = 15 Width = 178 Height = 166 Align = alClient ItemHeight = 13 TabOrder = 0 end end end object TPanel Left = 0 Top = 185 Width = 631 Height = 157 Align = alClient TabOrder = 1 object Panel2: TPanel Left = 1 Top = 1 Width = 230 Height = 155 Align = alLeft TabOrder = 0 object Label2: TLabel Left = 10 Top = 8 Width = 84 Height = 13 Caption = ' 要修改的控件名 ' end object Label3: TLabel Left = 8 Top = 32 Width = 72 Height = 13 Caption = ' 修改的属性名 ' end object Label4: TLabel Left = 8 Top = 64 Width = 72 Height = 13 Caption = ' 将属性修改为 ' end object edComName: TEdit Left = 104 Top = 5 Width = 78 Height = 21 TabOrder = 0 Text = ' label1 ' end object edPproName: TEdit Left = 104 Top = 32 Width = 81 Height = 21 TabOrder = 1 Text = ' caption ' end object edValue: TEdit Left = 104 Top = 56 Width = 81 Height = 21 TabOrder = 2 Text = ' 12345 ' end object btnInit: TButton Left = 8 Top = 104 Width = 75 Height = 25 Caption = ' 初始化 ' TabOrder = 3 OnClick = btnInitClick end object btnModify: TButton Left = 104 Top = 104 Width = 75 Height = 25 Caption = ' 修改 ' TabOrder = 4 OnClick = btnModifyClick end end object Panel3: TPanel Left = 231 Top = 1 Width = 399 Height = 155 Align = alClient TabOrder = 1 object GroupBox4: TGroupBox Left = 1 Top = 1 Width = 397 Height = 153 Align = alClient Caption = ' 被修改的控件 ' TabOrder = 0 object Label1: TLabel Left = 16 Top = 32 Width = 28 Height = 13 Caption = ' label1 ' end object BitBtn1: TBitBtn Left = 8 Top = 64 Width = 75 Height = 25 Caption = ' BitBtn1 ' TabOrder = 0 end end end end end
... { 作者:wudi_1982 联系方式:wudi_1982@hotmail.com 转载请注明出处 } unit main;interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs,typinfo, StdCtrls, ExtCtrls, Buttons; type InsertCom = record Name : string ; // 要修改属性的组件名 PproName : string ; // 要修改控件的属性名 MethodName : string ; // 要修改or添加给控件的事件名 text : string ; // 属性值,这里修改的是string类型的数值 end; TForm1 = class (TForm) Panel1: TPanel; GroupBox1: TGroupBox; ListBox1: TListBox; GroupBox2: TGroupBox; GroupBox3: TGroupBox; ListBox2: TListBox; ListBox3: TListBox; Panel2: TPanel; edComName: TEdit; Label2: TLabel; Label3: TLabel; edPproName: TEdit; Label4: TLabel; edValue: TEdit; Panel3: TPanel; btnInit: TButton; btnModify: TButton; GroupBox4: TGroupBox; Label1: TLabel; BitBtn1: TBitBtn; procedure FormCreate(Sender: TObject); procedure ListBox1Click(Sender: TObject); procedure btnInitClick(Sender: TObject); procedure btnModifyClick(Sender: TObject); private TestCom : InsertCom; procedure MyClick(Sender : TObject); // 给控件添加onclick事件 public ... { Public declarations } end; var Form1: TForm1; implementation... {$R * .dfm} function CreateClass(const AClassName : string ):TObject; // 根据名字生成 var tm : TObject; t : TFormClass; begin t : = TFormClass(FindClass(AClassName)); tm : = t.Create(nil); Result : = tm; end; procedure GetBaseClassInfo(AClass : TObject;AStrings : TStrings); // 获 得类型的基本信息 var classTypeInfo : PTypeInfo; ClassDataInfo : PTypeData; begin classTypeInfo := AClass.ClassInfo; ClassDataInfo : = GetTypeData(classTypeInfo); with AStrings do begin Add(Format(' name is :%s ' ,[classTypeInfo.Name])); Add(format( ' type kind is :%s ' ,[GetEnumName(TypeInfo (TTypeKind),integer(classTypeInfo.Kind))])); Add(Format( ' in : %s ' ,[ClassDataInfo.UnitName])); end; end; procedure GetBaseClassPro(AClass : TObject;Astrings : TStrings); // 获 得属性信息 var NumPro : integer; // 用来记录事件属性的个数 Pplst : PPropList; // 存放属性列表 Classtypeinfo : PTypeInfo; classDataInfo: PTypeData; i : integer; begin Classtypeinfo : = AClass.ClassInfo; classDataInfo : = GetTypeData(Classtypeinfo); if classDataInfo.PropCount <> 0 then begin // 分配空间 GetMem(Pplst, sizeof (PpropInfo) * classDataInfo.PropCount); try // 获得属性信息到pplst GetPropInfos(AClass.ClassInfo,Pplst); for I : = 0 to classDataInfo.PropCount - 1 do begin if Pplst[i] ^ .PropType ^ .Kind <> tkMethod then // 这里过滤掉了事件属性 Astrings.Add(Format( ' %s:%s ' ,[Pplst[i] ^ .Name,Pplst[i] ^ .PropType ^ .Name])); end; // 获得事件属性 NumPro : = GetPropList(AClass.ClassInfo,[tkMethod],Pplst); if NumPro <> 0 then begin // 给列表添加一些标志 Astrings.Add( '' ); Astrings.Add( ' -----------EVENT----------- ' ); Astrings.Add( '' ); for i : = 0 to NumPro - 1 do // 获得事件属性的列表 Astrings.Add(Format( ' %s:%s ' ,[Pplst[i] ^ .Name,Pplst[i] ^ .PropType ^ .Name])); end; finally FreeMem(Pplst,sizeof (PpropInfo) * classDataInfo.PropCount); end; end; end; procedure TForm1.btnInitClick(Sender: TObject); begin // 修改label1的caption属性为12345 TestCom.Name : = edComName.Text; TestCom.PproName : = edPproName.Text; TestCom.text : = edValue.Text; TestCom.MethodName : = ' OnClick ' ; btnModify.Enabled : = true ; end; procedure TForm1.btnModifyClick(Sender: TObject); var pp : PPropInfo; obj : TComponent; a : TMethod; tm : TNotifyEvent; begin obj : = FindComponent(TestCom.Name); // 通过名字查找此控件 if not Assigned(obj) then exit; // 如果没有则退出 // 通过getPropInfo获得指定控件的属性信息,注意,这里只能获得那些公开 了的属性 pp := GetPropInfo(obj.ClassInfo,TestCom.PproName); if Assigned(pp) then begin // 根据kind判断类型是否为string类型 case pp ^ .PropType ^ .Kind of // 这里使用setStrProp来为string类型的属性赋值,对起来类型的赋值 ,请参考TypInfo.pas tkString,tkLString,tkWString : SetStrProp (obj,TestCom.PproName,TestCom.text); end; // 给要修改的控件添加onClick事件, pp : = GetPropInfo(obj.ClassInfo,TestCom.MethodName); if Assigned(pp) then begin if pp ^ .PropType ^ .Kind = tkMethod then begin tm : = MyClick; // Tmethod的code为函数地址,你也可以通过MethodAddress方法获得 a.Code : = @tm; a.Data : = Self; // 对时间赋值 SetMethodProp(obj,TestCom.MethodName,a); end; end; end; end; procedure TForm1.FormCreate(Sender: TObject); begin btnModify.Enabled : = false ; // 给listbox1添加一些类型的类名 with ListBox1.Items do begin Add(' TApplication ' ); Add( ' TEdit ' ); Add( ' TButton ' ); Add( ' Tmemo ' ); Add( ' TForm ' ); end; end; procedure TForm1.ListBox1Click(Sender: TObject); var t : TObject; begin // 当在类型列表中选择一个类型并用鼠标单击后,分别得到它的属性信息和 基本信息 ListBox2.Clear; ListBox3.Clear; t := CreateClass(ListBox1.Items[ListBox1.ItemIndex]); try GetBaseClassInfo(t,ListBox2.Items); GetBaseClassPro(t,ListBox3.Items); finally t.Free; end; end; procedure TForm1.MyClick(Sender: TObject); begin // 给指定控件添加的一个方法 ShowMessage( ' wudi_1982 ' ); end; initialization // 初始化的时候注册 RegisterClasses([TApplication,TButton,TEdit,TMemo,TForm]); end.
注:示例程序在winxp+D7以及turbo delphi+winxp下测试通过。Borland文档中不包含将来也许会有版本变化的功能。当使用如RTTI等无文档说明的功能时,就不能保证你的程序可以完全移植到Delphi的未来版本。转载请注明出处!
程序效果图如下:
编译、运行程序,你可以通过点击左上角列表框中的类型,获得他们的信息。而在窗体的下部,主要演示了通过读取配置信息来对控件的属性赋值(例程中的配置信息是通过edit输入的,可以在实际运用中改成从配置文件读取)。当使用下半部分功能时,在默认情况下,点击初始化按钮,然后点击修改,你会发现label1的caption变成了12345,并在在鼠标点击的时候会弹出一个对话框,你可以尝试把第一个edit的内容改成bitbtn1试试。