# 3. MVVM-C案例分析
上一章讲解了如何制作 MVVM-C 项目,为了加深大家对 MVVM-C 的理解,本章将以 Mvvm-C-Demo 为例进行 MVVM-C 项目的案例分析,该案例主要功能如下:
- 显示多个设备。
- 可以增加和删除设备。
- 解锁后可以修改设备类型和设备参数。
- 可以还原和清空设备列表。
- 不同设备类型显示不同数据,下图为案例项目中的设备参数表:

界面及功能如图所示:

# 3.1 模型设计
首先使用 AWTK Designer 新建一个MVVM-C 项目工程,步骤详见上文 2.1 章节,然后判断该项目是否需要增加模型,根据上文案例项目的需要的功能,显然是需要增加一个设备模型,此处将该模型名为"device",该模型包含设备类型、设备参数和数据,具体详见下表:
属性名称 | 类型 | 描述 | 备注 |
---|---|---|---|
pack_type | device_type_t | 设备类型 | 自定义枚举类型,影响设备数据 |
pack_params | device_params_t | 设备参数 | 自定义枚举类型 |
io1 | bool_t | 设备数据 | 随机生成,由设备类型决定是否显示 |
io1 | bool_t | 设备数据 | 随机生成,由设备类型决定是否显示 |
a1 | int32_t | 设备数据 | 随机生成,由设备类型决定是否显示 |
a2 | int32_t | 设备数据 | 随机生成,由设备类型决定是否显示 |
temp | double | 设备数据 | 随机生成,由设备类型决定是否显示 |
tps | double | 设备数据 | 随机生成,由设备类型决定是否显示 |
本案例中"device"模型的 pack_type 属性和 pack_params 属性均为自定义的枚举类型,实现方式详见下一小节,其中 pack_type 属性将决定该设备显示哪些数据,为了方便演示效果,这些设备数据均是随机生成的。
# 3.1.1 增加自定义枚举类型
为了代码拥有更好的可读性,可以将设备类型和设备参数自定义为一种枚举类型,打开项目目录下“src/models/model_types_def.h”,在该文件中新增自定义枚举类型(注意:注释不可删除并且需要按照格式写,否则在 AWTK Designer 下无法找到该枚举类型)。
设备类型:
- PACK_SKP_1000
- PACK_SKP_2000
- PACK_SKP_3042
- PACK_SKP_3132
- PACK_SKP_3142
- PACK_SKP_5002
在代码处增加设备类型的枚举类型:
/* src/models/model_types_def.h */
/**
* @enum device_type_t
* @prefix DEV_TYPE_
*/
typedef enum _device_type_t {
/**
* @const DEV_TYPE_PACK_SKP_1000
*/
DEV_TYPE_PACK_SKP_1000 = 0,
/**
* @const DEV_TYPE_PACK_SKP_2000
*/
DEV_TYPE_PACK_SKP_2000,
/**
* @const DEV_TYPE_PACK_SKP_3042
*/
DEV_TYPE_PACK_SKP_3042,
/**
* @const DEV_TYPE_PACK_SKP_3132
*/
DEV_TYPE_PACK_SKP_3132,
/**
* @const DEV_TYPE_PACK_SKP_3142
*/
DEV_TYPE_PACK_SKP_3142,
/**
* @const DEV_TYPE_PACK_SKP_5002
*/
DEV_TYPE_PACK_SKP_5002,
DEV_TYPE_MAX_COUNT
} device_type_t;
设备参数类型:
- NONE
- NTC_2252K
- NTC_5K
- NTC_10K
在代码处增加设备参数类型的枚举类型:
/* src/models/model_types_def.h */
/**
* @enum device_params_t
* @prefix DEV_PARAMS_
*/
typedef enum _device_params_t {
/**
* @const DEV_PARAMS_NONE
*/
DEV_PARAMS_NONE = 0,
/**
* @const DEV_PARAMS_NTC_2252K
*/
DEV_PARAMS_NTC_2252K,
/**
* @const DEV_PARAMS_NTC_5K
*/
DEV_PARAMS_NTC_5K,
/**
* @const DEV_PARAMS_NTC_10K
*/
DEV_PARAMS_NTC_10K,
DEV_PARAMS_MAX_COUNT
} device_params_t;
# 3.1.2 新增设备模型
最后按照案例的需求,新增“设备”模型:device,步骤详见上文 2.3.1 章节。

在 AWTK Designer 新增 device 模型保存后,会自动在项目目录下“src/models”生成 device 模型的相关代码。
# 3.2 视图模型设计
接下来是为主界面 home_page 新建一个视图模型 home_page_view_model,步骤详见上文 2.2.1 章节,在 AWTK Designer 新增视图模型 home_page_view_model 保存后,会自动在项目目录下“src/view_models”生成 home_page_view_model 的相关代码。
# 3.2.1 为视图模型增加属性
根据案例项目的需要的功能,为视图模型增加下图属性:

items 对象数组和 current_index 属性在实现命令功能时就会使用,而 unlocked 属性会在讲解界面设计时才会使用。
# 3.2.2 为视图模型增加命令
根据案例项目的需要的功能,为视图模型增加下图命令:

最后需要跳转到命令代码,实现命令功能函数和判断命令是否可执行的函数。
# 3.2.3 实现设置当前设备序号功能
该功能是用于设置设备插入或删除序号,是实现插入设备功能和删除设备功能的前提功能,通过执行 setCurrent 命令,更改 current_index 属性的值。
/* src/view_models/home_page_view_model_impl.inc */
static bool_t home_page_view_model_can_setCurrent(tk_object_t* obj, const char* args) {
home_page_view_model_t* model = HOME_PAGE_VIEW_MODEL(obj);
(void)args;
return_value_if_fail(model != NULL, FALSE);
return TRUE;
}
ret_t home_page_view_model_setCurrent(tk_object_t* obj, const char* args) {
home_page_view_model_t* model = HOME_PAGE_VIEW_MODEL(obj);
tk_object_t* a = object_default_create();
(void)args;
return_value_if_fail(model != NULL && a != NULL, RET_BAD_PARAMS);
/* 将字符串类型的命令参数转换为 object 对象 */
tk_command_arguments_to_object(args, a);
/* 更新当前选中设备序号 */
model->current_index = tk_object_get_prop_int32(a, "index", -1);
TK_OBJECT_UNREF(a);
return RET_OBJECT_CHANGED;
}
# 3.2.4 实现插入设备功能
该功能是将新增设备插入到设备列表里当前选中设备序号处,通过执行 insert 命令,将新建的 device 设备对象,插入到 items 对象数组里的 current_index (该值通过设置当前设备序号功能修改) 处。
/* src/view_models/home_page_view_model_impl.inc */
static bool_t home_page_view_model_can_insert(tk_object_t* obj, const char* args) {
home_page_view_model_t* model = HOME_PAGE_VIEW_MODEL(obj);
object_array_t* items = NULL;
(void)args;
return_value_if_fail(model != NULL, FALSE);
items = OBJECT_ARRAY(model->items);
return_value_if_fail(items != NULL, FALSE);
/* 当前选中设备序号合法时,才可以使用 insert 命令 */
return model->current_index >= 0 && model->current_index < items->size;
}
ret_t home_page_view_model_insert(tk_object_t* obj, const char* args) {
home_page_view_model_t* model = HOME_PAGE_VIEW_MODEL(obj);
value_t v;
tk_object_t* device = NULL;
ret_t ret = RET_OK;
(void)args;
return_value_if_fail(model != NULL && model->items != NULL, RET_BAD_PARAMS);
/* 创建设备并插入到设备列表里当前选中设备序号处 */
device = device_create();
return_value_if_fail(device != NULL, RET_FAIL);
value_set_object(&v, device);
emitter_on(EMITTER(device), EVT_PROP_CHANGED, emitter_forward, model);
ret = object_array_insert(model->items, model->current_index, &v);
TK_OBJECT_UNREF(device);
return ret;
}
# 3.2.5 实现移除设备功能
该功能是将设备列表里当前选中设备序号处的设备移除,通过执行 remove 命令,将 items 对象数组里的 current_index (该值通过设置当前设备序号功能修改) 处 device 设备对象移除。
/* src/view_models/home_page_view_model_impl.inc */
static bool_t home_page_view_model_can_remove(tk_object_t* obj, const char* args) {
home_page_view_model_t* model = HOME_PAGE_VIEW_MODEL(obj);
object_array_t* items = NULL;
(void)args;
return_value_if_fail(model != NULL, FALSE);
items = OBJECT_ARRAY(model->items);
return_value_if_fail(items != NULL, FALSE);
/* 当前选中设备序号合法时,才可以使用 remove 命令 */
return model->current_index >= 0 && model->current_index < items->size;
}
ret_t home_page_view_model_remove(tk_object_t* obj, const char* args) {
home_page_view_model_t* model = HOME_PAGE_VIEW_MODEL(obj);
(void)args;
return_value_if_fail(model != NULL && model->items != NULL, RET_BAD_PARAMS);
/* 删除设备列表里当前选中设备序号处的设备 */
return object_array_remove(model->items, model->current_index);
}
# 3.2.6 实现清除设备列表功能
该功能是将设备列表里的所有设备,通过执行 clear 命令,将 items 对象数组里的所有 device 设备对象移除。
/* src/view_models/home_page_view_model_impl.inc */
static bool_t home_page_view_model_can_clear(tk_object_t* obj, const char* args) {
home_page_view_model_t* model = HOME_PAGE_VIEW_MODEL(obj);
object_array_t* items = NULL;
(void)args;
return_value_if_fail(model != NULL, FALSE);
items = OBJECT_ARRAY(model->items);
return_value_if_fail(items != NULL, FALSE);
/* 设备列表不为空时,才可以使用 clear 命令 */
return items->size > 0;
}
ret_t home_page_view_model_clear(tk_object_t* obj, const char* args) {
home_page_view_model_t* model = HOME_PAGE_VIEW_MODEL(obj);
return_value_if_fail(model != NULL && model->items != NULL, RET_BAD_PARAMS);
(void)args;
/* 清空设备列表 */
return object_array_clear_props(model->items);
}
# 3.2.7 实现重置设备列表功能
该功能是将重置设备列表,为了方便演示效果,该功能的实现是插入50个属性随机的设备对象到设备列表中,通过执行 reset 命令,在 items 对象数组里增加50个属性随机的 device 设备对象。
/* src/view_models/home_page_view_model_impl.inc */
static bool_t home_page_view_model_can_reset(tk_object_t* obj, const char* args) {
home_page_view_model_t* model = HOME_PAGE_VIEW_MODEL(obj);
object_array_t* items = NULL;
return_value_if_fail(model != NULL, FALSE);
items = OBJECT_ARRAY(model->items);
return_value_if_fail(items != NULL, FALSE);
/* 设备列表为空时,才可以使用 reset 命令 */
return items->size == 0;
}
ret_t home_page_view_model_reset(tk_object_t* obj, const char* args) {
home_page_view_model_t* model = HOME_PAGE_VIEW_MODEL(obj);
ret_t ret = RET_OK;
uint32_t i = 0;
(void)args;
return_value_if_fail(model != NULL && model->items != NULL, RET_BAD_PARAMS);
/* 插入50个属性随机的设备对象到设备列表中 */
for (i = 0; i < 50 && ret == RET_OK; i++) {
tk_object_t* device = device_create();
if (device != NULL) {
value_t v;
tk_object_set_prop_int(device, "pack_type", random() % DEV_TYPE_MAX_COUNT);
tk_object_set_prop_int(device, "pack_params", random() % DEV_PARAMS_MAX_COUNT);
tk_object_set_prop_bool(device, "io1", random() % 2 == 0);
tk_object_set_prop_bool(device, "io2", random() % 2 == 0);
tk_object_set_prop_double(device, "temp", random() / 10.0);
tk_object_set_prop_int32(device, "a1", random());
tk_object_set_prop_int32(device, "a2", random());
tk_object_set_prop_double(device, "tps", random() / 10.0);
value_set_object(&v, device);
object_array_push(model->items, &v);
emitter_on(EMITTER(device), EVT_PROP_CHANGED, emitter_forward, model);
TK_OBJECT_UNREF(device);
} else {
ret = RET_FAIL;
}
}
return ret;
}
# 3.3 界面设计
最后是界面设计,在案例项目里需要显示多个设备,使用列表视图是一个不错的选择,按照《AWTK-Designer快速使用指南》进行界面设计,可以得到设备列表界面的雏形:

# 3.3.1 列表渲染
列表渲染可将指定控件作为模板,将列表中的各项数据进行重复渲染。下一步将视图模型 home_page_view_model 中的 items 设备列表数组绑定到 list_item 列表项控件的列表渲染规则中:
步骤一:点击下图框选位置的按钮:

步骤二:在"绑定的数组"中点击选择 items 设备列表数组(上文在视图模型中添加的属性),或者点击右侧按钮进行更详细地设定,之后按确定完成绑定:

列表渲染设置完成后,在 list_item 控件对象中点击的下图红色处就会有个小图标显示:
接下来在 list_item 控件及其子控件中可以通过 index 变量名访问当前项下标,item 变量名访问当前项的数组元素,在该案例项目里其元素就是 device 模型对象。
注意:list_item 是界面中的列表项控件;items 是在视图模型中添加的object_array类型属性,意为设备列表数组,用于存取设备;item 为条件渲染规则中用于获取绑定变量数组当前项元素的属性,注意不要混淆。
下一步给 list_item 设置子控件布局,在下文会有作用:

下一步给 list_item 增加子控件,通过数据绑定(步骤详见上文 2.2.3 章节),就可以在子控件显示列表当前项下标和设备信息了。
label 控件 的 text 属性绑定 index 变量用于显示列表当前项下标:

combo box 控件 的 value 属性绑定 item.pack_type 变量用于给用户选择当前项设备的设备类型(item.pack_type 表示 device 模型对象的 pack_type 属性):

按照以上方法将设备固定部分的信息绑定绑定完成后,效果如下图:

还有其他设备数据会根据设备类型的不同,所显示的内容也不同,这时需要用到条件渲染。
# 3.3.2 条件渲染
条件渲染可以根据不同的条件进行不同的渲染。下一步使用条件渲染实现不同设备类型显示不同数据的功能点:

要想实现上图的效果,首先设计用于显示设备数据的控件组合,并用容器 view 将其归类:

将这四个容器放到 list_item 中,在 list_item 子控件布局的作用下,会发现这四个容器会超出 list_item 的显示范围:
下一步给这四个容器设置条件渲染规则,运行时只有符合条件的那个容器才会被创建,从而达到不同条件下显示不同内容的效果。
步骤一:选择第一个容器,点击框选按钮:

步骤二:在弹出对话框里点击框选处:

步骤三:在弹出对话框中设置渲染条件,图中框选条件意思为当前设备类型为0(DEV_TYPE_PACK_SKP_1000),这渲染该控件,设置完成后点击确定:


步骤四:接着设置下一个容器,和步骤二、步骤三的操作相同:

步骤五:到最后一个容器,就可以选择 else 判断,在前三个容器都不符合条件时,就会渲染该容器,之后点击确定按钮保存渲染规则:

设置完成后,就会出现图中框选效果:

之后为容器中控件绑定当前项设备的设备属性即可,如将设备中的 io2 属性绑定到 check button 控件的 value 属性上:

如将设备中的 temp 属性绑定到 label 控件的 text 属性上,并对显示内容格式化:

目前为止,已经实现了显示多个设备和不同设备类型显示不同数据的功能点,下一步实现解锁后才可以修改设备类型和设备参数这一功能:
增加 check button 控件,将 unlocked 属性绑定到 check button 控件的 value 属性:

再将 unlocked 属性绑定到用于选择设备类型的 combo box 的 enable 属性,就可以实现通过 check button 控制设备类型能否被修改(控制设备参数能否被修改和该操作类似,这里就不赘述了):

下一步实现设备的增加和删除功能,首先增加两个按钮,一个用于增加并插入设备,一个用于删除选中设备,绑定在视图模型设计好的 insert 命令和 remove 命令到对应的按钮的点击事件上,以绑定 insert 命令为例:

还需要将 setCurrent 命令绑定到 list_item 的点击事件上,实现通过点击 list_item 选中设备插入或删除序号(这里的参数是以fscript表达式形式的参数序列传入的,其意思是给名称为"index"参数赋值为列表当前项下标(index),想了解更多请参阅 AWTK-MVVM 命令绑定 (opens new window)):

最后一步实现还原和清空设备列表功能:在界面处增加两个按钮,一个用于还原设备列表,一个用于清空设备列表,绑定在视图模型设计好的 reset 命令和 clear 命令到对应的按钮的点击事件上即可,以绑定 clear 为例:

到这一步,案例项目的所有功能就完成了。
# 3.4 运行效果
最后将项目打包并编译,点击模拟运行,即可看到其运行效果:

