一步一步学习使用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。

你的项目结构应该像这样:

首先新建一个名为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。

设计窗口如下图所示:

3. 接下来需要完成绑定工作,目前AdapterBindSource1虽然指向了DataGeneratorAdapter1,但是DataGeneratorAdapter1还没有设置字段和相应的数据生成器。现在右击DataGeneratorAdapter1,从弹出的菜单中选择“Fields Editor”菜单项,根据在CollectionObjects.pas单元中定义的TPerson类的属性来创建3个字段,并分别指定如下图所示的数据生成器。

4. 在TGrid上右击鼠标,从弹出的菜单中选择“Bind Visually”,在LiveBinding Designer中,将AdapterBindSource1的字段分别拖放到MainGrid上,可以看到生成的数据会立即显示在Grid上。

注意:拖动单独的列到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上,可以进行上下移动编辑了。

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字符串,所以应该是这样的一个效果:

可以看到,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已经可以解决不少问题了。当然在实际的项目中还是有很多细节要处理的,比如显式格式的转换,复杂的绑定场景等等。

在本系列的后面的课程中会继续深挖。

一步一步学习使用LiveBindings(7) 实现对JSON数据的绑定的更多相关文章

  1. 【Spring学习笔记-MVC-4】SpringMVC返回Json数据-方式2

    <Spring学习笔记-MVC>系列文章,讲解返回json数据的文章共有3篇,分别为: [Spring学习笔记-MVC-3]SpringMVC返回Json数据-方式1:http://www ...

  2. 【Spring学习笔记-MVC-3】SpringMVC返回Json数据-方式1

    <Spring学习笔记-MVC>系列文章,讲解返回json数据的文章共有3篇,分别为: [Spring学习笔记-MVC-3]SpringMVC返回Json数据-方式1:http://www ...

  3. 只要三步!阿里云DLA帮你处理海量JSON数据

    概述 您可能有大量应用程序产生的JSON数据,您可能需要对这些JSON数据进行整理,去除不想要的字段,或者只保留想要的字段,或者仅仅是进行数据查询. 那么,利用阿里云Data Lake Analyti ...

  4. Android 学习笔记之Volley(七)实现Json数据加载和解析...

    学习内容: 1.使用Volley实现异步加载Json数据...   Volley的第二大请求就是通过发送请求异步实现Json数据信息的加载,加载Json数据有两种方式,一种是通过获取Json对象,然后 ...

  5. 我的angularjs源码学习之旅3——脏检测与数据双向绑定

    前言 为了后面描述方便,我们将保存模块的对象modules叫做模块缓存.我们跟踪的例子如下 <div ng-app="myApp" ng-controller='myCtrl ...

  6. 【Struts2学习笔记-8】Struts2实现json数据的返回

    需要的jar包 struts2-json-plugin-2.3.12.jar xwork-core-2.3.16.3.jar struts.xml 来自为知笔记(Wiz) 附件列表 IMG_20150 ...

  7. Knockout.Js官网学习(加载或保存JSON数据)

    前言 Knockout可以实现很复杂的客户端交互,但是几乎所有的web应用程序都要和服务器端交换数据(至少为了本地存储需要序列化数据),交换数据最方便的就是使用JSON格式 – 大多数的Ajax应用程 ...

  8. (转)SpringMVC学习(十)——SpringMVC与前台的json数据交互

    http://blog.csdn.net/yerenyuan_pku/article/details/72514022 json数据格式在接口调用中.html页面中比较常用,json格式比较简单,解析 ...

  9. 初识Scrapy——1—scrapy简单学习,伯乐在线实战、json数据保存

    Scrapy——1 目录 什么是Scrapy框架? Scrapy的安装 Scrapy的运行流程 Scrapy的使用 实战:伯乐在线案例(json文件保存) 什么是Scrapy框架? Scrapy是用纯 ...

  10. (数据科学学习手札126)Python中JSON结构数据的高效增删改操作

    本文示例代码及文件已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 在上一期文章中我们一起学习了在Python ...

随机推荐

  1. pyqt点击右上角关闭界面但子线程仍在运行

    现象: 通过右上角的叉关闭图形界面后,程序运行的子线程却不会被自动关闭,依然留存在系统中 原因: 子线程没有正确关闭 解决方法: 1.将子线程设置成守护线程 self.your_thread = th ...

  2. to查完成绩的夏夏

    虽然不知道你现在看到的成绩是310还是340,抑或预估值域外的其他成绩,此刻我都想对你说你真的很棒! 还记得吗,一个人拖着个行李箱,来到陌生的城市二战,只有要去更大的世界的决心和傻气.有的时候会怀疑自 ...

  3. WindowsPE文件格式入门02.选项头其它和节表

    https://www.bpsend.net/thread-444-1-1.html 选项头 IMAGE_OPTIONAL_HEADER:以供操作系统加载PE文件使用,32位必选. 重要字段: DWO ...

  4. Spring异常处理 bug !!!同一份代码,结果却不一样?

    1. 背景 在上周遇到一个spring bug的问题,将其记录一下.简化的代码如下: public void insert() { try { Person person = new Person() ...

  5. L2-4、选择微调还是提示工程?企业级内容生成的最佳实践

    一.Prompt 工程与模型微调的本质区别 Prompt 工程的特点 Prompt 工程是通过精心设计输入提示来引导大语言模型生成所需输出的技术.它不改变模型的基本参数,而是利用现有模型能力. 工作原 ...

  6. Spring 动态绑定多实现类实例综述

      摘要: 由于业务场景复杂,一个算法需要开发行为变化多端的多个实现类,然后在系统运行时根据不同场景装载不同的类实例.为了使应用程序具有更好的灵活性.可扩展性和代码的可重用性,在借鉴前人处理方法的基础 ...

  7. 钓鱼攻击(phishing)详解和实现过程

    钓鱼攻击 定义:钓鱼攻击是一种常见的网络攻击手段,攻击者通过伪装成合法的网站.邮件或信息,诱骗用户提供敏感信息,如用户名.密码.银行卡号等,从而达到非法获取用户数据或进行欺诈的目的. 网络钓鱼(phi ...

  8. DeepSeekMath -- GRPO

    Deepseek系列博客目录 Model 核心 Date DeepSeekLLM 探究LLM Scalling Law 2024.01 DeepSeekMath 提出GRPO 2024.04 Deep ...

  9. ArkUI-x跨平台Bridge最佳实践

    bridge核心架构思想 平台桥接机制是ArkUI-X框架提供的⼀种ArkTs语⾔和平台原⽣语⾔(Java.OC)之间通信的机制,⽅便⼆者互相调⽤.需要说明的是,平台桥接机制必须在打开ArkUI界⾯时 ...

  10. 函数使用十四:BAPI_PO_CREATE1

    *&---------------------------------------------------------------------* *& Report ZBAPI_PO_ ...