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

DELPHI 10.4版本后,有关LivingBind的源代码修改了,原来的适用于10.3.3以前的方法不适用了,再来分析下10.4版本以后适用的方法。

​​​​​​(原创) Delphi中LiveBinding 绑定非数据库类数据的时候显示字段自定义名称(一)_看那山瞧那水的博客-CSDN博客

10.4版本后,部分源代码修改了,删除了接口IScopeMemberDisplayNames,所以原来的方法不能用了。有关显示的部分放到了接口IEditFormatLink里,这个接口声明在System.Classes单元,是个基础接口。(电脑上没有10.3.3以前的版本了,不知道原来有没有这个接口)

通过断断续续的摸索和看源码,总算知道了部分LiveBinding的部分基本流程。其基础是Delphi里早已存在的Observer机制(观察者模式)。我记得在D7里的TObject就有这个Observer。基本版的观察者模式比较简单,但是Delphi的这个就复杂了。支持整个TControl(这里是指FMX框架下)的LiveBinding,单向或双向的绑定。接口众多,文档只说应用,没有更深入的说明,看得是晕头转向。还好只关心如何显示自定义名称,其它先不管(比如绑定表达式等)。

首先先看看是如何绑定数据库并支持显示自定义名称。

绑定数据库的绑定源组件是TBindSourceDB->TCustomBindSourceDB->TBaseLinkingBindSource,就到了基类TBaseLinkingBindSource,TBaseLinkingBindSource是父类TBaseBindScopeComponent的套壳,

TBaseBindScopeComponent = class(TComponent, IScopeComponent, IScopeExpressions, IScopeLocks)

数据库绑定组件是从TCustomBindSourceDB开始的,看看声明:

  TCustomBindSourceDB = class(TBaseLinkingBindSource, IScopeEditLink, IScopeRecordEnumerable, IScopeRecordEnumerableBuffered,
    IScopeNavigator, IScopeActive, IScopeState, IScopeEditor, IScopeMemberNames, IScopeCurrentRecord,
    IScopeMemberScripting, IScopeGetRecord,
    IScopeLookup, IScopeNavigatorUpdates, IScopeBuffer, IScopeLocate, IScopeUnidirectional,
    IScopeRecordControlUpdator)

眼花缭乱了,这里有关显示的接口是 IScopeEditor。

说些题外话:这里当然不能直接看出是和IScopeEditor有关,是不断的调试和摸索,查看我们关心的问题,才知道和这个接口有关,但还只是间接的关系。我们知道数据库字段显示的时候是会显示我们定义的域DisplayName的,FDC是DisplayLabel,我们猜测源码里应当有DisplayName的相关代码,TField有个DisplayName的属性,可以在Data.Bind.DBScope单元里找到相关代码,并且是在IEditFormatLink接口实现里,但是这个接口不在TCustomBindSourceDB 的实现列表里,然后找到IScopeEditor里的方法GetFormatLink()。IScopeEditor的主要功能是管编辑的,IEditFormatLink是管编辑格式的,比如对齐、宽度什么的,然后把显示名称也放在这里了。然后和GRID控件相关的是在Data.Bind.Grid单元里,看到有源码FHeader := LFormatLink.DisplayName; 这里LFormatLink是IEditFormatLink。

回归正题,看看是如何取得TField.DisplayName的。

TCustomBindSourceDB有个字段,FEditFormatLinks: TDictionary<TField, IEditFormatLink>;

其GetFormatLink()方法:

function GetFormatLink(const AFieldName: string): IEditFormatLink;

var
  LField: TField;
begin
  Result := nil;
  if CheckDataSet then
  begin
    LField := FDataSource.DataSet.FindField(AFieldName);
    if Assigned(LField) then
      if not FEditFormatLinks.TryGetValue(LField, Result) then
      begin
        Result := TEditFormatFieldLink.Create(Self, LField) as IEditFormatLink;
        FEditFormatLinks.Add(LField, Result);
      end;
  end;
end;

这里实现了IEditFormatLink的实例,并保存在FEditFormatLinks里。

IEditFormatLink接口的实现:

  TEditFormatFieldLink = class(TInterfacedObject, IEditFormatLink)
  private
    [weak] FOwner: TCustomBindSourceDB;
    FField: TField;
    FCurrency: Boolean;
  protected
    { IEditFormatLink }
    function GetDisplayName: string;
    function GetDisplayWidth: Integer;
    function GetDisplayTextWidth: Integer;
    function GetReadOnly: Boolean;
    function GetVisible: Boolean;
    function GetCurrency: Boolean;
    function GetEditMask: string;
    function GetAlignment: TAlignment;
    function GetMaxLength: Integer;
  public
    constructor Create(AOwner: TCustomBindSourceDB; AField: TField);
  end;

可以看到通过TField可以取得所需信息。

分析源码的时候,还有些其它相关消息,比如Grid是如何自动创建列的并绑定到相应字段,并填充数据内容,设置Grid头(这个就是我们要实现的主要内容)等等。对于GRID这个控件,还有个相关的单元Fmx.Bind.Grid(VCL也有个VCL.Bind.Grid)等等。

明白了我们要实现的核心是如何实现字段的IEditFormatLink接口(数据库是和字段相关,对于一般对象和一般域和属性相关)。注意这里的IEditFormatLink是每个字段一个的。

现在来看看一般对象的绑定。

相关单元Data.Bind.ObjectScope,和TCustomBindSourceDB对应的是TBaseObjectBindSource:

  TBaseObjectBindSource = class(TBaseLinkingBindSource, IScopeEditLink, IScopeRecordEnumerable,
    IScopeNavigator, IScopeState, IScopeEditor, IScopeMemberNames, IScopeCurrentRecord, IScopeActive,
    IScopeMemberScripting, IScopeGetRecord,
    IScopeLookup, IScopeNavigatorUpdates, IScopeLocate,
    IScopeRecordControlUpdator)

我们看到也实现了IScopeEditor接口,也有GetFormatLink()方法,但是这个方法是实现是不同的:

function TBaseObjectBindSource.GetFormatLink(const AFieldName: string): IEditFormatLink;
begin
  Result := nil;
  if CheckAdapter then
    Result := GetInternalAdapter.GetFormatLink(AFieldName);
end;

TBaseObjectBindSource是把GetFormatLink交给了它的适配器。

绑定数据库的时候,TCustomBindSourceDB是直接和DataSet关联的,没有另外的适配器。而绑定一般对象是时候,需要不同的适配器,主要有三种,单个对象适配器,对象列表适配器,快速原型适配器。

TBaseObjectBindSource的GetInternalAdapter只是一个占位方法,直接返回nil,需要子类实现。

适配器的基类是TBindSourceAdapter = class(TComponent, IBindSourceAdapter)

其GetFormatLink()实现:

function TBindSourceAdapter.GetFormatLink(const AMemberName: string): IEditFormatLink;
var
  LField: TBindSourceAdapterField;
begin
  LField := FindField(AMemberName);
  if LField <> nil then
    Result := LField.GetFormatLink
  else
    Result := nil;
end;

交给对象的字段(域)实现。


function TBindSourceAdapterField.GetFormatLink: IEditFormatLink;
begin
  Result := nil;
end;
到这里,就到最终端的实现了,但是默认是返回nil。

我们看看TBindSourceAdapter的几个子类有没有覆盖GetFormatLink,但是发现没有。所以,我们自己实现的各种适配器,需要覆盖这个方法GetFormatLink()。

主要是2个子类的实现,

单个对象绑定适配器:

TObjectBindSourceAdapter<T: class> = class(TBaseObjectBindSourceAdapter)

列表对象绑定适配器:

TListBindSourceAdapter<T: class> = class(TBaseListBindSourceAdapter)

另外一个原型开发用的先不考虑。

关键是如何获得对象各个域(属性)的IEditFormatLink

还是从适配器的AddFields()方法入手。子类适配器里有这个方法:

单个对象的:

procedure TObjectBindSourceAdapter<T>.AddFields;
var
  LType: TRttiType;
  LIntf: IGetMemberObject;
begin
  LType := GetObjectType;
  LIntf := TBindSourceAdapterGetMemberObject.Create(Self);
  AddFieldsToList(LType, Self, Self.Fields, LIntf);
  AddPropertiesToList(LType, Self, Self.Fields, LIntf);
end;

对象列表的:

procedure TListBindSourceAdapter<T>.AddFields;
var
  LType: TRttiType;
  LGetMemberObject: IGetMemberObject;
begin
  LType := GetObjectType;
  LGetMemberObject := TBindSourceAdapterGetMemberObject.Create(Self);
  AddFieldsToList(LType, Self, Self.Fields, LGetMemberObject);
  AddPropertiesToList(LType, Self, Self.Fields, LGetMemberObject);
end;

基本是一样的。

我们自己实现的适配器,可以学数据库的绑定源组件,用一个列表来保存各个字段的IEditFormatLink信息。

代码如下:

unit JkSoft.Bind.ObjectScopeExp;

interface

uses
  System.SysUtils, System.Classes, System.Generics.Collections, System.Rtti, System.TypInfo,
  Data.Bind.ObjectScope, Data.Bind.Components;

type
  TJkBindAttribute = class(TCustomAttribute)

  end;

  //不绑定的Field一般只适用于自动字段或只读字段
  JkNoBindField = class(TJkBindAttribute)

  end;

  //BindFormat
  JkBindFormatField = class(TJkBindAttribute)
  private
    FDisplayName: string;
    FDisplayWidth: Integer;
    FDisplayAlignment: TAlignment;
  public
    constructor Create(const ADisplayName: string; const ADisplayWidth: Integer = -1;
        const ADisplayAlignment: TAlignment = TAlignment.taLeftJustify);
    property DisplayName: string read FDisplayName;
    property DisplayWidth: Integer read FDisplayWidth;
    property DisplayAlignment: TAlignment read FDisplayAlignment;
  end;

  TJkBindDisplayInfo = record
    DisplayName: string;
    DisplayWidth: Integer;
    Alignment: TAlignment;
    IsCurrency: Boolean;
    IsReadOnly: Boolean;
  end;

  TJkBindDisplayInfoList = TObjectDictionary<string, TJkBindDisplayInfo>;

  TJkObjectBindSourceAdapter<T: class> = class(TObjectBindSourceAdapter<T>)
  private
    FBindDisplayInfoList: TJkBindDisplayInfoList;
  protected
    procedure BindDisplayInfoProcess;
    function GetObjectType: TRttiType; override;
    procedure AddFields; override;
    function GetFormatLink(const AFieldName: string): IEditFormatLink; override;
  public
    constructor Create(AOwner: TComponent; AObject: T; AOwnsObject: Boolean = True); override;
    destructor Destroy; override;
  end;

  TJkListBindSourceAdapter<T: class> = class(TListBindSourceAdapter<T>)
  private
    FBindDisplayInfoList: TJkBindDisplayInfoList;
  protected
    procedure BindDisplayInfoProcess;
    function GetObjectType: TRttiType; override;
    procedure AddFields; override;
    function GetFormatLink(const AFieldName: string): IEditFormatLink; override;
  public
    constructor Create(AOwner: TComponent; AList: TList<T>; AOwnsObject: Boolean = True); override;
    destructor Destroy; override;
  end;

//  TJkAdapterBindSource = class(TCustomAdapterBindSource)
//  protected
//
//  end;

  TJkEditFormatFieldLink = class(TInterfacedObject, IEditFormatLink)
  private
    FMemberName: string;
    FBindDisplayInfo: TJkBindDisplayInfo;
  protected
    { IEditFormatLink }
    function GetDisplayName: string;
    function GetDisplayWidth: Integer;
    function GetDisplayTextWidth: Integer;
    function GetReadOnly: Boolean;
    function GetVisible: Boolean;
    function GetCurrency: Boolean;
    function GetEditMask: string;
    function GetAlignment: TAlignment;
    function GetMaxLength: Integer;
  public
    constructor Create(const ABindDisplayInfo: TJkBindDisplayInfo; const AMemberName: string);
    destructor Destroy; override;
  end;

implementation

{ TJkBindFormat }

constructor JkBindFormatField.Create(const ADisplayName: string; const ADisplayWidth: Integer;
  const ADisplayAlignment: TAlignment);
begin
  FDisplayName := ADisplayName;
  FDisplayWidth := ADisplayWidth;
  FDisplayAlignment := ADisplayAlignment;
end;

{ TJkEditFormatFieldLink }

constructor TJkEditFormatFieldLink.Create(const ABindDisplayInfo: TJkBindDisplayInfo;
  const AMemberName: string);
begin
  FBindDisplayInfo := ABindDisplayInfo;
  FMemberName := AMemberName;
end;

destructor TJkEditFormatFieldLink.Destroy;
begin

  inherited;
end;

function TJkEditFormatFieldLink.GetAlignment: TAlignment;
begin
  Result := FBindDisplayInfo.Alignment;
end;

function TJkEditFormatFieldLink.GetCurrency: Boolean;
begin
  Result := FBindDisplayInfo.IsCurrency;
end;

function TJkEditFormatFieldLink.GetDisplayName: string;
begin
  Result := FBindDisplayInfo.DisplayName;
end;

function TJkEditFormatFieldLink.GetDisplayTextWidth: Integer;
begin
  Result := -1;
end;

function TJkEditFormatFieldLink.GetDisplayWidth: Integer;
begin
  Result := -1;
end;

function TJkEditFormatFieldLink.GetEditMask: string;
begin
  Result := '';
end;

function TJkEditFormatFieldLink.GetMaxLength: Integer;
begin
  Result := 0;
end;

function TJkEditFormatFieldLink.GetReadOnly: Boolean;
begin
  Result := FBindDisplayInfo.IsReadOnly;
end;

function TJkEditFormatFieldLink.GetVisible: Boolean;
begin
  Result := True;
end;

{ TJkObjectBindSourceAdapter<T> }

procedure TJkObjectBindSourceAdapter<T>.BindDisplayInfoProcess;
var
  LType: TRttiType;
  LProperties: TArray<TRttiProperty>;
  LAttrs: TArray<TCustomAttribute>;
  LProperty: TRttiProperty;
  Lattr: TCustomAttribute;
  LTypeInfo: PTypeInfo;
  LTypeData: PTypeData;
  LAlign: TAlignment;
  LIsCurrency: Boolean;
  LIsReadOnly: Boolean;
  LNotBindField: TBindSourceAdapterField;
  LInfo: TJkBindDisplayInfo;
begin
  LAlign := TAlignment.taLeftJustify;
  LIsCurrency := False;
  LIsReadOnly := False;
  LType := GetObjectType;
  LProperties := LType.GetProperties;
  for LProperty in LProperties do
  begin
    LTypeInfo := LProperty.PropertyType.Handle;
    if LTypeInfo = nil then
      continue;
    LTypeData := GetTypeData(LTypeInfo);
    LIsReadOnly := not LProperty.IsWritable;
    case LProperty.Visibility of
      TMemberVisibility.mvPublic, TMemberVisibility.mvPublished:
      begin
        {$IFDEF NEXTGEN}
        if (LProperty.Name = 'Disposed') or (LProperty.Name = 'RefCount') then
          continue;
        {$ENDIF}

        case LProperty.PropertyType.TypeKind of
          tkEnumeration: LAlign := TAlignment.taCenter;
          tkInteger, tkInt64: LAlign := TAlignment.taRightJustify;
          tkChar, tkWChar: LAlign := TAlignment.taCenter;
          tkString, tkUString, tkLString: LAlign := TAlignment.taLeftJustify;
          tkFloat:
          begin
            LAlign := TAlignment.taRightJustify;
            LIsCurrency := LTypeData^.FloatType = ftCurr;
//            case LTypeData^.FloatType of
//              ftCurr: LIsCurrency := True;
//            end;
          end;
        else
          LAlign := TAlignment.taLeftJustify;
        end;

        LInfo.DisplayName := LProperty.Name;
        Linfo.DisplayWidth := -1;
        LInfo.Alignment := LAlign;
        LInfo.IsCurrency := LIsCurrency;
        LInfo.IsReadOnly := LIsReadOnly;

        LAttrs := LProperty.GetAttributes;
        for Lattr in LAttrs do
        begin
          if Lattr is JkNoBindField then
          begin
            LNotBindField := Self.FindField(LProperty.Name);
            if (LNotBindField <> nil) and Self.Fields.Contains(LNotBindField) then
              Self.Fields.Remove(LNotBindField);
            if FBindDisplayInfoList.ContainsKey(LProperty.Name) then
              FBindDisplayInfoList.Remove(LProperty.Name);
            Break;
          end
          else if Lattr is JkBindFormatField then
          begin
            if not JkBindFormatField(Lattr).DisplayName.IsEmpty then
              LInfo.DisplayName := JkBindFormatField(Lattr).DisplayName;
            if JkBindFormatField(Lattr).DisplayWidth > 0 then
              Linfo.DisplayWidth := JkBindFormatField(Lattr).DisplayWidth;
            if LInfo.Alignment <> JkBindFormatField(Lattr).DisplayAlignment then
              LInfo.Alignment := JkBindFormatField(Lattr).DisplayAlignment;
          end;
        end;
        FBindDisplayInfoList.AddOrSetValue(LProperty.Name, LInfo);
      end;
    end;
  end;
end;

constructor TJkObjectBindSourceAdapter<T>.Create(AOwner: TComponent; AObject: T;
  AOwnsObject: Boolean);
begin
  inherited;
  FBindDisplayInfoList := TJkBindDisplayInfoList.Create;
end;

destructor TJkObjectBindSourceAdapter<T>.Destroy;
begin
  FBindDisplayInfoList.Free;
  inherited;
end;

procedure TJkObjectBindSourceAdapter<T>.AddFields;
begin
  inherited;
  BindDisplayInfoProcess;
end;

function TJkObjectBindSourceAdapter<T>.GetFormatLink(const AFieldName: string): IEditFormatLink;
var
  LBindDisplayInfo: TJkBindDisplayInfo;
begin
  Result := nil;
  if FBindDisplayInfoList.TryGetValue(AFieldName, LBindDisplayInfo) then
  begin
    Result := TJkEditFormatFieldLink.Create(LBindDisplayInfo, AFieldName);
  end;
end;

function TJkObjectBindSourceAdapter<T>.GetObjectType: TRttiType;
var
  LCtx: TRttiContext;
begin
  inherited;
  Result := LCtx.GetType(TypeInfo(T));
end;

{ TJkListBindSourceAdapter<T> }

procedure TJkListBindSourceAdapter<T>.AddFields;
begin
  inherited;
  BindDisplayInfoProcess;
end;

procedure TJkListBindSourceAdapter<T>.BindDisplayInfoProcess;
var
  LType: TRttiType;
  LProperties: TArray<TRttiProperty>;
  LAttrs: TArray<TCustomAttribute>;
  LProperty: TRttiProperty;
  Lattr: TCustomAttribute;
  LTypeInfo: PTypeInfo;
  LTypeData: PTypeData;
  LAlign: TAlignment;
  LIsCurrency: Boolean;
  LIsReadOnly: Boolean;
  LNotBindField: TBindSourceAdapterField;
  LInfo: TJkBindDisplayInfo;
begin
  LAlign := TAlignment.taLeftJustify;
  LIsCurrency := False;
  LIsReadOnly := False;
  LType := GetObjectType;
  LProperties := LType.GetProperties;
  for LProperty in LProperties do
  begin
    LTypeInfo := LProperty.PropertyType.Handle;
    if LTypeInfo = nil then
      continue;
    LTypeData := GetTypeData(LTypeInfo);
    LIsReadOnly := not LProperty.IsWritable;
    case LProperty.Visibility of
      TMemberVisibility.mvPublic, TMemberVisibility.mvPublished:
      begin
        {$IFDEF NEXTGEN}
        if (LProperty.Name = 'Disposed') or (LProperty.Name = 'RefCount') then
          continue;
        {$ENDIF}

        case LProperty.PropertyType.TypeKind of
          tkEnumeration: LAlign := TAlignment.taCenter;
          tkInteger, tkInt64: LAlign := TAlignment.taRightJustify;
          tkChar, tkWChar: LAlign := TAlignment.taCenter;
          tkString, tkUString, tkLString: LAlign := TAlignment.taLeftJustify;
          tkFloat:
          begin
            LAlign := TAlignment.taRightJustify;
            LIsCurrency := LTypeData^.FloatType = ftCurr;
//            case LTypeData^.FloatType of
//              ftCurr: LIsCurrency := True;
//            end;
          end;
        else
          LAlign := TAlignment.taLeftJustify;
        end;

        LInfo.DisplayName := LProperty.Name;
        Linfo.DisplayWidth := -1;
        LInfo.Alignment := LAlign;
        LInfo.IsCurrency := LIsCurrency;
        LInfo.IsReadOnly := LIsReadOnly;

        LAttrs := LProperty.GetAttributes;
        for Lattr in LAttrs do
        begin
          if Lattr is JkNoBindField then
          begin
            LNotBindField := Self.FindField(LProperty.Name);
            if (LNotBindField <> nil) and Self.Fields.Contains(LNotBindField) then
              Self.Fields.Remove(LNotBindField);
            if FBindDisplayInfoList.ContainsKey(LProperty.Name) then
              FBindDisplayInfoList.Remove(LProperty.Name);
            Break;
          end
          else if Lattr is JkBindFormatField then
          begin
            if not JkBindFormatField(Lattr).DisplayName.IsEmpty then
              LInfo.DisplayName := JkBindFormatField(Lattr).DisplayName;
            if JkBindFormatField(Lattr).DisplayWidth > 0 then
              Linfo.DisplayWidth := JkBindFormatField(Lattr).DisplayWidth;
            if LInfo.Alignment <> JkBindFormatField(Lattr).DisplayAlignment then
              LInfo.Alignment := JkBindFormatField(Lattr).DisplayAlignment;
          end;
        end;
        FBindDisplayInfoList.AddOrSetValue(LProperty.Name, LInfo);
      end;
    end;
  end;
end;

constructor TJkListBindSourceAdapter<T>.Create(AOwner: TComponent; AList: TList<T>;
  AOwnsObject: Boolean);
begin
  //这里要先建列表,因为祖先类的Create()调用了AddFields()!!!
  FBindDisplayInfoList := TJkBindDisplayInfoList.Create;
  inherited;
end;

destructor TJkListBindSourceAdapter<T>.Destroy;
begin
  FBindDisplayInfoList.Free;
  inherited;
end;

function TJkListBindSourceAdapter<T>.GetFormatLink(const AFieldName: string): IEditFormatLink;
var
  LBindDisplayInfo: TJkBindDisplayInfo;
begin
  Result := nil;
  if FBindDisplayInfoList.TryGetValue(AFieldName, LBindDisplayInfo) then
  begin
    Result := TJkEditFormatFieldLink.Create(LBindDisplayInfo, AFieldName);
  end;
end;

function TJkListBindSourceAdapter<T>.GetObjectType: TRttiType;
var
  LCtx: TRttiContext;
begin
  inherited;
  Result := LCtx.GetType(TypeInfo(T));
end;

end.

测试下,可以。但是没有详细测试。。。

测试类定义:

  TTestObj = class
  private
    FName: string;
    FMarriage: Boolean;
    FAge: Integer;
    FContry: string;
    procedure SetAge(const Value: Integer);
    procedure SetMarriage(const Value: Boolean);
    procedure SetName(const Value: string);
  public
    [JkBindFormatField('名称')]
    property Name: string read FName write SetName;
    [JkBindFormatField('年龄', 10, TAlignment.taCenter)]
    property Age: Integer read FAge write SetAge;
    [JkBindFormatField('婚否')]
    property Marriage: Boolean read FMarriage write SetMarriage;
    [JkNoBindField]
    property Contry: string read FContry;
  end;

 

说明:

JkNoBindField用于不显示的属性,比如ID等,最后只用于自动字段和只读字段。

JkBindFormatField包含了三个属性:显示名称,显示宽度,对齐方式。

如果显示名称为空,则显示属性名称。

如果没有指定对齐方式,枚举类型是居中,字符串左对齐,数字和货币右对齐,字符类型居中。

如果没有指定宽度,则是Grid处理中的默认宽度。

  TJkBindDisplayInfo = record
    DisplayName: string;
    DisplayWidth: Integer;
    Alignment: TAlignment;
    IsCurrency: Boolean;
    IsReadOnly: Boolean;
  end;

这里的保存判断是否货币类型,是否只读字段,是IEditFormatLink的要求。

测试例子代码:

unit uBindObject;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  System.Generics.Collections,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, System.Rtti, FMX.Grid.Style,
  Data.Bind.ObjectScope, Data.Bind.Components, FMX.Controls.Presentation, FMX.ScrollBox, FMX.Grid,
  Data.Bind.EngExt, Fmx.Bind.DBEngExt, Fmx.Bind.Grid, System.Bindings.Outputs, Fmx.Bind.Editors,
  Data.Bind.Grid, FMX.StdCtrls, FMX.Edit,
  JkSoft.Bind.ObjectScopeExp;

type
  TTestObj = class
  private
    FName: string;
    FMarriage: Boolean;
    FAge: Integer;
    FContry: string;
    procedure SetAge(const Value: Integer);
    procedure SetMarriage(const Value: Boolean);
    procedure SetName(const Value: string);
  public
    [JkBindFormatField('名称')]
    property Name: string read FName write SetName;
    [JkBindFormatField('年龄', 10, TAlignment.taCenter)]
    property Age: Integer read FAge write SetAge;
    [JkBindFormatField('婚否')]
    property Marriage: Boolean read FMarriage write SetMarriage;
    [JkNoBindField]
    property Contry: string read FContry;
  end;

  TForm2 = class(TForm)
    strgrd1: TStringGrid;
    btn1: TButton;
    edt1: TEdit;
    procedure absCreateAdapter(Sender: TObject; var ABindSourceAdapter: TBindSourceAdapter);
    procedure btn1Click(Sender: TObject);
  private
    FList: TObjectList<TTestObj>;
    FGridLink: TLinkGridToDataSource;
    FAbs: TAdapterBindSource;
  public
    function GetObjectList: TObjectList<TTestObj>;
  end;

var
  Form2: TForm2;

implementation

{$R *.fmx}

{ TTestObj }

procedure TTestObj.SetAge(const Value: Integer);
begin
  FAge := Value;
end;

procedure TTestObj.SetMarriage(const Value: Boolean);
begin
  FMarriage := Value;
end;

procedure TTestObj.SetName(const Value: string);
begin
  FName := Value;
end;

{ TForm2 }

procedure TForm2.absCreateAdapter(Sender: TObject; var ABindSourceAdapter: TBindSourceAdapter);
begin
  ABindSourceAdapter := TJkListBindSourceAdapter<TTestObj>.Create(Self, GetObjectList);
  //ABindSourceAdapter := TListBindSourceAdapter.Create(Self, GetObjectList, TTestObj, true);
end;

procedure TForm2.btn1Click(Sender: TObject);
begin
  if not Assigned(FAbs) then
    FAbs := TAdapterBindSource.Create(Self);
  FAbs.Active := False;
  FAbs.AutoActivate := False;
  FAbs.OnCreateAdapter := absCreateAdapter;

  FGridLink := TLinkGridToDataSource.Create(Self);
  FGridLink.DataSource := FAbs;
  FGridLink.GridControl := strgrd1;

  FAbs.Active := True;
end;

function TForm2.GetObjectList: TObjectList<TTestObj>;
var
  obj: TTestObj;
begin
  Result := TObjectList<TTestObj>.Create();
  for var I := 0 to 9 do
  begin
    obj := TTestObj.Create;
    obj.FContry := 'dongyi' + I.ToString;
    obj.Name := 'Name'+I.ToString;
    obj.Age := 20+I;
    obj.Marriage := I mod 2 = 0;
    Result.Add(obj);
  end;
end;

end.

测试例子简单,就不上传了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值