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.
测试例子简单,就不上传了。