(原创) 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;

 

posted @ 2020-08-30 02:41  舞天涯  阅读(387)  评论(0编辑  收藏  举报