DELPHI 10.4版本后,有关LivingBind的源代码修改了,原来的适用于10.3.3以前的方法不适用了,再来分析下10.4版本以后适用的方法。
(原创) Delphi中LiveBinding 绑定非数据库类数据的时候显示字段自定义名称(一)_看那山瞧那水的博客-优快云博客
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 := GetInternalAdapte