# 2. 制作MVVM-C项目

MVVM-C 项目是使用 AWTK-MVVM 框架和 C 语言开发应用程序的一个工程项目,关于 MVVM 的介绍,请参阅 AWTK-MVVM (opens new window) 中的文档。下面详细介绍如何使用 AWTK Designer 制作一个 MVVM-C 项目。

# 2.1 新建 MVVM-C 项目工程

AWTK Designer 启动后默认打开"新建项目"对话框,如下图所示。项目类型选择 MVVM-C,然后设置好项目的相关参数后,最后点击"创建"按钮即可创建一个 MVVM-C 项目,关于新建项目工程的更多信息,请参阅《AWTK Designer 用户手册》。

图2.1 新建 MVVM-C 项目
图2.1 新建 MVVM-C 项目

点击主界面工具栏的"新建项目"按钮,也可打开"新建项目"对话框。

新建项目后,AWTK Designer 会进入主界面并默认创建和打开一个名为 home_page 的页面,这个页面就可以称为一个 View(视图)。

对比常规项目,主界面增加了下图框选的内容,继续阅读下文便知其用途,关于 AWTK Designer 界面的更多信息,请参阅《AWTK Designer 用户手册》。

图2.2 主界面
图2.2 主界面

项目的目录结构

点击下图框选按钮切换到常规模式,可以看到项目的目录结构:

图2.3 项目目录结构
图2.3 项目目录结构

对比常规项目, MVVM-C 项目会多出 models 和 view_models 两个文件夹,models 文件夹用于存放模型的C代码,models 文件夹中默认会有一个全局对象管理器 objects_manager,它是一个特殊的 Model(模型),提供访问业务数据和操作的接口,一个程序有且仅有一个,被 ViewModel(视图模型)共享,默认在程序启动时创建、退出时销毁。而 view_models 文件夹用于存放视图模型的C代码。

AWTK Designer 新建的常规项目目录结构的详细信息,请参阅《AWTK Designer 用户手册》。

# 2.2 ViewModel(视图模型)

ViewModel(视图模型,简称 VM)是与视图沟通的一座桥梁,想要完成数据绑定和命令绑定,就必须关联视图模型和视图。

数据绑定是通过规则在 View 和 Model 之间建立联系,当用户在 View 上修改数据时,View 上的数据自动同步到 Model 中,当 Model 中的数据有变化时,自动更新到 View 上去。而命令绑定是通过规则在 View 中控件的事件和 Model 之间建立联系,用户在 View 上触发某个事件之后,框架自动执行 Model 中对应的函数。

# 2.2.1 为 View 新增 ViewModel

现在为 home_page 页面增加一个 VM 。

步骤一:点击“模型” -> “新建模型”按钮新增 VM:

图2.4 点击新增模型按钮
图2.4 点击新增模型按钮

步骤二:之后会弹出如下图的对话框,勾选“添加全局对象为 ViewModel 的属性”会把全局对象管理器对象添加到这个新增的 VM 中,点击确定就会为 home_page 新增一个 VM 。

图2.5 弹出对话框
图2.5 弹出对话框

同时 AWTK Designer 默认会在项目 src/view_models 目录下自动生成 VM 代码(切换至常规模式可以看到目录结构):

图2.6 自动增加代码
图2.6 自动增加代码

并且在 src 目录下 application.c 自动增加注册 VM 的代码:

图2.7 自动增加代码
图2.7 自动增加代码

# 2.2.2 为 ViewModel 添加属性

在数据绑定前,需要先为 VM 添加属性。属性分为局部属性和全局属性,在窗口关闭,与之关联的 VM 跟着销毁时,局部属性则会跟着一起销毁,而全局属性则不会销毁,直到程序结束。

# 1. 为 ViewModel 添加局部属性

现在为 home_page_view_model 添加一个局部属性。点击“模型”-> “属性” -> “+”按钮,之后就会在 VM 中增加一个局部属性:

图2.8 添加局部属性
图2.8 添加局部属性

# 2. 编辑属性

增加属性后还需要编辑属性中名称、类型、默认值和描述,鼠标左键点击属性中的类型可对其进行选择:

图2.9 编辑属性
图2.9 编辑属性

鼠标左键双击属性中的名称、默认值和描述可对其进行编辑:

图2.10 编辑属性
图2.10 编辑属性

通过上述步骤,就可以得到一个已编辑的属性了:

图2.11 编辑属性完成
图2.11 编辑属性完成

# 3. 为 ViewModel 添加全局属性

现在为 home_page_view_model 添加一个全局属性(为 VM 添加属性后,对应的 VM 代码也会跟着添加属性)。

(1) 添加属性到全局对象管理器

步骤一:在菜单栏点击“查看” -> “业务对象”按钮:

图2.12 点击全局对象按钮
图2.12 点击全局对象按钮

步骤二:跳转到业务对象窗口,点击“属性” -> “+”按钮,之后就会在全局对象管理器 objects_manager 中增加一个属性(在 objects_manager 中增加一个属性,在项目 src/models 目录下 objects_manager 的代码也会跟着改变。):

图2.13 添加属性到全局对象管理器
图2.13 添加属性到全局对象管理器

步骤三:按照上文编辑得到一个已编辑的全局属性 g_prop:

图2.14 编辑属性完成
图2.14 编辑属性完成

(2) 添加全局属性到 ViewModel

回到 home_page 界面,点击全局对象管理器 object_manager就会发现多出一个子属性 g_prop:

图2.15 添加全局属性
图2.15 添加全局属性

在业务对象窗口中点击下图中框选按钮,之后弹出对话框,添加object_manager.g_prop 到 VM 即可:

图2.16 添加全局属性到视图模型
图2.16 添加全局属性到视图模型
图2.17 添加全局属性到视图模型
图2.17 添加全局属性到视图模型

回到 home_page 界面,就会发现多出一个属性 g_prop:

图2.18 添加全局属性到视图模型
图2.18 添加全局属性到视图模型

# 2.2.3 数据绑定

下面会以上文中为 home_page_view_model 添加的 property 属性绑定到控件 slider 的 value 属性为例,讲解如何完成数据绑定。

# 1. 增加数据绑定规则

方式一:

步骤一:选择需要绑定数据的控件,然后选择需要绑定的控件属性,点击框选按钮:

图2.19 数据绑定步骤一
图2.19 数据绑定步骤一

步骤二:弹出选项,选择设置属性绑定:

图2.20 数据绑定步骤二
图2.20 数据绑定步骤二

步骤三:弹出数据绑定编辑对话框,在"绑定的数据"一栏选择需要绑定的属性或者在编辑器中输入属性,也可以点击右侧的按钮进入设置绑定规则对话框,然后点击确定:

图2.21 数据绑定步骤三
图2.21 数据绑定步骤三

上文只是使用了最简单的绑定规则,还可以根据自身的需求去选择模式,使用内嵌表达式和操作符。点击按钮就会添加规则,在按钮上停留还会告诉其作用是什么,还有使用的方法。想了解更多的绑定规则请参阅 AWTK-MVVM 数据绑定 (opens new window)

图2.22 设置绑定规则
图2.22 设置绑定规则
图2.23 在按钮上停留了解使用方法
图2.23 在按钮上停留了解使用方法

方式二:

步骤一:将 VM 上的属性(property 属性)用鼠标拖动到需要绑定的控件中,或者拖动到需要绑定的控件的某个属性中:

图2.24 数据绑定步骤一
图2.24 数据绑定步骤一
图2.25 数据绑定步骤一
图2.25 数据绑定步骤一
图2.26 数据绑定步骤一
图2.26 数据绑定步骤一

步骤二:弹出对话框,选择需要绑定的控件属性,点击绑定规则编辑框弹出绑定规则对话框,设置方式和方法一相同,设置完后点击确认即可完成绑定。

图2.27 数据绑定步骤二
图2.27 数据绑定步骤二

数据绑定完成后就会看到下图框选的按钮样式发生了改变:

图2.28 数据绑定完成
图2.28 数据绑定完成

绑定成功后,再添加一个新的 label 控件,然后按照上面的方法把 property 属性绑定到 label 的 text 属性上,这样就可以在滑动 slider 滑块时,上面的 label 文本也会跟着改变。

图2.29 数据绑定运行结果
图2.29 数据绑定运行结果

# 2.2.4 为 ViewModel 添加命令

虽然命令绑定不一定需要使用到 VM 中的命令,但是先了解如何为 VM 添加命令,后面就可以更全面地了解命令绑定了。命令分为局部命令和全局命令,局部命令只可以在该 VM 下使用,而全局命令可以在多个 VM 下共同使用(为 VM 添加命令后,对应 的 VM 代码文件也会自动添加命令代码)。

# 1. 为 ViewModel 添加局部命令

现在为 home_page_view_model 添加一个局部命令。

步骤一:点击“模型” -> “命令” -> “+”按钮,之后就会在 VM 中增加一个局部命令:

图2.30 添加命令步骤一
图2.30 添加命令步骤一

步骤二:和编辑属性的操作一样,鼠标左键双击属性中的名称和描述可对其进行编辑:

图2.31 编辑命令步骤二
图2.31 编辑命令步骤二

(1)修改命令实现

步骤一:最后需要修改命令的实现,点击该命令的“跳转代码”按钮,打开对应 VM 代码文件:

图2.32 编辑命令
图2.32 编辑命令

步骤二:修改命令执行函数,如需要修改 home_page_view_model 的 command 命令的实现,就更改 home_page_view_model_command() 函数,如下图让命令打印语句"command run!\r\n"。home_page_view_model_can_command() 函数是判断命令能否执行的函数,在执行命令前会先执行该函数,返回 TRUE,则调用命令执行函数,否则不调用命令执行函数。:

图2.33 编辑命令代码
图2.33 编辑命令代码

通过上述步骤,为 VM 添加局部命令就完成了。

# 2. 为 ViewModel 添加全局命令

(1) 添加命令到全局对象管理器

步骤一:在菜单栏点击“查看” -> “业务对象”按钮:

图2.34 添加命令到全局对象管理器步骤一
图2.34 添加命令到全局对象管理器步骤一

步骤二:跳转到业务对象窗口,点击“命令” -> “+”按钮,之后就会在全局对象管理器中增加一个命令,其代码中也会增加命令的实现函数:

图2.35 添加命令到全局对象管理器步骤二
图2.35 添加命令到全局对象管理器步骤二

步骤三:和编辑属性的操作一样,鼠标左键双击命令中的名称和描述可对其进行编辑:

图2.36 添加命令到全局对象管理器步骤三
图2.36 添加命令到全局对象管理器步骤三

步骤四:最后需要修改命令的实现,由于上文有详细解析,这里就不再赘述:

图2.37 添加命令到全局对象管理器步骤四
图2.37 添加命令到全局对象管理器步骤四
图2.38 编辑全局命令代码
图2.38 编辑全局命令代码

(2) 添加全局命令到 ViewModel

回到 home_page 界面,就会发现多出一个命令 object_manager.g_command(:

图2.39 添加全局命令到视图模型
图2.39 添加全局命令到视图模型

通过上述步骤,为 VM 添加全局命令就完成了。

# 2.2.5 命令绑定

下面讲解如何完成命令绑定。

# 1. 增加命令绑定规则

步骤一:选择需要绑定命令的控件,然后点击“事件”按钮,点击“+”按钮:

图2.40 增加命令绑定规则步骤一
图2.40 增加命令绑定规则步骤一

步骤二:之后会弹出对话框选择触发事件(该事件触发时,就会执行绑定的命令),如选择“value_changed”进入下一步,就会生成与控件 value_changed 事件绑定的命令:

图2.41 增加命令绑定规则步骤二
图2.41 增加命令绑定规则步骤二

# 2. 选择执行动作

点击动作下拉列表,可以看到有如下动作:

图2.42 选择执行动作
图2.42 选择执行动作

每个动作的可选参数如下:

打开窗口:

可选参数 描述
窗口名称 指定打开窗口的名称
切换到已存在的窗口 存在同名窗口,会切换到已存在的窗口,而不会打开一个新窗口

导航请求:

可选参数 描述
请求类型 指定请求命令的名称
请求参数 请求命令需要传入的参数

执行 ViewModel 中的命令:

可选参数 描述
命令的名称 指定执行命令的名称
命令的参数 执行命令时需要传入的参数

执行 FScript 脚本:

可选参数 描述
FScript 代码 需要执行的FScript 代码

下文逐一介绍(什么也不做顾名思义就是什么也不做,所以不作详细介绍)。

(1) 打开窗口

用于打开指定窗口,例如窗口名称选择窗口 new,就会在 slider 滑动改变值后打开窗口 new:

图2.43 打开窗口
图2.43 打开窗口

(2) 导航请求

对导航器发起请求,请求后是否执行该动作由导航器进行判断,想深入了解可以查看awtk-mvvm源码里的docs/navigate.md文档

图2.44 导航请求
图2.44 导航请求
图2.45 导航请求参数
图2.45 导航请求参数

(3) 执行 ViewModel 中的命令

用于执行在 VM 添加的命令,例如选择执行 command 命令(该命令是在上文添加的,命令实现为打印语句"command run!\r\n")。

图2.46 执行 VM 中的命令
图2.46 执行 VM 中的命令

在 slider 滑动改变值后执行命令打印该语句:

图2.47 执行结果
图2.47 执行结果

指定命令的参数

可以指定一个参数,该参数并不是必须的,但有时命令参数确实能带来不少便利。

步骤一:点击命令的参数编辑框:

图2.48 指定命令的参数步骤一
图2.48 指定命令的参数步骤一

步骤二:弹出设置参数的对话框:

图2.49 指定命令的参数步骤二
图2.49 指定命令的参数步骤二

参数类型有三种:normal(单个字符串)、string(字符串形式的参数序列)和 fscript(FScript表达式形式的参数序列),一般来说使用 normal 类型就足够了,如果想使用另外两种参数类型,可以选择对应类型,就可以在列表中填写所需要的数据(关于命令参数的更多资料请参阅 AWTK-MVVM 命令绑定 (opens new window)11.2 命令的参数):

图2.50 指定命令的参数步骤三
图2.50 指定命令的参数步骤三

命令的参数会以字符串的形式传递到实现代码中:

图2.51 指定命令的参数步骤四
图2.51 指定命令的参数步骤四

(4) 执行 FScript 脚本

如果上述的动作都满足不了需求,则可以使用 FScript 脚本。

FScript 是一个极简的脚本引擎,借鉴了函数语言中一些思路,主要用于低端嵌入式系统,让用户轻松扩展现有系统,而不需要重新编译和下载固件。

步骤一:点击 FScript 代码编辑框:

图2.52 执行 FScript 脚本步骤一
图2.52 执行 FScript 脚本步骤一

步骤二:在设定绑定规则对话框中的编辑器中编写 FScript 脚本:

图2.53 执行 FScript 脚本步骤二
图2.53 执行 FScript 脚本步骤二

关于 FScript 的更多资料请参阅 FScript (opens new window)

在slider滑动改变值后执行结果:

图2.54 命令的参数
图2.54 命令的参数

# 3. 动作结束后行为

动作结束后行为在 AWTK-MVVM 命令绑定 (opens new window)里有详细解释,在这里就不赘述了。

# 2.3 Model(模型)

如果只是做一个简单的应用程序,上面讲解的内容(VVM 模式)是完全够用的,但如果制作的应用程序需要在 VM 中添加大量的属性和命令,这样后面就很难对其进行维护和拓展。这时就需要用到 Model(模型,简称 M),模型相当于一个类,是对现实世界中业务逻辑的抽象,里面封装了其属性和行为。将 VM 中添加大量的属性和命令封装为一个个模型,最后再将模型对象添加为 VM 的属性,这样就是 MVVM 模式,解决了程序很难对其进行维护和拓展的问题。

# 2.3.1 新建 Model

步骤一:在菜单栏点击“查看” -> “业务模型”按钮:

图2.55 新建模型步骤一
图2.55 新建模型步骤一

步骤二:跳转到业务模型窗口后,点击下图框选按钮:

图2.56 新建模型步骤二
图2.56 新建模型步骤二

步骤三:之后就会新建一个 model,对应的代码也会一并生成:

图2.57 新建模型步骤三
图2.57 新建模型步骤三

步骤四:双击模型中的名称和描述对其进行编辑:

图2.58 新建模型步骤四
图2.58 新建模型步骤四

# 2.3.2 为 Model 添加属性

步骤一:选中模型,点击“+”按钮,之后就会在 M 中增加一个属性:

图2.59 为模型添加属性步骤一
图2.59 为模型添加属性步骤一

步骤二:编辑刚才在 M 中新增属性(和在 VM 编辑属性的操作步骤一样,这里不再赘述):

图2.60 为模型添加属性步骤二
图2.60 为模型添加属性步骤二

# 2.3.3 为 Model 添加命令

步骤一:点击“命令”按钮,选中模型,点击“+”按钮,之后就会在 M 中增加一个命令:

图2.61 为模型添加命令步骤一
图2.61 为模型添加命令步骤一

步骤二:编辑刚才在 M 中新增命令:

图2.62 为模型添加命令步骤二
图2.62 为模型添加命令步骤二

步骤三:最后需要修改命令的实现,由于上文“为 ViewModel 添加命令”有详细解析,这里就不再赘述:

图2.63 为模型添加命令步骤三
图2.63 为模型添加命令步骤三
图2.64 修改命令代码
图2.64 修改命令代码

# 2.3.4 添加 Model 对象到 ViewModel

和“为 ViewModel 添加属性”操作一样,只需把类型改为上文新建的模型即可:

图2.65 添加模型对象到视图模型
图2.65 添加模型对象到视图模型
图2.66 添加模型对象到视图模型
图2.66 添加模型对象到视图模型

将上文绑定在 slider 和 label 的属性上,换成刚添加到 VM 里的 model 对象中的 m_prop 属性(数据绑定的方法在上文有详细教程):

图2.67 数据绑定
图2.67 数据绑定

运行后拖动 slider 滑块得到的效果也是完全一样的:

图2.68 数据绑定运行结果
图2.68 数据绑定运行结果

将上文绑定在 slider 的 value_changed 事件 的 command 命令,换成刚添加到 VM 里的 model 对象中的 m_command 命令(命令绑定的方法在上文有详细教程):

图2.69 命令绑定
图2.69 命令绑定

运行后拖动 slider 滑块得到的效果:

图2.70 命令绑定运行结果
图2.70 命令绑定运行结果

# 2.4 读写外部数据

如果在开发的应用程序需要将外部数据(如外部设备数据、网络数据)更新到界面上,或者将界面上的数据上传,那么下文将对项目的制作有所帮助。

# 2.4.1 同步读写模式

同步读写模式是程序需要读写外部数据时,UI 线程会阻塞,缺点是如果读写一次的时间久,那么界面会卡顿。优点是代码简单,容易维护且不容易出 Bug 。

首先需要为 model 添加命令,如在上一章 3 Model(模型)的 model 对象中的增加 read 和 write 两个命令:

图2.71 增加读写命令
图2.71 增加读写命令

跳转到代码处,编写实现读写外部数据代码,下面的代码实现可供参考,使用命令参数 arg 判断模型对象读写其中某个属性,如果 arg 为空或为"",则整个模型对象进行读写:

ret_t model0_read(tk_object_t* obj, const char* args) {
  model0_t* model = MODEL0(obj);
  return_value_if_fail(model != NULL, RET_BAD_PARAMS);

	if(args == NULL || tk_str_eq(args,"")) {
		log_debug("读取外部数据所有内容到model\r\n");
	} else if(tk_str_eq(args,"m_prop")) {
		log_debug("读取外部数据xxx到model->m_prop\r\n");
	}
  
  return RET_OK;
}

ret_t model0_write(tk_object_t* obj, const char* args) {
  model0_t* model = MODEL0(obj);
  return_value_if_fail(model != NULL, RET_BAD_PARAMS);
  
	if(args == NULL || tk_str_eq(args,"")) {
		log_debug("将model所有内容上传");
	} else if(tk_str_eq(args,"m_prop")) {
		log_debug("上传model->m_prop\r\n");
	}

  return RET_OK;
}

(1) 触发读写

触发读写是当用户每操作界面一次(如鼠标点击按钮),就读写一次。实现触发读写只需将上文添加的命令绑定到界面上的某个控件的某个事件即可,如需要点击 read 按钮更新 model 对象的 m_prop 属性,那么将 read 命令绑定到 read 按钮的 click 事件,命令参数设置为 m_prop 即可:

图2.72 绑定同步触发读命令
图2.72 绑定同步触发读命令

运行后点击 read 按钮得到的效果:

图2.73 绑定同步触发读命令结果
图2.73 绑定同步触发读命令结果

同理 write 命令的绑定也是一样,不再赘述。

(2) 定时读写

定时读写是根据时间间隔进行周期性读写。实现定时读写首先需要为窗口绑定命令,在打开窗口时打开定时器(其参数为定时时间间隔,单位毫秒),在关闭窗口时关闭定时器。(想了解更多关于 FScript 定时器相关的函数,参阅 fscript 的 widget 扩展函数 (opens new window))。

然后将读写命令绑定到窗口的 timer 事件上即可,这里只展示绑定 read 命令,write 命令的绑定也是一样的,不再赘述:

图2.74 绑定同步定时读命令
图2.74 绑定同步定时读命令

运行后得到的结果:

图2.75 绑定同步定时读命令结果
图2.75 绑定同步定时读命令结果

# 2.4.2 异步读写模式

异步读写模式相较于同步读写模式解决了在读写外部数据时,UI 线程会阻塞的问题,但同时代码也会变得更复杂,由于代码较为复杂,下文将由伪代码的形式讲解如何实现。

首先需要 application.c 处在程序运行增加读写线程(如不知该文件位置,可以看上文项目的目录结构),然后在函数内实现读写逻辑即可。(临界资源会在 UI 线程和读写数据线程中进行读写,所以需要互斥锁保证线程间不会同时对临界资源进行读写):

/* application.c */

static ret_t application_on_launch(void) {
  objects_manager_set(objects_manager_create());

  /* 创建临界资源并创建互斥锁 */
  /* 创建读写数据线程并启动 */

  return RET_OK;
}

读写线程内部实现

读写线程内部实现按照下图实现即可:

图2.76 线程读写逻辑图
图2.76 线程读写逻辑图

要注意的是模型对象数据和临界资源的数据交换需要放到 UI 线程里执行,因为如果放在非 UI 线程执行该操作时,用户操作界面时也会在 UI 线程中读写模型对象数据,这样就会让两条线程同时操作同一份数据,这种操作是不被允许的。idle_queue() 可以用于非 GUI 线程增加一个idle,向 UI 线程主循环的事件队列中发送一个增加 idle 的请求,在 UI 线程里下一帧执行 idle 的回调函数。实现上图中的逻辑后,还可以在使用 idle_queue() 完成需补充的 UI 逻辑。

以下是读线程读取数据的伪代码:

/* application.c */

static bool_t g_is_quit_thread = FALSE; /* 关闭线程标志位 */
model_t g_tmp_model;                    /* 临界资源 */
tk_mutex_t* g_tmp_model_mutex = NULL;   /* 临界资源互斥锁 */

/* 通知界面更新 */
static ret_t update_view(const idle_info_t* idle) {
  tk_object_t* model = TK_OBJECT(idle->ctx);

  /* 为 model 分发属性改变事件,通知 UI 界面更新 */
  emitter_dispatch_simple_event(&model->emitter, EVT_PROPS_CHANGED);

  return RET_OK;
}

/* 弹出警告对话框 */
static ret_t pop_warn_dialog(const idle_info_t* idle) {
  const char* log = (const char*)(idle->ctx);

  dialog_warn(NULL, log);

  return RET_OK;
}

/* 模型对象读取临界资源数据 */
static ret_t read_tmp_data(const idle_info_t* idle) {
  model_t* model = (model_t*)(idle->ctx);

  tk_mutex_lock(g_tmp_model_mutex);
  /* model 读取临界资源数据,调用 update_view() 函数可将 model 数据更新到 UI 界面上 */
  /* model->m_prop = g_tmp_model.m_prop */
  tk_mutex_unlock(g_tmp_model_mutex);

  return RET_OK;
}

/* 临界资源读取外部数据 */
static ret_t read_external_data(void) {
  tk_mutex_lock(g_tmp_model_mutex);
  /* 临界资源读取外部数据(如网络数据、本地文件数据) */
  /* g_tmp_model.m_prop = xxx */
  tk_mutex_unlock(g_tmp_model_mutex);

  return RET_OK;
}

/* 读线程 */
static void* read_thread_entry(void* args) {
  ret_t ret = RET_SKIP;
  objects_manager_t* om = OBJECTS_MANAGER(objects_manager());
  tk_object_t* model = om->model; /* 模型对象 */

  while (!g_is_quit_thread) {
    /* 临界资源读取外部数据 */
    ret = read_external_data();

    if (ret == RET_OK) {
      /* 临界资源读取外部数据成功时,让模型对象读取临界资源,并通知界面更新 */
      idle_queue(read_tmp_data, model);
      idle_queue(update_view, model);
    } else {
      /* 临界资源读取外部数据失败时,弹出对话框通知 */
      idle_queue(pop_warn_dialog, "Read Fail!");
    }
    sleep_ms(100);
  }

  return NULL;
}

以下是写线程写入数据的伪代码:

/* application.c */

bool_t g_write_data_req = FALSE; /* 写数据请求 */

/* 临界资源写入外部数据 */
static ret_t write_external_data(void) {
  ret_t ret = RET_SKIP;
  tk_mutex_lock(g_tmp_model_mutex);
  if (g_write_data_req) { /* 是否有写数据请求,如没有则跳过 */
    /* 临界资源写入外部数据,如将临界资源数据上传到服务器上 */
    /* xxx = g_tmp_model.m_prop */
    g_write_data_req = FALSE;  /* 完成写数据请求 */
    ret = RET_OK;
  }
  tk_mutex_unlock(g_tmp_model_mutex);

  return ret;
}

/* 写线程 */
static void* write_thread_entry(void* args) {
  ret_t ret = RET_SKIP;
  objects_manager_t* om = OBJECTS_MANAGER(objects_manager());
  tk_object_t* model = om->model; /* 模型对象 */

  while (!g_is_quit_thread) {
    /* 临界资源写入外部数据 */
    ret = write_external_data();
    if (ret != RET_SKIP) {
      /* 通知 UI 写入成功或失败 */
      if (ret == RET_OK) {
        idle_queue(pop_warn_dialog, "upload Succeed!");
      } else {
        idle_queue(pop_warn_dialog, "upload Fail!");
      }
    }
    sleep_ms(100);
  }
  return NULL;
}

在 model 增加 write 命令,增加 write 命令可以参考上文同步读写模式。

/* model_impl.inc */

extern model_t g_tmp_model;
extern tk_mutex_t* g_tmp_model_mutex;
extern bool_t g_write_data_req;

ret_t model_write(tk_object_t* obj, const char* args) {
  model_t* model = MODEL(obj);
  return_value_if_fail(model != NULL, RET_BAD_PARAMS);

  tk_mutex_lock(g_tmp_model_mutex);
  /* 将模型对象数据写入到临界资源中 */
  g_tmp_model.m_prop = tk_object_get_prop_uint32(obj, "m_prop", 0);

  g_write_data_req = TRUE; /* 请求将临界资源写入外部数据 */
  tk_mutex_unlock(g_tmp_model_mutex);

  return RET_OK;
}