运用delphiXE RTTI在运行时动态获取信息及获取某个TComponent类或TObject类的RttiType信息的案例

运用delphiXE RTTI在运行时动态获取信息及获取某个TComponent类或TObject类的RttiType信息的案例

 

一、理解RTTI

先看看官方文档:http://docwiki.embarcadero.com/RADStudio/Rio/en/Working_with_RTTI   译文如下:

        运行时类型信息(RTTI)是一种编程模式,其中可以在运行时获取有关类型的信息。如果启用了RTTI生成,则生成的二进制文件将包含特殊的元数据,该元数据包含有关类型的信息(例如,类的祖先class ancestry,声明的字段declared fields,带注释的属性annotated attributes【我加的:包含类的字段、属性、函数、过程,包括已发布的,公共的,受保护的,私有的】)。使用System.Rtti单元中提供的功能,您可以在运行时获取这些信息。最终结果是能够创建抽象和通用的框架,在其中可以操作任何公开的RTTI类型。

注意:RTTI是Delphi开发工具的特有的 。

注意:不会为泛型方法生成运行时类型信息。

我加的:Delphi将RTTI信息根植到编译后链接的包信息(链接产生的二进制文件,它被链接到.exe可执行文件中( Windows)、.jar( Android)、.静态文件.a.h( IOS))表中System.PackageInfoTable,运行时是去读取该表。注意:在iOS平台上,链接是静态完成的,因此不会植根RTTI。但是,如果使用GetType方法,则可以获取RTTI

RTTI生成的控制

使用以下编译器指令来控制运行时类型信息的生成。您可能需要限制RTTI的生成以减小可执行文件的大小。

Delphi C ++
{$ M},{$ TYPEINFO} __declspec(delphirtti)
{$ METHODINFO} 不适用
{$ RTTI} #pragmaexplicit_rtti
{$ WEAKLINKRTTI} 不适用

话题

也可以看看

程式码范例

二、感性认知,先看看案例:

1、官网案例http://docwiki.embarcadero.com/CodeExamples/Rio/en/Rtti.TRttiType_(Delphi)

2、官方安装包案例:C:\Users\Public\Documents\Embarcadero\Studio\20.0\Samples\Object Pascal\RTL\AttributesAndRTTI\rtti_browser\rtti_browser.dproj

3、我写给大家的案例,查询系统任何类和任何低级别的对象的定义(方法、属性(方法的读写产生属性信息)、字段(一般是私有的泛型,帮助属性写赋值和读信息)),适用于VCL和Fmx,可跨平台使用:

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages,
  System.SysUtils, System.Variants,
  System.Classes, System.IOUtils,
  Vcl.Graphics,
  Vcl.Controls, Vcl.Forms,
  Vcl.Dialogs, Vcl.ComCtrls, Vcl.StdCtrls, Vcl.ExtCtrls;

type
  TForm1 = class(TForm)
    TreeView1: TTreeView;
    btn_getRttiTypeOfTComponent: TButton;
    Panel1: TPanel;
    Panel2: TPanel;
    ComboBoxEx1: TComboBoxEx;
    ComboBoxEx2: TComboBoxEx;

    ///<summary>按钮点击事件btn_getRttiTypeOfTComponent:</summary>
    procedure btn_getRttiTypeOfTComponentClick(Sender: TObject);
    ///<summary>ComboBoxEx1.Items获取RTTIType所请求列表的TComponent的类型名</summary>
    procedure ComboBoxEx1DropDown(Sender: TObject);
    ///<summary>产生文件及其路径:</summary>
    procedure FormCreate(Sender: TObject);
  private
    LFilePath:string;
    LFileHandle:THandle;
    ///<summary>Rtti获取delphi系统所有对象运行时的类名和元信息数组: </summary>
      ///<summary>1、TRttiContext上下文运行环境获取所有对象的运行时类型TRTTIType</summary>
      ///<summary>2、并计算某个TComponent的类名对应的类</summary>
      ///<summary>3、并获取某个类对象RTTIType的ClassName、QualifiedName</summary>
      ///<summary>4、并按类型ClassType的ClassName、方法Methods、属性Properties、字段Fields顺序</summary>
        ///<summary>:即TRttiType.AsInstance.MetaClassType.ClassName、TRttiMethod、TRttiProperty、TRttiField顺序</summary>
        ///<summary>:获取它们的元信息数组:</summary>
        ///<summary>:TRttiType.GetMethods;TRttiType.GetProperties;TRttiType.GetFields:</summary>
      ///<summary>5、通过按钮事件将上述信息写入TreeView(btn_getRttiTypeOfTComponentClick)</summary>
    procedure getRttiTypeOfTComponent(const AComponentName:string);
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation
uses
  RTTI;
{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  LFilePath:=ExtractFilePath(ParamStr(0))+'VclTWinControlCategories.text';
  if not System.SysUtils.FileExists(LFilePath) then
  begin
    LFileHandle:=System.SysUtils.FileCreate(LFilePath,fmShareDenyNone);
    if System.SysUtils.FileExists(LFilePath) then
      System.SysUtils.FileClose(LFileHandle);
    //System.SysUtils.FileCreate  System.IOUtils
  end;
end;

procedure TForm1.ComboBoxEx1DropDown(Sender: TObject);
//:ComboBoxEx1.Items获取RTTIType所请求列表的TComponent的类型名
var
  LFileLines:TArray<system.string>;
  LTComponentClassName:string;
  LListedAClassName:string;
  LIfExistListedAClassName:Boolean;
begin
  if (ComboBoxEx2.Items.Count<=1)
    and (ComboBoxEx1.Items.Count<=1) then
  begin
    try
      btn_getRttiTypeOfTComponentClick(sender);//:即:getRttiTypeOfTComponent(trim(ComboBoxEx1.Text));
      //:执行完之后会自动返回到这里,继续往下执行
        //:因为执行的是Rtti,(Sender:TObject)本身也是1个对象的Rtti
          //:TNotifyEvent = procedure(Sender: TObject) of object;
          //:TObject列不列出来,仅仅取决于其中执行的FillVclClasses中方法的定义
        //:先执行Rtti,再执行sender
    finally
    end;
  end;
  if ComboBoxEx2.Items.Count>0 then
  begin
    LFileLines:=TFile.ReadAllLines(LFilePath);
    if length(LFileLines)>0 then
    begin
      LIfExistListedAClassName:=false;
      for LTComponentClassName in LFileLines do
      begin
        for LListedAClassName in ComboBoxEx1.Items do
          if LListedAClassName=LTComponentClassName then
          begin
            LIfExistListedAClassName:=true;
            Break;
          end;
        if LIfExistListedAClassName=false then
          ComboBoxEx1.Items.Add(LTComponentClassName);
      end;
    end else exit;
  end;
end;

procedure TForm1.btn_getRttiTypeOfTComponentClick(Sender: TObject);
begin
  getRttiTypeOfTComponent(trim(ComboBoxEx1.Text));
end;

procedure TForm1.getRttiTypeOfTComponent(const AComponentName:string);
  ///<summary>返回是否成功,回调所有组件的TRttiType的实例TRttiInstanceType的元类的类名:</summary>
  function FillVclClasses(ATStrings:TStrings): Boolean;
  var
    ctx: TRttiContext;
    typ: TRttiType;
    list: TArray<TRttiType>;
  begin
    Result:=false;
    ctx := TRttiContext.Create;
    list := ctx.GetTypes;
    for typ in list do
      begin
        if typ.IsInstance        //:是实例化的类型
          //and typ.IsManaged    //:不能用:源代码意思好像是指的基本的数据类型或tkClass被UI引用:TypeInfo^.Kind
          //and typ.IsPublicType //:是公开的类型
          and (
                  typ.AsInstance.MetaClassType.QualifiedClassName.Contains('Vcl.StdCtrls')
               or typ.AsInstance.MetaClassType.QualifiedClassName.Contains('Vcl.ComCtrls')
               or typ.AsInstance.MetaClassType.QualifiedClassName.Contains('Vcl.ExtCtrls')
               or typ.AsInstance.MetaClassType.QualifiedClassName.Contains('Vcl.ActnList')
               or typ.AsInstance.MetaClassType.QualifiedClassName.Contains('Vcl.ImgList')
               or typ.AsInstance.MetaClassType.QualifiedClassName.Contains('Vcl.Graphics')
               or typ.AsInstance.MetaClassType.QualifiedClassName.Contains('Vcl.Menus')
               or typ.AsInstance.MetaClassType.QualifiedClassName.Contains('Vcl.Controls')
               or typ.AsInstance.MetaClassType.QualifiedClassName.Contains('TObject')
               //or typ.AsInstance.MetaClassType.QualifiedClassName.Contains('System.Classes')
               //or typ.AsInstance.MetaClassType.QualifiedClassName.Contains('Vcl.Themes') //:含枚举、集合、泛型列表和泛型字典
               //or typ.AsInstance.MetaClassType.QualifiedClassName.Contains('Vcl.Forms') //:不能获取TRttiType

              )
          //and not Uppercase(typ.AsInstance.MetaClassType.ClassName).Contains('TCUSTOM')
          //and not typ.AsInstance.MetaClassType.ClassParent.QualifiedClassName.Contains('Vcl.Controls')
        then
          begin
            ATStrings.Add(typ.AsInstance.MetaClassType.ClassName+sLineBreak);
          end
        else
          begin
            continue;
          end
        ;
      end;
    ctx.Free;
    if ATStrings.Count=0 then Result:=false
    else Result:=true;

  end;
  ///<summary>根据组件名或对象名返回对象的元类</summary>
  function FindAnyClass(const Name: string): TClass;
  var
    ctx: TRttiContext;
    typ: TRttiType;
    list: TArray<TRttiType>;
  begin
    Result := nil;
    ctx := TRttiContext.Create;
    list := ctx.GetTypes;
    for typ in list do
      begin
        if typ.IsInstance        //:是实例化的类型
          //and typ.IsManaged    //:不能用:源代码意思好像是指的基本的数据类型或tkClass被UI引用:TypeInfo^.Kind
          //and typ.IsPublicType //:是公开的类型
          and SameStr( Uppercase(Name),Uppercase(typ.Name) )
          and (Uppercase(Name)<>'TFRAME')//:相当于: and (typ.ClassName<>TFRAME.ClassName)
        then
          begin
            Result := typ.AsInstance.MetaClassType;
            break;
          end
        else
          begin
            Result := nil;
            continue;
          end;
      end;
    ctx.Free;
  end;
var
  //ATComponent:TComponent;
  LContext: TRttiContext;
  LType: TRttiType;
  LMethod: TRttiMethod;
  LProperty: TRttiProperty;
  LField: TRttiField;
  LTreeNode1, LTreeNode2: TTreeNode;
  LLTypeName:string;
  LStringList:TStrings;
  LTreeNodeCount:Integer;
  LIfExistTreeNode:Boolean;
  LTClass:TClass;
  LTComponentsList:string;
  LTComponentsListCount:Integer;
begin
  ComboBoxEx2.Items.Clear;
  ComboBoxEx2.Items.BeginUpdate;
  LStringList:=TStringList.Create;
  try
    if FillVclClasses(LStringList)=true then
    for LLTypeName in LStringList do
    begin
      ComboBoxEx2.Items.Add(LLTypeName);
    end;
  finally
    LStringList.Free;
  end;
  ComboBoxEx2.Items.EndUpdate;
  LTComponentsList:='';
  for LTComponentsListCount :=0 to ComboBoxEx2.Items.Count-1 do
  begin
    LTComponentsList :=LTComponentsList
      +ComboBoxEx2.Items[LTComponentsListCount];
    //:ComboBoxEx2.Items本身含换行分割符:
    //:这样需加换行分割符:for LTComponent in ComboBoxEx2.Items do
  end;
  TFile.WriteAllText(LFilePath,LTComponentsList);
  //ATComponent :=FindGlobalComponent( AComponentName );
    //:不行:LTClass :=ATComponent.ClassType;

  LContext := TRttiContext.Create;
  try
    LTClass :=FindAnyClass( AComponentName );
      //:不行:ATComponent.ClassType;
    if LTClass=nil then exit;

    LType := LContext.GetType( LTClass );//as TWinControl

    LIfExistTreeNode:=false;
    for LTreeNodeCount := 0 to TreeView1.Items.Count-1 do
      if (TreeView1.Items[LTreeNodeCount].Text =LType.ToString) then
        LIfExistTreeNode:=true; //:已经存在了

    if LIfExistTreeNode=false then //:不要重复加入TreeView1
    begin
      LTreeNode1 := TreeView1.Items.AddChild(nil, LType.ToString);

      LTreeNode2 := TreeView1.Items.AddChild(LTreeNode1, 'Methods');
      for LMethod in LType.GetMethods do
      begin
        TreeView1.Items.AddChild(LTreeNode2, LMethod.ToString);
      end;

      LTreeNode2 := TreeView1.Items.AddChild(LTreeNode1, 'Properties');
      for LProperty in LType.GetProperties do
      begin
        TreeView1.Items.AddChild(LTreeNode2, LProperty.ToString);
      end;

      LTreeNode2 := TreeView1.Items.AddChild(LTreeNode1, 'Fields');
      for LField in LType.GetFields do
      begin
        TreeView1.Items.AddChild(LTreeNode2, LField.ToString);
      end;
    end;
    //TreeView1.FullExpand;
  finally
    LContext.Free;
  end;

end;

end.

program 获取某个TComponent类或TObject类的RttiType信息;

uses
  Vcl.Forms,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.res}

begin
  ReportMemoryLeaksOnShutdown := True; 
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.

运行效果:

 VclTWinControlCategories.text  生成的信息:

三、实际应用中的作用

        我们通常的简单代码,会少了一层,即对象池这一层。造成我们从服务器获取数据库中的信息后,客户端必须将其加载到本地的数据集(比如内存表TFDMemTable),再从内存表中将字段类型读取出来,与UI界面中用户的输入做比对,借此来达到用户输入的类型是否满足要求的目的。

        这是很滞后的、很繁琐的、计算耗时效率较低、数据库服务器负荷重、系统内的对象是不能满足序列化和Json数据交换要求的,也是不好管理的,而且代码不易接口化扩展、不易复用,最终你的模式是很难真正实现Restful的。

1、输入的数据,类型及长度的有效性验证

        从服务器获取对象信息后,客户端UI界面的输入信息,匹配设计要求,而此时客户端并不与数据库产生直接的联系

2、登录信息匹配验证用户注册信息

        方法与上雷同,只是多了与用户注册信息窗口中的相关信息的比对。

3、用户界面配置信息(即皮肤)的个性化、接口化

        可根据用户选择的喜好,实现全系统快速换样式、换颜色、换字体等,而这一切都不需要修改UI部分代码,直接切换参数调用接口单元即可。

4、字段属性为计算字段、查找字段、聚合字段、内部计算字段时,分不同场景取值差异化、算法差异化

FieldKind属性

 

 

猜你喜欢

转载自blog.csdn.net/pulledup/article/details/104769345
今日推荐