SmartBinding与kbmMW#1
即将发布的kbmMW,实现了SmartBinding,SmartBinding的设计目标是:
- 必须易于使用
- 必须最小化或完全删除重复的代码。
(你看到这里的趋势了吗?... kbmMW初心就是为了让事情变得简单!) - 必须有良好的表现
- 必须具有较低的CPU占用及内存占用
- 绝不能导致无休止的循环调用
- 应该使用各种数据和控件
- 应该是灵活和可扩展的
- 必须接近实时
- 必须易于重构
- 应该与kbmMW的其他功能很好地配合
- 即使不使用kbmMW的其他功能也应该可用
看一个简单的属性绑定:
简单绑定
Binding.Bind(Edit1,'Text',Label1,'Caption'); Binding.Bind(Edit1,'Text',Button1,'Caption'); Binding.Bind(Edit1,'Text',Edit2,'Text',[mwboTwoWay]);
上面代码,在edit1中输入内容,Label1.Caption,Button1.Caption,Edit2.Text 三个组件显示的内容会跟edit1.text一模一样。第三个是双向绑定,edit2中输入内容,edit1也会更新。
Binding实例来自哪里?他是kbmMW SmartBinding默认带的一个实例,可以立即使用。代码的最后一行还包括一个可选参数mwboTwoWay,表示绑定是同步的,更改一方也会自动更新另一方。
基本上所有字符串,布尔值,浮点,int64和整数属性都可以通过自动转换轻松绑定,其中SmartBinding确保根据需要自动在不同类型之间转换数据。其他类型的数据也可以绑定,但源和目标属性必须是相同的类型(也有方法也可以...见后面)。
线程安全方面是什么情况呢?kbmMW SmartBinding自动识别从TControl类的继承的组件,所以它必须在主线程中执行更新,才保证应用程序稳定运行。
接下来,我们看看Record的绑定:
绑定Record
kbmMW SmartBinding实现绑定到常规对象或者Record,只要确保数据始终可用,那绑定就存在。如果数据不存在了,可以使用kbmMW SmartBinding实现的解绑与重新绑定功能。下面的代码,实现TEdit与全局Record的绑定:
type TData = record FData1:string; end; var data:TData; ... Binding.Bind(Edit1,'Text',@data,TypeInfo(TData),'FData1'); Binding.Bind(@data,TypeInfo(TData),'FData1',EditN,'Text');
现在,Edit1的所有更改会自动填充到数据记录中的FData1字段,同样,对数据记录的FData1字段的所有更改都会自动显示在EditN的Text属性中。
线程安全是什么情况?
上面的例子显然有些简单。但是,由于两个绑定都引用了TControl子类,因此数据记录的轮询和更新需要在主应用程序/ GUI线程中完成,因此,除非您有另一个访问data.FData1的线程,否则这将安全地工作。
如果其他线程中修改记录的FData1字段,那你必须进行常规的线程数据锁定。可以用TkbmMWLock这个工具来帮助解决这个问题。
kbmMW SimpleBinding当然还支持任何组合中的对象实例作为数据源或者接收者。
Record可以绑定了,接下来,再看看
绑定对象列表
下面示例说明了如何绑定对象列表。首先让我们声明一个包含一些数据的列表。
type TLine = class private FName:string; FAddress:string; public constructor Create(const AName:string; const AAddress:string); property Name:string read FName write FName; property Address:string read FAddress write FAddress; end; TLines = TObjectList<TLine>; var lines:TLines; ... lines:=TLines.Create; lines.Add(TLine.Create('Hans','Hansvej 1')); lines.Add(TLine.Create('Jens','Jensvej 1')); lines.Add(TLine.Create('Frederik','Frederikvej 1')); lines.Add(TLine.Create('Jonas','Jonasvej 1'));
现在让我们把lines对象列表与Edit1,Edit2可视控件进行绑定:
var bnd:IkbmMWBinding; begin Binding.Clear; bnd:=Binding.Bind(lines,'Name',Edit1,'Text'); Binding.Bind(lines,'Address',Edit2,'Text'); if bnd.Navigator<>nil then bnd.Navigator.First; end;
在这里,同时介绍了两个新内容:
- 使用Binding.Clear删除所有现有绑定
- 使用bnd.Navigator对列表进行导航。
请记住,如果有全局变量引用IkbmMWBinding,则必须在调用Binding.Clear之前将它们设置为nil 。如果不这样处理,特别是在将数据集作为数据源时(见后文),kbmMW将无法可靠地跟踪绑定之间的共享数据集。
调用Binding.Bind都会返回一个IkbmMWBinding接口,该接口可用于操作该特定绑定。IkbmMWBinding提供一个有用的属性是IkbmMWBindingNavigator类型的Navigator,通过Navigator可以轻松访问可导航的数据源...例如,象lines这样的列表对象。如果绑定的数据源不是列表,则Navigator为nil。
现在,你可以用bnd.Navigator.First/Last/Next/Previous进行导航,同时获取返回的书签。
对于特定来源的所有绑定,Navigator是常见且单一的。不同的可导航数据源具有自己的Navigator实例。
绑定数据集(TDataSet)
SmartBinding实现了可视控件与TDataSet子类的绑定,只要是TDataSet的子类,都可以绑定。现在,让我们通过将biolife.csv加载到TkbmMemTable中来制作数据源。
var mt:TkbmMemTable; csv:TkbmCSVStreamFormat; begin csv:=TkbmCSVStreamFormat.Create(nil); try mt:=TkbmMemTable.Create(nil); mt.LoadFromFileViaFormat('biolife.csv',csv); finally csv.Free; end;
接下来,让我们对Edit控件进行绑定:
Binding.Clear; bnd:=Binding.Bind(mt,'Category',Edit1,'Text',[mwboTwoWay]); Binding.Bind(mt,'Species Name',Edit2,'Text',[mwboTwoWay]); if bnd.Navigator<>nil then bnd.Navigator.First;
与绑定对象列表一样,完全相同的绑定方式。在本例中,使用了双向绑定方式,即mwboTwoWay方式,目的是使Edit1和Edit2与Delphi的数据敏感控件TDBEdit控件的行为相同,可以利用导航器(bnd.Navigator),轻松导航数据集。
现在,在Edit1中输入内容,会自动保存到Category字段中,当mt的当前记录发生变化时,Edit1会显示当前记录的内容。另外,当用代码修改数据集的内容时,同样也会显示到对应有Edit控件中。如执行下面代码:
mt.Edit; mt.FieldByName('Category').AsString:='测试!!!'; mt.Post;
Edit1的内容显示为测试!!!
绑定ListBox或ComboBox
有时候想要使用数据源列表/数据集(或其选定部分)来填充TList或TCombobox(或其子类)。
对于此示例,我们还希望同步控件,即在其中一个控件中进行的选择会自动反映在另一个控件中。
Binding.Clear; Binding.Bind(mt,'Species Name',ComboBox1,'Items'); Binding.Bind(mt,'@',ComboBox1,'ItemIndex',[mwboTwoWay]); bnd:=Binding.Bind(mt,'Common_Name',ListBox2,'Items'); Binding.Bind(mt,'@',ListBox2,'ItemIndex',[mwboTwoWay]); if bnd.Navigator<>nil then bnd.Navigator.First;
首先,我们将数据集mt中的Species Name字段绑定到TCombobox的Items属性,然后将另一个字段Common_Name绑定到TListBox的Items,之后,再使用@作为数据源属性,表示kbmMW SmartBinding引用数据源列表或者数据集的位置。这里,我们将数据源的位置绑定到TListBox和TCombobox的ItemIndex(位置)。此外,我们告诉SmartBinding用两种方式(mwboTwoWay),当更改数据源导航器中的位置不仅会更新TListBox和TCombobox中的位置,同时在控件中选择某些内容也会自动更新源列表/数据集位置,在这种情况下,确保两个控件彼此自动同步确保其来源一致。
绑定到Grid
看下面的代码,我们将数据集mt与Grid的指定列绑定,用@将数据集当前记录位置与Grid的当前行位置绑定,另外还用@将数据集位置与Grid的第一列绑定,同时,还将TEdit绑定到Grid的RowCount属性,所以能在运行期显示变化的Grid行数,即数据集的的记录数。
Binding.Clear; bnd:=Binding.Bind(mt,'Category',StringGrid1,'#1'); Binding.Bind(mt,'Species Name',StringGrid1,'#2'); Binding.Bind(mt,'@',StringGrid1,'@',[mwboTwoWay]);//将数据源与控件的位置进行双向绑定 // 在Grid的第一列显示位置数 Binding.Bind(mt,'@',StringGrid1,'#0'); // Bind to rowcount for easy on the fly change at runtime Binding.Bind(leRowCount,'Text',StringGrid1,'RowCount'); if bnd.Navigator<>nil then bnd.Navigator.First;
它仍然与我们习惯的绑定方式相同,但现在使用了#n语法,将数据集的字段绑定到Grid中的指定列(以0开头)。
使用导航器,Grid现在就像TDBGrid一样。因为我们将数据集的@与Grid的的@绑定到一起了,所以当滚动源数据集,Grid自动更新当前行位置,更改Grid当前选定行也将自动更新数据集的当前记录位置,数据集中当前记录的变化将自动反映在网格中,并且我们双向绑定方式,输入到网格中当前行的数据将反映回数据源。
2019-05-22 后记:笔者基于5.09版本做测试,用上面绑定的方法,在Grid中修改的结果,不会自动更新回数据集,如果用下面一行代码绑定:
bnd:=Binding.Bind(dataset,'Category',StringGrid1,'#1',[mwboTwoWay]);
则Grid中第一行显示不正常,去掉[mwboTwoWay],则正常显示。如下图:
第一行第一个字段没有显示。
绑定匿名函数
不仅对象、数据或数据集可以充当绑定的数据源或目标,匿名函数也可以。
// Show calling function when Edit1.Text is changed. Binding.Bind(Edit1,'Text',function(const AProxy:TkbmMWBindingCustomProxy; var AValue:TValue):boolean begin Log.Debug('Got change from binding to Edit1: '+AValue.ToString); Result:=true; end);
上面的示例基本上可以理解为将匿名函数作为Edit1.OnChange事件处理程序,Edit1.Text的变化将被检测到,自动执行匿名函数。在这个例子中,它使用kbmMW日志框架TkbmMWLog记录变化情况。
下面示例是如何使用匿名函数作为数据源:
Binding.Bind(function(const AProxy:TkbmMWBindingCustomProxy; var AValue:TValue):boolean begin AValue:=Random(100); Result:=true; end,Edit1,'Text');
现在重复调用匿名函数。每次函数返回一个新值时,Edit1.Text都会更新。轮询函数的频率取决于我们用于定义绑定的Binding实例的属性UpdateFrequency的设置。默认频率是每秒10次,但您可以随时更改频率以符合您的喜好。
Binding.UpdateFrequency:=1000;
扩充绑定
如果要定义从一个源获取数值的绑定,并将其输出到标签但格式不同,该怎么办?
您将使用从Binding.Bind函数返回的结果接口上可用的ToDestinationExpression方法。
// Show calling function to populate Edit1.Text and format its look. Binding.Bind(function(const AProxy:TkbmMWBindingCustomProxy; var AValue:TValue):boolean begin AValue:=Random(100); Result:=true; end,Edit1,'Text') .ToDestinationExpression('"Hello "+data');
上面的代码,我们简单地用前面的函数绑定示例,并要求kbmMW SmartBinding根据ToDestinationExpression函数中给出的字符串表达式在目标路径上增加数据。此示例导致Edit1.Text包含值'Hello'和随机数。
因为字符串表达式基于kbmMW在其他地方利用的相同表达式处理功能,所以表达式非常丰富。这些功能源自具有kbmMemTables功能的SQL解析器和评估程序。在这种情况下,我们只支持像表达式部分这样的数学,而不是SQL本身。但是你可以使用你期望能够使用的所有常规操作,包括许多不错的转换,正则表达式,数学,条件评估和更多功能。
由于绑定可以是双向的,因此还需要能够在返回数据源时格式化或者可能格式化的值。为此,SmartBinding也提供了ToSourceExpression函数。
Binding.Bind(Edit1,'Text',Edit2,'Text',[mwboTwoWay]) .ToDestinationExpression('"Hello "+data') .ToSourceExpression('Mid(data,7)');
这个例子抓住Edit1.Text中的内容并将其放入Edit2.Text中,文本“Hello”为前缀。但是,它还可以识别Edit2.Text中所做的更改,并在首先删除它的前6个字符后将该文本移动到Edit1.Text。这种类型的双向绑定通常会使事件驱动的绑定变得疯狂,因为TEdit控件中的更改可能会发生无限的事件触发。但是,kbmMW SmartBinding不受这些事件的影响,并确保以最小的代价进行更新。
启用,禁用,解除绑定和重新绑定
有时您可能希望阻止绑定来完成其工作。如果预防只是暂时的,那么一种方法是禁用它。
var bnd:IkbmMWBinding; begin bnd:=Binding.Bind(....); ... bnd.Disable:=true; ... bnd.Disable:=false; end;
如果您想要永久禁用它,您也可以删除它。为此目的,存在Unbind方法。
Binding.UnbindSource(Edit1);
以上将取消绑定Edit1作为任何绑定的数据源。
Binding.UnbindDestination(Edit2);
以上将取消绑定Edit2作为任何绑定的目标。
您还可以使用调用Bind方法时返回的IkbmMWBinding取消绑定。
var MyBinding:IkbmMWBinding; begin MyBinding:=Binding.Bind(....); ... Binding.Unbind(MyBinding);
如果您未绑定到匿名函数,则还可以使用与绑定完全相同的参数取消绑定
Binding.Bind(Edit1,'Text',Edit2,'Text'); ... Binding.Unbind(Edit1,'Text',Edit2,'Text');
最后你可能想要重新绑定。Rebind基本上可以修改从一个源或目标实例到另一个源或目标实例的绑定。绑定到瞬态记录或对象时特别有趣
Binding.Rebind(@data,@data2);
以上更改了引用记录或内存缓冲区“ 数据 ”的任何绑定,并更新这些绑定以引用记录或内存缓冲区“ data2 ”。
同样,您可以重新绑定控件
Binding.Rebind(Edit1,NewEdit1);
引用Edit1的所有绑定现在将引用NewEdit1。
序幕
您可能已经注意到,运行时绑定的语法是一致且简单的,并且在重构用户界面或控件时可以轻松地重构绑定。
我之前提到过,除了现有的线程安全Binding单例之外,您还可以选择创建自己的绑定管理器实例。这样做的原因可能包括您希望不同的绑定在某种原因的不同时间间隔更新,或者您希望非常容易地访问丢弃或重新创建所有绑定,例如一个简单的框架中的框架,而不会影响定义的所有其他绑定在其他框架中,无需明确解除其中的每一个。
var myBindingMgr:TkbmMWBindings; begin myBindingMgr:=TkbmMWBindings.Create(1000); ... myBindingMgr.Free;
上面的示例创建了另一个绑定管理器,它只会每秒轮询一次。当你不再需要它们时,请记得释放自己创建的绑定管理器。
我脑子里还有很多关于使绑定更容易并添加更多功能的想法,但这将是下一个完整版的kbmMW企业版中包含的beta代码。
如果您喜欢我们的产品和帖子,请与您认识的所有人分享这些帖子!
kbmMW用于简化软件开发的编码,让人专注于业务功能而不是基础代码。为什么?因为我讨厌在开发最终用户代码时做基础工作。所以实际上并没有为你开发所有这些东西,而是为了我自己使用,这很自私与自我,但希望你也会喜欢它。
哦..那个特色图片的含义是什么?
嗯..它可以解释很多方式......这里有一些
- Smartbinding为那些从活动的悬崖上掉下来的人们进行救援
- 绑定时,您需要确保绑定(系绳)是安全的,不会导致危险的问题
- 做出自己的解释
https://components4developers.blog/2019/04/25/smartbinding-with-kbmmw-1/
2019-05-22 译者注:当前5.09版本已经发布,但SmartBinding不支持FMX ListView控件,这让我感到非常遗憾,因为我的app使用了大量的ListView。急盼作者能考虑在下一版本中实现。
后记:在5.10.20版本中,作者完美实现了!