• (原创) Delphi中LiveBinding 绑定非数据库类数据的时候显示字段自定义名称


    DELPHI默认下,Grid控件通过TAdapterBindSource绑定到一个TObjectList<TObject>列表时,Grid的Header显示的是TObject的属性名称。不能像绑定数据集时显示自定义名称,我们来看看其是怎么实现的。

    Grid是如何绑定数据的,通过Grid的绑定类TLinkGridToDataSource,追踪到单元Data.Bind.Grid.pas。TCustomLinkGridToDataSource本身没什么相关的方法,其父类是TCustomLinkGridToDataSource,TCustomLinkGridToDataSource里发现了相关方法GetMemberDisplayName(),是个override方法,继承自TBaseLinkGridToDataSource,在TBaseLinkGridToDataSource中,GetMemberDisplayName()只是占位。真正实现是TCustomLinkGridToDataSource的GetMemberDisplayName(),代码:

    function TCustomLinkGridToDataSource.GetMemberDisplayName(const AMemberName: string; out ADisplayName: string): Boolean;
    var
      LScopeMemberDisplayNames: IScopeMemberDisplayNames;
    begin
      Result := Supports(GetDataSource, IScopeMemberDisplayNames, LScopeMemberDisplayNames);
      if Result then
        Result := LScopeMemberDisplayNames.GetMemberDisplayName(AMemberName, ADisplayName);
    end;

    这里可以看到,TCustomLinkGridToDataSource只是判断中转下,真正的实现取决于其DataSource属性,只有其DataSource支持了IScopeMemberDisplayNames接口并实现了GetMemberDisplayName()方法,才能实现这个功能。这个DataSource属性是什么呢?

    GetDataSource() 是在TBaseLinkGridToDataSource实现的

    function TBaseLinkGridToDataSource.GetDataSource: TBaseLinkingBindSource;
    begin
      Result := DataSource;
    end;

    直接返回祖先类的DataSource属性,DataSource属性在基类TBaseLinkToDataSource实现,类型是:TBaseLinkingBindSource。

    TBaseLinkingBindSource在单元Data.Bind.Components.pas声明,是所有绑定源的基类:

      TBaseLinkingBindSource = class(TBaseBindScopeComponent)
      end;

    只是其父类TBaseBindScopeComponent的别名。

    到这里,我们可以从另一面着手追踪,通过绑定数据集的控件TBindSourceDB和绑定非数据集TAdapterBindSource来追踪,最后都能追踪到TBaseLinkingBindSource。通过查看源码,我们可以看到TBaseLinkingBindSource的2个子类,TBaseObjectBindSource是所有非数据集控件的绑定源基类,TCustomBindSourceDB是数据集绑定控件的基类。

    TAdapterBindSource的基类是TBaseObjectBindSource,在单元Data.Bind.ObjectScope.pas; 

    TBindSourceDB的基类是TCustomBindSourceDB,在单元Data.Bind.DBScope.pas; 

    TBaseObjectBindSource和TCustomBindSourceDB继承自TBaseLinkingBindSource,TBaseLinkingBindSource是TBaseBindScopeComponent别名,在单元Data.Bind.Components.pas。

    IScopeMemberDisplayNames接口也在单元Data.Bind.Components.pas。

    源代码:

      IScopeMemberDisplayNames = interface
      ['{B02AADEE-2F39-4A26-A17C-5A7B391647FD}']
        function GetMemberDisplayName(const AMemberName: string; out ADisplayName: string): Boolean;
      end;
      TBaseObjectBindSource = class(TBaseLinkingBindSource, IScopeEditLink, IScopeRecordEnumerable,
        IScopeNavigator, IScopeState, IScopeEditor, IScopeMemberNames, IScopeCurrentRecord, IScopeActive,
        IScopeMemberScripting, IScopeGetRecord,
        IScopeLookup, IScopeNavigatorUpdates, IScopeLocate)
      TCustomBindSourceDB = class(TBaseLinkingBindSource, IScopeEditLink, IScopeRecordEnumerable, IScopeRecordEnumerableBuffered,
        IScopeNavigator, IScopeActive, IScopeState, IScopeEditor, IScopeMemberNames, IScopeCurrentRecord,
        IScopeMemberScripting, IScopeGetRecord,
        IScopeLookup, IScopeNavigatorUpdates, IScopeBuffer, IScopeLocate, IScopeUnidirectional,
        IScopeMemberDisplayNames)

    可以看到,TCustomBindSourceDB实现了接口IScopeMemberDisplayNames,而TBaseObjectBindSource没有。

    到这里,我们基本明白了,绑定数据集时,为什么可以显示自定义名称了:一个原因是数据集域控件本身支持属性DisplayName(TField有个属性DisplayName,FD的控件里叫DiplayLable对应DB.Field里的DisplayName);另一个原因是TBindSourceDB直接支持接口IScopeMemberDisplayNames。

    TCustomBindSourceDB的GetMemberDisplayName()实现源码:

    function TCustomBindSourceDB.GetMemberDisplayName(const AMemberName: string; out ADisplayName: string): Boolean;
    var
      LField: TField;
    begin
      Result := False;
      if FDataSource.DataSet <> nil then
      begin
        if FDataSource.DataSet.Fields.Count > 0 then
        begin
          LField := DataSource.DataSet.FindField(AMemberName);
          if LField <> nil then
          begin
            if LField.DisplayName <> '' then
            begin
              Result := True;
              ADisplayName := LField.DisplayName;
            end;
          end;
        end;
        if (not Result) and (DataSource.DataSet.AggFields.Count > 0) then
        begin
          LField := DataSource.DataSet.AggFields.FindField(AMemberName);
          if LField.DisplayName <> '' then
          begin
            Result := True;
            ADisplayName := LField.DisplayName;
          end;
        end;
      end;
    end;

    就是通过数据域名 (AMemberName)找到域,然后获取DisplayName。

    那么,对于非数据集数据,我们可以模拟此实现。比如当我们用ORM来操作数据库时,一般GRID绑定的数据就是 TObjectList<TEntityObject>列表。要从两个地方进行修改:

    一是修改TBaseObjectBindSource,实现IScopeMemberDisplayNames;二是修改TEntityObject,对于需要自定义显示名的字段定义DisplayName。下面分别讨论。

    一、修改TBaseObjectBindSource,实现IScopeMemberDisplayNames。

    前面说过TBaseObjectBindSource是所有绑定非数据集数据的绑定源基类,研究源码,其继承关系是TAdapterBindSource->TCustomAdapterBindSource->TBaseObjectBindSource;另一个是TPrototypeBindSource->TCustomPrototypeBindSource->TBaseObjectBindSource,比较适合改造的肯定是TCustomAdapterBindSource和TCustomPrototypeBindSource了。先不管TCustomPrototypeBindSource,先看一般的绑定源类TCustomAdapterBindSource,我们先定义一个类:

    TJkAdapterBindSource = class(TCustomAdapterBindSource, IScopeMemberDisplayNames)
    public
        function GetMemberDisplayName(const AMemberName: string; out ADisplayName: string): Boolean;
    end;

    现在就是如何实现这个GetMemberDisplayName()了,但是发现我们无法参考TCustomBindSourceDB类的GetMemberDisplayName()实现,为什么呢?主要是因为TCustomBindSourceDB类直接包含了一个FDataSource: TDataSource字段,所以TCustomBindSourceDB类可以通过这个FDataSource来获取数据域信息。但是TBaseObjectBindSource类是没有这个属性的,联想下我们平常的使用,TCustomBindSourceDB类可以直接设置DataSource属性,TBaseObjectBindSource的子类只能是设置一个Adapter属性或者是通过OnCreateAdapter事件来获取一个Adapter,就是说,绑定到非数据集的绑定源控件,是要通过一个Adapter来绑定到非数据集数据的(Adapter就相当于TDataSource,TDataSource就是绑定数据集控件的Adapter,TDataSource只要一个,但是Adapter要有多种才能适配不同的非数据集数据)。通过研究源码,发现TBaseObjectBindSource关于数据的信息都是通过Adapter来获取的,比如GetMemberNames的实现:

    procedure TBaseObjectBindSource.GetMemberNames(AList: TStrings);
    begin
      if CheckAdapter then
        GetInternalAdapter.GetMemberNames(AList)
    end;

    确实是,也应当是,这才是Adapter的功能。GetInternalAdapter()的代码:

    function TBaseObjectBindSource.GetInternalAdapter: TBindSourceAdapter;
    begin
      Result := nil;
    end;

    基类没有实现,看子类实现:

    function TCustomAdapterBindSource.GetInternalAdapter: TBindSourceAdapter;
    begin
      if CheckRuntimeAdapter then
        Result := GetRuntimeAdapter
      else
        Result := FAdapter;
      if Result <> nil then
        ConnectAdapter(Result);
    end;

    这里为什么要通过GetInternalAdapter()方法来获取Adapter,而不是直接用属性Adapter,TBaseObjectBindSource类的代码中有提示:

      /// <remarks>Adapter my be provided by setting a property or by implementing
      ///  the OnCreateAdapter event</remarks>
      TBaseObjectBindSource = class(TBaseLinkingBindSource, IScopeEditLink, IScopeRecordEnumerable,
        IScopeNavigator, IScopeState, IScopeEditor, IScopeMemberNames, IScopeCurrentRecord, IScopeActive,
        IScopeMemberScripting, IScopeGetRecord,
        IScopeLookup, IScopeNavigatorUpdates, IScopeLocate)

    就是说,TBaseObjectBindSource的Adapter可以通过属性Adapter设置,也可以通过OnCreateAdapter()事件运行时生成(通过CheckRuntimeAdapter()、SetRuntimeAdapter()和GetRuntimeAdapter()等)

    function TBaseObjectBindSource.CheckRuntimeAdapter: Boolean;
    var
      LAdapter: TBindSourceAdapter;
    begin
      if FCheckRuntimeAdapter and (FRuntimeAdapter = nil) and not (csDestroying in ComponentState) then
      begin
        FCheckRuntimeAdapter := False;
        Self.DoCreateAdapter(LAdapter);
        if LAdapter <> nil then
          SetRuntimeAdapter(LAdapter);
      end;
      Result := FRuntimeAdapter <> nil;
    end;
    
    procedure TBaseObjectBindSource.SetRuntimeAdapter(AAdapter: TBindSourceAdapter);
    begin
      SetInternalAdapter(AAdapter,
        procedure(AScope: TBindSourceAdapter)
        begin
          if FRuntimeAdapter <> nil then
          begin
            if not (csDestroying in FRuntimeAdapter.ComponentState) then
              FreeAndNil(FRuntimeAdapter);
            if (AAdapter = nil) and not (csDestroying in ComponentState) then
              // Recheck
              FCheckRuntimeAdapter := True;
          end;
          FRuntimeAdapter := AAdapter;
          if FRuntimeAdapter <> nil then
          begin
            FRuntimeAdapter.FreeNotification(Self);
          end;
        end);
    end;
    
    function TBaseObjectBindSource.GetRuntimeAdapter: TBindSourceAdapter;
    begin
      Result := FRuntimeAdapter;
    end;
    
    function TCustomAdapterBindSource.GetInternalAdapter: TBindSourceAdapter;
    begin
      if CheckRuntimeAdapter then
        Result := GetRuntimeAdapter
      else
        Result := FAdapter;
      if Result <> nil then
        ConnectAdapter(Result);
    end;

    所以在获取实际运用的Adapter时,不能通过Adapter,而是用GetInternalAdapter()方法。

    现在知道了,具体的GetMemberDisplayName()实现,还是在Adapter里。也就是TBindSourceAdapter类。

    TBindSourceAdapter是Adapter的基类

        /// <summary>Adapter base class for providing data to a TAdapterBindScope</summary>
      TBindSourceAdapter = class(TComponent, IBindSourceAdapter)

    因为其是基类,不能在这里修改,这里修改了,相当于我们要替换掉单元Data.Bind.ObjectScope.pas,这比较麻烦,也不可取。那只能在其子类想办法了。我们实际中使用的子类主要是:TObjectBindSourceAdapter,TListBindSourceAdapter,TDataGeneratorAdapter等,TObjectBindSourceAdapter适配单个对象,TListBindSourceAdapter适用与对象列表,TDataGeneratorAdapter适用于临时产生数据。为了简单,可以以这些子类来修改。另外实现的这写子类都实现了IScopeMemberDisplayNames接口,并添加了一个私有域FDisplayNameList: TDictionary<string, string>,用于保存属性和显示名称对应值。 然后在Adapter的AddFields()方法里添加属性名称和显示名称对应值,具体的修改策略和TEntityObject如何实现DisplayName有关,这里以其中的一种策略来说明。

    unit JkSoft.Bind.Utils;
    
    interface
    
    type
      TJkBindAttribute = class(TCustomAttribute)
    
      end;
    
      JkBindDisplayName = class(TJkBindAttribute)
      private
        FName: string;
      public
        constructor Create(const AName: string);
    
        property Name: string read FName;
      end;
    
    implementation
    
    { JkBindDisplayName }
    
    constructor JkBindDisplayName.Create(const AName: string);
    begin
      FName := AName;
    end;
    
    end.
    unit JkSoft.Bind.ObjectScope;
    
    interface
    
    uses
      System.SysUtils, System.Classes, System.Generics.Collections, System.Rtti, System.TypInfo,
      Data.Bind.ObjectScope, Data.Bind.Components;
    
    type
      TJkObjectBindSourceAdapter<T: class> = class(TObjectBindSourceAdapter<T>, IScopeMemberDisplayNames)
      private
        FDisplayNameList: TDictionary<string, string>;
      protected
        procedure AddFields; override;
        procedure CreateList;
      public
        constructor Create(AOwner: TComponent; AObject: T; AOwnsObject: Boolean = True); override;
        destructor Destroy; override;
    
        function GetMemberDisplayName(const AMemberName: string; out ADisplayName: string): Boolean;
      end;
    
      TJkListBindSourceAdapter<T: class> = class(TListBindSourceAdapter<T>, IScopeMemberDisplayNames)
      private
        FDisplayNameList: TDictionary<string, string>;
      protected
        procedure AddFields; override;
        procedure CreateList;
      public
        constructor Create(AOwner: TComponent; AList: TList<T>; AOwnsObject: Boolean = True); override;
        destructor Destroy; override;
    
        function GetMemberDisplayName(const AMemberName: string; out ADisplayName: string): Boolean;
      end;
    
      TJkAdapterBindSource = class(TCustomAdapterBindSource, IScopeMemberDisplayNames)
      public
        function GetMemberDisplayName(const AMemberName: string; out ADisplayName: string): Boolean;
      end;
    
    implementation
    
    { TJkObjectBindSourceAdapter<T> }
    
    uses
      JkSoft.Bind.Utils;
    
    procedure TJkObjectBindSourceAdapter<T>.AddFields;
    var
      LType: TRttiType;
      LFields: TArray<TRttiField>;
      LField: TRttiField;
      LProps: TArray<TRttiProperty>;
      LProp: TRttiProperty;
      LAttrs: TArray<TCustomAttribute>;
      Lattr: TCustomAttribute;
    begin
      inherited;
    
      CreateList;
    
      LType := GetObjectType;
    
    // 规定要显示的数据域全部是Published的属性,则不用检索Field
    //  LFields := LType.GetFields;
    //  for LField in LFields do
    //  begin
    //    if LField.Visibility = TMemberVisibility.mvPublished then
    //    begin
    //      LAttrs := LField.GetAttributes;
    //      for Lattr in LAttrs do
    //      begin
    //        if (Lattr is JkBindDisplayName) and (not JkBindDisplayName(Lattr).Name.IsEmpty) then
    //        begin
    //          FDisplayNameList.AddOrSetValue(LField.Name, JkBindDisplayName(Lattr).Name);
    //        end
    //        else if FDisplayNameList.ContainsKey(LField.Name) then
    //        begin
    //          FDisplayNameList.Remove(LField.Name);
    //        end;
    //      end;
    //    end;
    //  end;
    //
    //  SetLength(LAttrs, 0);
    
      LProps := LType.GetProperties;
      for LProp in LProps do
      begin
        if LProp.Visibility = TMemberVisibility.mvPublished then
        begin
          LAttrs := LProp.GetAttributes;
          for Lattr in LAttrs do
          begin
            if (Lattr is JkBindDisplayName) and (not JkBindDisplayName(Lattr).Name.IsEmpty) then
            begin
              FDisplayNameList.AddOrSetValue(LProp.Name, JkBindDisplayName(Lattr).Name);
            end
            else if FDisplayNameList.ContainsKey(LProp.Name) then
            begin
              FDisplayNameList.Remove(LProp.Name);
            end;
          end;
        end;
      end;
    end;
    
    constructor TJkObjectBindSourceAdapter<T>.Create(AOwner: TComponent; AObject: T; AOwnsObject: Boolean);
    begin
      inherited;
      //FDisplayNameList := TDictionary<string, string>.Create;
    end;
    
    procedure TJkObjectBindSourceAdapter<T>.CreateList;
    begin
      if not Assigned(FDisplayNameList) then
        FDisplayNameList := TDictionary<string, string>.Create;
    end;
    
    destructor TJkObjectBindSourceAdapter<T>.Destroy;
    begin
      if Assigned(FDisplayNameList) then
        FDisplayNameList.Free;
      inherited;
    end;
    
    function TJkObjectBindSourceAdapter<T>.GetMemberDisplayName(const AMemberName: string;
      out ADisplayName: string): Boolean;
    begin
      Result := False;
      if FDisplayNameList.ContainsKey(AMemberName) and (not FDisplayNameList[AMemberName].IsEmpty) then
      begin
        Result := True;
        ADisplayName := FDisplayNameList[AMemberName];
      end;
    end;
    
    { TJkListBindSourceAdapter<T> }
    
    procedure TJkListBindSourceAdapter<T>.AddFields;
    var
      LType: TRttiType;
      //LFields: TArray<TRttiField>;
      //LField: TRttiField;
      LProps: TArray<TRttiProperty>;
      LProp: TRttiProperty;
      LAttrs: TArray<TCustomAttribute>;
      Lattr: TCustomAttribute;
    begin
      inherited;
    
      CreateList;
    
      LType := GetObjectType;
    
    // 规定要显示的数据域全部是Published的属性,则不用检索Field
    //  LFields := LType.GetFields;
    //  for LField in LFields do
    //  begin
    //    if LField.Visibility = TMemberVisibility.mvPublished then
    //    begin
    //      LAttrs := LField.GetAttributes;
    //      for Lattr in LAttrs do
    //      begin
    //        if (Lattr is JkBindDisplayName) and (not JkBindDisplayName(Lattr).Name.IsEmpty) then
    //        begin
    //          FDisplayNameList.AddOrSetValue(LField.Name, JkBindDisplayName(Lattr).Name);
    //        end
    //        else if FDisplayNameList.ContainsKey(LField.Name) then
    //        begin
    //          FDisplayNameList.Remove(LField.Name);
    //        end;
    //      end;
    //    end;
    //  end;
    //
    //  SetLength(LAttrs, 0);
    
      LProps := LType.GetProperties;
      for LProp in LProps do
      begin
        if LProp.Visibility = TMemberVisibility.mvPublished then
        begin
          LAttrs := LProp.GetAttributes;
          for Lattr in LAttrs do
          begin
            if (Lattr is JkBindDisplayName) and (not JkBindDisplayName(Lattr).Name.IsEmpty) then
            begin
              FDisplayNameList.AddOrSetValue(LProp.Name, JkBindDisplayName(Lattr).Name);
            end
            else if FDisplayNameList.ContainsKey(LProp.Name) then
            begin
              FDisplayNameList.Remove(LProp.Name);
            end;
          end;
        end;
      end;
    end;
    
    constructor TJkListBindSourceAdapter<T>.Create(AOwner: TComponent; AList: TList<T>; AOwnsObject: Boolean);
    begin
      inherited;
      //FDisplayNameList := TDictionary<string, string>.Create;
    end;
    
    procedure TJkListBindSourceAdapter<T>.CreateList;
    begin
      if not Assigned(FDisplayNameList) then
        FDisplayNameList := TDictionary<string, string>.Create;
    end;
    
    destructor TJkListBindSourceAdapter<T>.Destroy;
    begin
      if Assigned(FDisplayNameList) then
        FDisplayNameList.Free;
      inherited;
    end;
    
    function TJkListBindSourceAdapter<T>.GetMemberDisplayName(const AMemberName: string; out ADisplayName: string): Boolean;
    begin
      Result := False;
      if FDisplayNameList.ContainsKey(AMemberName) and (not FDisplayNameList[AMemberName].IsEmpty) then
      begin
        Result := True;
        ADisplayName := FDisplayNameList[AMemberName];
      end;
    end;
    
    { TJkAdapterBindSource }
    
    function TJkAdapterBindSource.GetMemberDisplayName(const AMemberName: string; out ADisplayName: string): Boolean;
    var
      adpInf: IScopeMemberDisplayNames;
      adpInternal: TBindSourceAdapter;
    begin
      Result := False;
      adpInternal := GetInternalAdapter;
      if Assigned(adpInternal) then
      begin
        if Supports(adpInternal, IScopeMemberDisplayNames, adpInf) then
        begin
          Result := adpInf.GetMemberDisplayName(AMemberName, ADisplayName);
        end;
      end;
    end;
    
    end.

    这个实现是因为TEntityObject类的自定义显示名采用了TAttritube来实现。如果TEntityObject类的自定义显示名采用了其它的实现方法,则修改Adapter的AddFields()方法。

    二、修改TEntityObject,对于需要自定义显示名的字段定义DisplayName。

    TEntityObject的实现自定义显示名称的方法主要考虑下面两种策略:

    1、固定设置显示名称

    通过TAttritube实现,这种方法比较简单,缺点是编译时就固定了显示名称,运行时无法修改。比如:

      TPerson = class
      private
        FName: string;
        FAge: Integer;
        FBirthDay: TDateTime;
        procedure SetAge(const Value: Integer);
        procedure SetBirthDay(const Value: TDateTime);
        procedure SetName(const Value: string);
      public
    
      published
        [JkBindDisplayName('姓名')]
        property Name: string read FName write SetName;
        [JkBindDisplayName('年龄')]
        property Age: Integer read FAge write SetAge;
        property BirthDay: TDateTime read FBirthDay write SetBirthDay;
      end;

    2、动态设置显示名称

    创建一个TEntityObject的基类TEntityBase,这个基类负责设置属性显示名称对应表

    type
      //动态设置显示名称
      TEntityBase = class(TPersistent)
      private
        class var FDisplayNameList: TDictionary<string, string>;
        class var FPropNameList: TStrings;
        class destructor UnInitialize;
      protected
        constructor Create; virtual;
        destructor Destroy; override;
    
      public
        class function GetPropNameList: TStrings;
        class function GetDisplayNameList: TDictionary<string, string>;
        class procedure SetDisplayName(const APropName, ADisplayName: string); virtual;
        class procedure SetDisplayNames(const ADisplayNames: TDictionary<string, string>); virtual;
      end;
    
      TPeople = class(TEntityBase)
      private
        FName: string;
        FAge: Integer;
        FBirthDay: TDateTime;
        procedure SetAge(const Value: Integer);
        procedure SetBirthDay(const Value: TDateTime);
        procedure SetName(const Value: string);
      published
        property Name: string read FName write SetName;
        property Age: Integer read FAge write SetAge;
        property BirthDay: TDateTime read FBirthDay write SetBirthDay;
      end;
    
    implementation
    
    uses
      System.TypInfo;
    
    { TEntityBase }
    
    constructor TEntityBase.Create;
    begin
    
    end;
    
    destructor TEntityBase.Destroy;
    begin
      inherited;
    end;
    
    class function TEntityBase.GetDisplayNameList: TDictionary<string, string>;
    begin
      Result := FDisplayNameList;
    end;
    
    class function TEntityBase.GetPropNameList: TStrings;
    var
      PropList: PPropList;
      Count: Integer;
      i: Integer;
    begin
      if not Assigned(FPropNameList) then
      begin
        FPropNameList := TStringList.Create;
        Count := GetPropList(Self.ClassInfo, PropList);
        if Count > 0 then
        begin
          for i := 0 to Count-1 do
          begin
            FPropNameList.Add(PropList[i].Name);
          end;
        end;
      end;
      Result := FPropNameList;
    end;
    
    class procedure TEntityBase.SetDisplayName(const APropName, ADisplayName: string);
    begin
      if not Assigned(FDisplayNameList) then
        FDisplayNameList := TDictionary<string, string>.Create;
      FDisplayNameList.AddOrSetValue(APropName, ADisplayName);
    end;
    
    class procedure TEntityBase.SetDisplayNames(const ADisplayNames: TDictionary<string, string>);
    begin
      if Assigned(ADisplayNames) and (ADisplayNames.Count > 0) then
      begin
        if Assigned(ADisplayNames) then
          FreeAndNil(FDisplayNameList);
        FDisplayNameList := TDictionary<string, string>.Create(ADisplayNames);
      end;
    end;
    
    class destructor TEntityBase.UnInitialize;
    begin
      if Assigned(FPropNameList) then
        FPropNameList.Free;
      if Assigned(FDisplayNameList) then
        FDisplayNameList.Free;
    end;
    
    { TPeople }
    
    procedure TPeople.SetAge(const Value: Integer);
    begin
      FAge := Value;
    end;
    
    procedure TPeople.SetBirthDay(const Value: TDateTime);
    begin
      FBirthDay := Value;
    end;
    
    procedure TPeople.SetName(const Value: string);
    begin
      FName := Value;
    end;
    
    initialization
    
    finalization
    
    end.

    这种方法,则对应的Adapter的AddFields方法要相应修改。

    procedure TJkObjectBindSourceAdapter<T>.AddFields;
    var
      LType: TRttiType;
      MProp: TRttiMethod;
      MNames: TRttiMethod;
      Value: TValue;
    begin
      inherited;
    
      LType := GetObjectType;
      MNames := LType.GetMethod(MethodGetDisplayNameList);
      if MNames <> nil then
      begin
        Value := MNames.Invoke(LType.AsInstance.MetaclassType, []);
        if (not Value.IsEmpty) and Value.IsType<TDictionary<string, string>> then
        begin
          if Assigned(FDisplayNameList) then
            FreeAndNil(FDisplayNameList);
          FDisplayNameList.Create(Value.AsType<TDictionary<string, string>>);
        end;
      end;
    end;

    =================================================================================================================

    另外:如果要自定义这个Header显示的名称,简单可以通过Grid的 OnDrawColumnHeader事件来定制,比如要修改"ID“列:

    procedure TForm1.grd1DrawColumnHeader(Sender: TObject; const Canvas: TCanvas; const Column: TColumn;
      const Bounds: TRectF);
    begin
      if Column.Header = 'ID' then
        Column.Header := 'ID码';
    end;
  • 相关阅读:
    端口查看netstat -tunpl |grep 25
    解释一下查找出文件并删除find /var/log -type f -mtime +7 -ok rm {} ;
    2021.6.2
    2021.6.1
    2021.5.31
    2021.5.30(每周总结)
    2021.5.28
    2021.5.27
    2021.5.26
    2021.5.25
  • 原文地址:https://www.cnblogs.com/jankerxp/p/13584245.html
Copyright © 2020-2023  润新知