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 上面。

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章