动态

详情 返回 返回

一步一步學習使用LiveBindings(7) 實現對JSON數據的綁定 - 动态 详情

本課將介紹如何從JSON中獲取綁定數據源,並且將更新也寫回JSON。可以設想一下有一台遠端服務器提供JSON數據,Delphi客户端可以接收這些JSON數據,然後轉換成數據綁定對象,在應用程序中處理完數據後,將更新的數據序列化為JSON傳回遠端服務器,很多移動應用使用了這種模式處理服務器端的數據。好了廢話少説,開始打開Delphi 12.3,建項目吧。

本系列課程具有前後關聯性,如果你對LiveBindings的諸多細節沒有一個大的概念,請看一步一步學習使用LiveBindings的前幾課。

1. 單擊主菜單中的 File > New > Multi-Device Application - Delphi > Blank Application ,創建一個新的多設備應用程序。

建議立即單擊工具欄上的Save All按鈕,將單元文件保存為uMainForm.pas,將項目保存為LiveBinding_BindToJSON.dproj。

你的項目結構應該像這樣:

img

首先新建一個名為CollectionObjects.pas的Unit,右鍵單擊Project Manager中的項目名稱,選擇“Add New > Unit”,保存為CollectionObjects.pas文件名,CollectionObjects.pas包含一個簡單的TPerson類,你可以想象為一個業務實體,代碼如下:

//
unit CollectionObjects;

interface

type
  TPerson = class
  private
    FAge: Integer;
    FLastName: string;
    FFirstName: string;
  public
    constructor Create(const FirstName, LastName: String; Age: Integer);
    property FirstName: string read FFirstName write FFirstName;
    property LastName: string read FLastName write FLastName;
    property Age: Integer read FAge write FAge;
  end;

implementation

{ TPerson }

constructor TPerson.Create(const FirstName, LastName: String; Age: Integer);
begin
  FFirstName := FirstName;
  FLastName := LastName;
  FAge := Age;
end;

end.

代碼過於簡潔,無須過多介紹。

2. 在主窗體上,放一個TTabControl控件,為該控件添加2個TabItem,一個指定Text為"Grid",一個指定Text為“JSON”,這個名為JSON的Tab頁用來演示後台的JSON數據的變化,用户將可以編輯這個JSON,同時在Grid上看到更新。

在名為TabGrid的TabItem上,放置一個TGrid和一個TBindNavigator控件,在名為TabJSON的TabItem上,放置一個TMemo控件用來顯示JSON內容。
最後放置一個TDataGeneratorAdapter和一個TAdapterBindSource,指定AdapterBindSource1的Adapter為DataGeneratorAdapter1,BindNavigator1的DataSource屬性為AdapterBindSource1。

設計窗口如下圖所示:

img

3. 接下來需要完成綁定工作,目前AdapterBindSource1雖然指向了DataGeneratorAdapter1,但是DataGeneratorAdapter1還沒有設置字段和相應的數據生成器。現在右擊DataGeneratorAdapter1,從彈出的菜單中選擇“Fields Editor”菜單項,根據在CollectionObjects.pas單元中定義的TPerson類的屬性來創建3個字段,並分別指定如下圖所示的數據生成器。

img

4. 在TGrid上右擊鼠標,從彈出的菜單中選擇“Bind Visually”,在LiveBinding Designer中,將AdapterBindSource1的字段分別拖放到MainGrid上,可以看到生成的數據會立即顯示在Grid上。

img

注意:拖動單獨的列到Grid,可以單獨調整Grid列的屬性。

到目前為止,就構建了一個具有樣例數據的應用程序。

5. 由於示例沒有真正的訪問遠端服務器,為了演示數據綁定的效果,接下來實現AdapterBindSource1的OnCreateAdapter事件,在該事件中添加測試數據,以便在Grid上可以看到“真實”的數據。

首先在Interface區的uses下面添加類引用,並在private區添加一個泛型集合類。

unit uMainForm;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.TabControl,
  Data.Bind.Controls, System.Rtti, FMX.Grid.Style, FMX.Memo.Types,
  Data.Bind.Components, Data.Bind.ObjectScope, FMX.Memo,
  FMX.Controls.Presentation, FMX.ScrollBox, FMX.Grid, FMX.Layouts,
  Fmx.Bind.Navigator, Data.Bind.GenData, Data.Bind.EngExt, Fmx.Bind.DBEngExt,
  System.Bindings.Outputs, Data.Bind.Grid, Fmx.Bind.Grid, Fmx.Bind.Editors,
  //添加如下的單元引用
  CollectionObjects,System.Generics.Collections,REST.Json,System.JSON;

type
  TfrmMain = class(TForm)
    Tab: TTabControl;
    tabGrid: TTabItem;
    tabJSON: TTabItem;
    NavigatorAdapterBindSource1: TBindNavigator;
    mmJSON: TMemo;
    DataGeneratorAdapter1: TDataGeneratorAdapter;
    AdapterBindSource1: TAdapterBindSource;
    grdMain: TGrid;
    BindingsList1: TBindingsList;
    LinkGridToDataSourceAdapterBindSource1: TLinkGridToDataSource;
  private
    { Private declarations }
    //添加保存人員信息的泛型列表類
    FMyPeople: TObjectList<TPerson>;
  public
    { Public declarations }
  end;

在單元引用區,可以看到添加了System.JSON和REST.JSON引用,它們將用來處理JSON的解析與生成。

接下來在AdapterBindSource1的OnCreateAdapter事件中添加一些模擬的人員數據,如下代碼所示:

procedure TfrmMain.AdapterBindSource1CreateAdapter(Sender: TObject;
  var ABindSourceAdapter: TBindSourceAdapter);
begin
  //用來保存人員數據的集合。
  FMyPeople := TObjectList<TPerson>.Create(True);

  //添加單個的人員信息
  FMyPeople.Add(TPerson.Create('Gomez', 'Addams', 40));
  FMyPeople.Add(TPerson.Create('Morticia', 'Addams', 38));
  FMyPeople.Add(TPerson.Create('Pugsley', 'Addams', 8));
  FMyPeople.Add(TPerson.Create('Wednesday', 'Addams', 12));
  FMyPeople.Add(TPerson.Create('Uncle', 'Fester', 55));
  FMyPeople.Add(TPerson.Create('Grandmama', 'Frump', 72));
  FMyPeople.Add(TPerson.Create('', 'Lurch', 50));
  FMyPeople.Add(TPerson.Create('Thing T.', 'Thing', 99));
  FMyPeople.Add(TPerson.Create('Cousin', 'Itt', 21));
  // 使用TListBindSourceAdapter綁定到集合數據。
  ABindSourceAdapter := TListBindSourceAdapter<TPerson>.Create(Self, FMyPeople, True);
end;

現在運行示例,可以看到這些數據已經顯示在了Grid上,可以進行上下移動編輯了。

img

6. 搞定了數據綁定的問題,現在真正要解決的問題是將TObjectList 類型的泛型集合轉換成JOSN字符串發送給服務器,或者是在接收到JSON字符串後,轉換為TObjectList類型的泛型集合以更新綁定UI,在這裏需要引入2個過程。

在private區域定義2個過程,用來分別將對象轉換為JSON以及將JSON轉換為對象。

  private
    { Private declarations }
    //添加保存人員信息的泛型列表類
    FMyPeople: TObjectList<TPerson>;
    //將對象轉換為JSON數據
    procedure ObjectsToJson;
    //將JSON轉換為對象
    procedure JsonToObjects;

Implementation

procedure TfrmMain.JsonToObjects;
begin
  //TODO 將TMemo中的JSON字符串轉換為對象
end;

procedure TfrmMain.ObjectsToJson;
begin
  //TODO 將TGrid綁定的對象轉換為JSON字符串顯示在TMemo中。
end;

由於ObjectsToJson和JsonToObjects涉及到一些JSON相關的操作,咱們需要先想想如何實現,但是UI的佈局應該是當Tab切換時,如果切換到Grid這個Tab頁,則調用JsonToObjects更新Grid上的數據;如果切換到JSON這個Tab頁,則調用ObjectsToJson將Grid上的更新序列化為JSON字符串,所以應該是這樣的一個效果:

img

可以看到,JSON與Grid是同步的,兩邊都可以更改。

7. 在主窗體中選中TTabControl控件,在屬性編輯器中切換到Event標籤頁,找到OnChange事件,添加如下的代碼:

procedure TfrmMain.TabChange(Sender: TObject);
begin
  //如果當前頁面是JSON頁
  if Tab.ActiveTab = tabJSON then
  begin
    //如果AdapterBindSource處於編輯模式,則提交。
    if AdapterBindSource1.Editing then
      AdapterBindSource1.Post;
    ObjectsToJson;   //將對象轉換為JSON。
  end
  else if Tab.ActiveTab = tabGrid then
    JsonToObjects;   //反之將JSON轉換為對象。
end;

8. 在開始這2個核心的過程之前,良好的可重用的代碼設計就顯得很重要。System.JSON單元封裝了JSON對象操作的邏輯,REST.JSON則提供了單一JSON字符串轉換為對象或者對象到JSON字符串的轉換功能。在這裏封裝了一個名為TUtils的類,它包含2個類方法:

type
  TUtils = class
  public
    //將一個泛型列表對象轉換為JSON數組
    class function ObjectListToJSON<T: class>(const AObjects: TObjectList<T>): TJSONArray;
    //將一個JSON數組轉換為泛型列表對象
    class function JsonToObjectList<T: class, constructor>(const AText: string): TObjectList<T>;
  end;

  { TUtils }

class function TUtils.JsonToObjectList<T>(const AText: string): TObjectList<T>;
var
  LObject: T;
  LArray: TJSONArray;
  LValue: TJSONValue;
  LList: TObjectList<T>;
begin
  LList := TObjectList<T>.Create;
  LArray := nil;
  try
    LArray := TJSONObject.ParseJSONValue(AText) as TJSONArray;
    if LArray = nil then
      raise Exception.Create('Invalid JSON');
    for LValue in LArray do
      if LValue is TJSONObject then
      begin
        //使用REST.Json提供的TJson類的類方法完成轉換
        LObject := TJson.JsonToObject<T>(TJSONObject(LValue));
        LList.Add(LObject);
      end;
    Result := LList;
    LList := nil;
  finally
    LArray.Free;
    LList.Free;
  end;
end;

class function TUtils.ObjectListToJSON<T>(
  const AObjects: TObjectList<T>): TJSONArray;
var
  LObject: T;
  LArray: TJSONArray;
  LValue: TPerson;
  LElement: TJSONObject;
begin
  LArray := TJSONArray.Create;
  try
    for LObject in AObjects do
    begin
      //使用REST.Json提供的TJson類的類方法完成轉換
      LElement := TJson.ObjectToJsonObject(LObject);
      LArray.AddElement(LElement);
    end;
    Result := LArray;
    LArray := nil;
  finally
    LArray.Free;
  end;
end;

代碼中的TJSONArray,TJSONObject類是由System.Json的供來操縱JSON的,核心部分的TJson類是由REST.Json所提供,顧名思議,這個單元是處理Restful操作的。

9. 現在一切準備就緒,繼續完成ObjectsToJson和JsonToObjects這兩個過程,代碼如下所示:

procedure TfrmMain.JsonToObjects;
begin
  //使用JsonToObjectList將JSON轉換為對象
  fMyPeople := TUtils.JsonToObjectList<TPerson>(mmJSON.Text);
  //將列表數據重新賦給AdapterBindSource1。
  TListBindSourceAdapter<TPerson>(AdapterBindSource1.InternalAdapter).SetList(fMyPeople);
  //刷新用户界面
  AdapterBindSource1.Active := True;
end;

procedure TfrmMain.ObjectsToJson;
var
  LArray: TJSONArray;
begin
  //將對象轉換為TJSONArray數組
  LArray := TUtils.ObjectListToJSON<TPerson>(fMyPeople);
  try
    //將JSON數組稍稍美經後顯示在TMemo控件
    mmJSON.Text := PrettyJSON(LArray.ToString);
  finally
    LArray.Free;
  end;
end;

function PrettyJSON(AJson: String): String;
begin
  Result := StringReplace(AJson, '},', '},' + sLineBreak, [rfReplaceAll]);
  Result := StringReplace(Result, '[{', '[' + sLineBreak + '{', [rfReplaceAll]);
end;

10. 萬事皆備,只欠一Run了,按下F9,或者是主菜單的“Run > Run”,可以看到JOSN和Grid的數據果然已經同步了。

真實的項目中,JSON生成後,應該是要發送給Server端,或者存儲到本地文件,這可以根據需要而定。

本課就講到這裏了,雖然到目前為止筆者還沒有深挖TBindingList的內幕,不過可以看到使用LiveBindings Designer已經可以解決不少問題了。當然在實際的項目中還是有很多細節要處理的,比如顯式格式的轉換,複雜的綁定場景等等。

在本系列的後面的課程中會繼續深挖。

Add a new 评论

Some HTML is okay.