LiveBindings 绑定界面元素和存储数据的对象

Delphi 的 LiveBindings 的例子,绑定数据库控件 TDataSet 的比较多。对于 TDataSet 来说,用 LiveBindings 框架,只是可以让更多的非 TDBEdit ... 这样的数据敏感控件,可以用来显示数据。

对于 FireMonkey 来说,已经没有了数据敏感控件,要绑定数据库,只能使用 LiveBindings 框架了。

除了数据库,如果数据源是对象,该怎么个做法?我之前写过一篇,和现在网上能够搜到的多数文章一样,讲了简单的做法,但并没有讲透。尤其是,在实际使用中,该怎么做?如果纯按网上的例子来做,无法把界面和数据分开。

把界面和数据分开,有很多模式。其中一个是所谓的 MVVM 模式。什么是 MVVM 模式,在 DELPHI 里面怎么用它,请参考:

https://blog.grijjy.com/2018/01/22/mvvm-starter-kit-part-1-of-3/

什么是 MVVM?

研究过上面那篇文章及其代码,我的总结是:

1. MVVM 是所谓的 Model -- ViewModel -- View。这里的 Model 是指数据模块;ViewModel 是指包含数据显示逻辑的代码模块;View 才是真正的显示界面。这样分开的好处是,降低了代码的耦合程度。显示逻辑和显示界面分开,显示界面纯显示。数据及数据逻辑和显示逻辑分开。对于真正的大型代码,这样分开是有明显的好处的。

2. 研究了上述文章的代码,我发现它的实现虽然看起来挺复杂,归纳起来,和我们通常做 Delphi 的数据库软件类似:把数据也就是 TDataSet 放在一个 DataModule 里面,而显示界面是另外一个或多个单元。每个显示界面的界面元素 TDBEdit, TDBLabel 等等通过 TDataSource 和 DataModule 里面的 TDataSet 连接,实现双向通讯的功能。所谓双向通讯就是,

2.1. 界面上用户修改了 TDBEdit 的值,会自动反馈到 TDataSet 里面去,也会同时反应到其它界面上的其它 TDBGrid 等等绑定到对应字段的界面元素。

2.2. 如果用代码直接修改了 TDataSet 里面的数据,界面上的元素的显示会自动跟随变化。

符合 MVVM 模式的对象做 LiveBinding 到界面

那么,如果数据源不是 TDataSet 而是普通对象,该怎么做?

假设有一个 TUser 类,用它来存放一些数据,比如 ID, Age, FullName 等等,都是它的属性(public 就可以,无需 publish)。

1. 创建一个 DataModule,在这个 DataModule 里面,定义一个 FUser: TUser;这个 DataModule 就是 MVVM 里面的 Model,

2. 在这个 DataModule 里面,拖一个 AdapterBindSource1 进去,再拖一个 DataGeneratorAdapter1 进去。

3. 右键点 DataGeneratorAdapter1 下拉菜单选 Fields Editor, 给 DataGeneratorAdapter1 创建几个字段,对应 TUser 的属性。字段名要和属性名相同。同时设置前述的 AdapterBindSource1 的 Adapter 属性为这个 DataGeneratorAdapter1。这个 DataGeneratorAdapter1 纯粹是用于设计期可视化建立绑定连接用。运行期它会被替换掉。替换的代码请看:

procedure TDataModule2.AdapterBindSource1CreateAdapter(Sender: TObject;
  var ABindSourceAdapter: TBindSourceAdapter);
begin
  if not Assigned(FUser) then
  begin
    FUser := TUser.Create(Self);
    FUser.Numb := 666;
    FUser.UserID := 'pcplayer';
    FUser.FullName := 'abcdefg';
  end;


  ABindSourceAdapter := TObjectBindSourceAdapter<TNLUserInfo>.Create(AdapterBindSource1,
                                                               FUser, True);

  ABindSourceAdapter.AutoPost := False;
end;

上述代码的框架,是 AdapterBindSource1 的事件 OnCreateAdapter 双击产生。

到此,数据模块就做好了。

4. 界面:在 Form 上面拖放几个 Label1, Edit1 用来显示数据。右键点击 Form 下拉菜单选则 BindVisually,IDE 底部出来绑定的界面,问题来了,按照网上例子,AdapterBindSource1 就在这个 Form 里面,因此在可视化绑定的界面里,可以看到这个作为绑定数据源的 AdapterBindSource1,然后从 AdapterBindSource1 里面挑选字段拉线到 Label 或者 Edit 就可以了。但现在这个 AdapterBindSource1 不在 Form 本地,而是在另外一个模块里面,这里就看不见。无法拉线的方式来建立绑定。

4.1. 首先,这个 Form 的单元里面,需要 Uses 上述 DataModule 的单元名称。但 User 以后,可视化绑定的界面里面,依然不会出现 DataModule 里面的 AdapterBindSource1,无法做拉线绑定操作。

4.2. 拖一个 BindingsList 控件到 Form 上,双击它,弹出绑定连接对话框。如果做了可视化的拉线绑定,这里面会出来绑定对象。现在里面是空的。鼠标右键点这个绑定对话框,下拉菜单选择 New Binding,出现一个绑定类型对话框,在里面选择 LinkControlToField,产生一个连接,这时候 IDE 左边的属性面板,会有这个连接的属性,设置属性里面的:Control 设置为 Edit1;DataSource 下拉,就可以看到 DataModule2.AdapterBindSource1,选择它;然后选择 FieldName 下拉,可以看到字段名字,选择一个字段。绑定建立。如果绑定的界面元素是 Label,则在新建连接时,选择 LinkPropertyToField,其它操作类似。

4.3. 多个界面元素可以同时绑定到同一个 AdapterBindSource1 的相同字段;多个不同的 Form 上的界面元素也可以同时绑定到 AdapterBindSource1 的相同字段。当在一个界面里面用户修改 Edit1 的值的时候,其它界面里对应该字段的界面元素的显示会自动跟随变化。

5. 问题:用代码修改了 FUser 的属性值,界面元素没有自动跟随变化。如果是 TDataSet,修改了里面的值,界面元素的显示是会自动跟随变化的。这里,如果用代码修改了 FUser 的属性值,必须调用一次 AdapterBindSource1.Refresh; 才能让界面元素的显示自动跟随变化。因为 AdapterBindSource1 就在 DataModule 里面,因此,作为 MVVM 里面的 ViewModel 的 DataModule2 本身无需知道界面,无需和 Form 有耦合代码。而作为数据源的 TUser 也无需知道 DataModule2.

6. 在 Edit1 里面修改了一个字段值,其它 Form 里面绑定到对应字段的 Label 的显示也会自动跟随同步,但这时候,FUser 的值是否也被同步修改了?这时候取决于代码的写法。注意上述代码里面的  ABindSourceAdapter.AutoPost := False;

6.1. 如果是 ABindSourceAdapter.AutoPost := True;则用户在界面上的手动修改操作会自动反映到 FUser。

6.2. 如果是 ABindSourceAdapter.AutoPost := False; 则不会。这里看起来就是仅仅是 AdapterBindSource1 作为一个类似 ClientDataSet 的东西它内部的数据变化了,但并没有提交到数据对象 FUser,这时候如果用代码执行一次 AdapterBindSource1.Post; 则可以发现 FUser 的对应的属性值跟着改变了。

总结:

1. 这个 AdapterBindSource1 有点类似 TClientDataSet,连接到它的各个界面上的界面元素,修改了一个,另外一个会自动跟随变化;

2. AdapterBindSource1 对应的数据对象 FUser 类似数据库。如果 AdapterBindSource1 没有 Post 则修改的数据只是在 AdapterBindSource1 里面,不会更新到 FUser。

3. 因此,从 MVVM 的角度来看,正确的做法应该是把 AdapterBindSource1 和数据对象放在一个模块。或者说,数据对象一个模块(Model),AdapterBindSource1 在一个模块(ViewModel),而界面则属于 View 这个模块。这样就避免了数据对象和界面的耦合。数据对象的代码可以单独写,和界面完全无关。ViewModel 的代码也和界面无关,而且可以多个界面绑定到一个 ViewModel 上面。

发布了116 篇原创文章 · 获赞 19 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/pcplayer/article/details/104272700