AWTK-MVVM 如何让多个View复用一个Model记录+关于app_conf的踩坑

前言

有这么一个业务,主界面点击应用窗口进入声纳显示界面,声纳显示界面再通过按钮进入菜单界面,菜单界面有很多关于该声纳显示界面的设置项,比如量程,增益,时间显示,亮度,对比度等等,大概十几个设置。

有些数值类的设置还有子预览菜单,在子预览菜单里面通过滑条去设置数值,回到菜单后,设置会显示子预览菜单设置的数值。

声纳显示界面需要显示一些菜单的设置,比如量程,增益等等。

也就是大概这么一个页面关系,其中后面三个页面之间还有数据依赖的关系。

image-20250412120317482

由于菜单的设置项非常多,用传统的基于控件树的方法写起来代码量很大,而且美工时常改动菜单UI,容易影响界面代码,我当时自然而然的选择了AWTK特有的MVVM框架来完成菜单设置的显示逻辑。

一开始由于赶项目时间,我就直接在界面上使用了mvvm的app_conf功能,app_conf在AWTK-MVVM还有专门的model,使得不用写代码就能完成配置文件与多个界面间的数据联动与设置保存,十分贴心。

虽然这种方法能够快速实现功能,但是后期维护性极差,因为业务的配置key都写死在xml中,跟xml耦合。

比如这样的一个设置项:

<window v-model="m_home" name="filter_color"><view name="v_setting_color" x="175" y="0" w="387" h="183"><label name="title" x="9" y="10" w="58" h="28" text="Color"/><view name="view" x="34" y="58" w="359" h="112" children_layout="default(c=3,r=3)"><radio_button name="radio_button" v-data:value="{A.color==1}" text="color1"/><radio_button name="radio_button" v-data:value="{A.color==2}" text="color2"/><radio_button name="radio_button" v-data:value="{A.color==3}" text="color3"/><radio_button name="radio_button" v-data:value="{A.color==4}" text="color4"/><radio_button name="radio_button" v-data:value="{A.color==5}" text="color5"/><radio_button name="radio_button" v-data:value="{A.color==6}" text="color6"/><radio_button name="radio_button" v-data:value="{A.color==7}" text="color7"/><radio_button name="radio_button" v-data:value="{A.color==8}" text="color8"/></view></view>
</window>

实际业务是三种不同的声纳模式的设置菜单A, B, C, 三个设置菜单每个界面都有十几个行十几个设置项,加起来就是三十几个设置项,而且这些设置项的UI大都重复。

每个声纳的大部分属性还是各自独立的,也就是不同菜单的同一个设置,上面的color,A,B,C菜单都在用,A用A.color, B就是B.color,同样的key,父路径不同,这就导致没法用AWTK的component机制将这些设置项UI抽象出来复用,变成这样:

<window v-model="m_home" name="filter_color"><?include filename="comp_setting_color.xml" ?>
</window>

(话说AWTK的这个组件机制真的鸡肋,就是单纯的include替换,连个內部slot, 组件通信也没有)

如果用上述的绑死app_conf key的方式来做,后面一旦加了什么影响UI的新功能或者菜单风格更改,又要一个个菜单的去照着美工原型图去改,十分痛苦。

而且app_conf模型自身的命令绑定十分有限,稍微复杂一点的需求(比如点击按钮发送MQTT)就做不了,还是要结合自定义model的命令绑定或者传统的基于控件名索引的widget_on, 自己写函数

等到项目周期开始放缓,我决心把之前写死了配置key的几个菜单页面给重构了,改成用自定义model的自定义的属性来做数据绑定,在代码层面实现具体的选择菜单A,B,C的逻辑。

重构跟本文的逻辑不大,就不展开了,我在这边引出这些,是因为之前直接用app_conf有一个优点,就是不用关心页面之间数据联动的问题,awtk-mvvm内部代码自己会处理好,如果用自定义model, 就要写代码理清窗口导航的数据流通关系了,确保窗口退出时返回正确的设置数据给上一个页面。

实践

回到这个界面关系中,由于显示界面,设置菜单,子菜单都指向同一个对象的数据,考虑到三个页面后期可能的变动,我索性让三个页面都使用同一个model了。

我的目标是,弄清楚三个页面使用同一个model之后窗口的传参如何处理,才能实现子菜单设置时能够返回保存的数据。

抽象的例子如下,所有界面绑定一个叫m_home的model。

image-20250412170404083

sonar_page有bottom_lock,noise_limiter,pic_advance三个界面,每个界面都是在独立的子菜单中设置,设置完的结果会在sonar_page上显示。

<window v-model="m_home" name="sonar_page"><button name="button" x="272" y="320" w="100" h="36" v-on:click="{mreturn}" text="Back"/><label name="value" x="272" y="49" w="160" h="28" v-data:text="{value_int}"/><label name="key" x="272" y="104" w="160" h="28" v-data:text="{noise_limiter}" text="setting_item"/><label name="key" x="272" y="178" w="160" h="28" v-data:text="{pic_advance}" text="setting_item"/><button name="button1" x="101" y="49" w="100" h="36" text="Button"  v-on:click="{home_navigate, Args=string?page_name=bottom_lock}"/><button name="button1" x="101" y="104" w="100" h="36" text="Button" v-on:click="{home_navigate, Args=string?page_name=noise_limiter}"/><button name="button1" x="101" y="170" w="100" h="36" text="Button" v-on:click="{home_navigate, Args=string?page_name=pic_advance}"/>
</window>

一开始我犯了个错误,感觉一个页面重复建相同的Model,旧Model还要拷贝数据到新Model, 开销比较大,就把Model的创建和销毁搞成了引用计数的模式:

m_home_t *last_page_model = NULL;
m_home_t *g_home_ref = NULL;
static int g_home_ref_count = 0;m_home_t* m_home_create(navigator_request_t* req)
{m_home_t *home = NULL;if(!g_home_ref){home = TKMEM_ZALLOC(m_home_t);str_init(&home->pic_advance, 32);}else{home = g_home_ref;}g_home_ref = home;g_home_ref_count++;home->req = req;printf("m_home=%#x created, value: %d ref_count: %d\r\n", home, home->value_int, g_home_ref_count);return home;
}ret_t m_home_on_return(navigator_request_t* req, const value_t* result)
{m_home_t *home = g_home_ref;printf("set last model %p\r\n", home);emitter_dispatch_simple_event(EMITTER(home), EVT_PROPS_CHANGED);return RET_OK;
}ret_t m_home_mreturn(m_home_t *home)
{value_t v;navigator_request_on_result(home->req, &v);navigator_back();return RET_OK;
}ret_t m_home_destroy(m_home_t* home)
{g_home_ref_count--;if(g_home_ref_count == 0){str_reset(&home->pic_advance);TKMEM_FREE(home);g_home_ref = NULL;}printf("m_home=%#x destroyed\r\n", home);return RET_OK;  
}

但是后面发现子菜单上设置的值返回后无法在sonar_page上显示,查了半天,才发现m_home_on_return设置的其实是只跟当前界面有关的view_model,一旦返回这个页面就销毁了,根本影响不到上一个页面的view model。

image-20250412172638543

只好老实了,乖乖用默认的一个view一个model的传统构建方法,导航到新页面时把旧model作为参数, 传给新model,新model拷贝旧model的参数,退出页面时,旧model从新model加载数据。

实际业务是进页面从app_conf load数据,退页面save 数据到app_conf,然后旧model再从app_conf save数据的,这个例子里面省略了,直接copy对象来表示。

#include "m_home.h"
#include "awtk.h"
#include "mvvm/mvvm.h"
#include "mvvm/base/utils.h"m_home_t *last_page_model = NULL;
m_home_t *g_current_home_ref = NULL;void m_home_data_copy(m_home_t *ahome, m_home_t *bhome)
{ahome->value_int = bhome->value_int;str_set(&ahome->pic_advance, bhome->pic_advance.str);ahome->noise_limiter = bhome->noise_limiter;
}m_home_t* m_home_create(navigator_request_t* req)
{m_home_t *home = NULL;home = TKMEM_ZALLOC(m_home_t);str_init(&home->pic_advance, 32);m_home_t *last_model = tk_object_get_prop_pointer(TK_OBJECT(req), "last_model");if(last_model != NULL){m_home_data_copy(home, last_model);}g_current_home_ref = home;home->req = req;printf("m_home=%p created, value: %d last_model: %p\r\n", home, home->value_int, last_model);return home;
}ret_t m_home_destroy(m_home_t* home)
{str_reset(&home->pic_advance);TKMEM_FREE(home);g_current_home_ref = NULL;printf("m_home=%#x destroyed\r\n", home);return RET_OK;  
}ret_t m_home_on_return(navigator_request_t* req, const value_t* result)
{m_home_t *home = g_current_home_ref;m_home_t *last_model = tk_object_get_prop_pointer(TK_OBJECT(req), "last_model");printf("set last model %p\r\n", last_model);if(last_model != NULL){m_home_data_copy(last_model, home);emitter_dispatch_simple_event(EMITTER(last_model), EVT_PROPS_CHANGED);}return RET_OK;
}ret_t m_home_mreturn(m_home_t *home)
{value_t v;g_current_home_ref = home;navigator_request_on_result(home->req, &v);navigator_back();return RET_OK;
}ret_t m_home_to_navigate(m_home_t *home, const char *args)
{   tk_object_t *obj = object_default_create();tk_command_arguments_to_object(args, obj);const char *page_name = tk_object_get_prop_str(obj, "page_name");navigator_request_t* req = navigator_request_create(page_name, m_home_on_return);tk_object_set_prop_pointer(TK_OBJECT(req), "last_model", home);navigator_to_ex(req);tk_object_unref(TK_OBJECT(req));return RET_OK;
}ret_t m_home_set_prop_int(m_home_t *home, const char *args)
{tk_object_t *obj = object_default_create();tk_command_arguments_to_object(args, obj);const char *key = tk_object_get_prop_str(obj, "key");int32_t value = tk_object_get_prop_int(obj, "value", 0);if(tk_str_eq(key, "noise_limiter")){home->noise_limiter = value;}else{home->value_int = value;}printf("m_home_set_prop_int: %s = %d\r\n", key, value);TK_OBJECT_UNREF(obj);return RET_OBJECT_CHANGED;
}ret_t m_home_set_prop_str(m_home_t *home, const char *args)
{tk_object_t *obj = object_default_create();tk_command_arguments_to_object(args, obj);const char *key = tk_object_get_prop_str(obj, "key");const char *value = tk_object_get_prop_str(obj, "value");if(tk_str_eq(key, "pic_advance")){str_set(&home->pic_advance, value);printf("pic_advance set %s\r\n", home->pic_advance.str);}printf("m_home_set_prop_str: %s = %s\r\n", key, value);TK_OBJECT_UNREF(obj);return RET_OBJECT_CHANGED;
}

逻辑展示如下:

image-20250412174251820

懒得展开了,放上代码:

https://gitee.com/tracker647/awtk-practice/tree/master/awtk_mvvm_shared_model_return_test

效果:

image-20250412182014755

image-20250412182027504

附录:关于sub_view_model

虽然app_conf有一个sub_view_model的功能可以缓解不同object有一样的配置key的问题,但是实际业务里配置既有私有配置也有共通配置,共通配置还是混杂在私有配置里面的,配置文件的结构是这样:

shared_conf:{range_mode:1range_val:10
};
A:{color:1
}
B:{color:2
}
C:{color:3
}

设置项的位置表现上,是这种情况:

私有属性
共有属性
私有属性
共有属性

如果使用sub_view_model,就要另外给相关的配置包上带sub_view_model属性的view标签。

上面的例子就要包两次sub_view_model标签,对于之前业务那种设置项多的情况就是会建立很多个冗余的只用于限定设置作用域的model, 程序上十分不优雅且有不稳定的风险,我找了一圈AWTK库,没有找到在sub_view_model的标签作用域里引用父级model来索引到公共属性的方法,只好放弃。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/75543.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CrystalDiskInfo电脑硬盘监控工具 v9.6.0中文绿色便携版

前言 CrystalDiskInfo是一个不用花钱的硬盘小帮手软件&#xff0c;它可以帮你看看你的电脑硬盘工作得怎么样&#xff0c;健不健康。这个软件能显示硬盘的温度高不高、还有多少地方没用、传输东西快不快等等好多信息。用了它&#xff0c;你就能很容易地知道硬盘现在是什么情况&…

数据分析-数据预处理

数据分析-数据预处理 处理重复值 duplicated( )查找重复值 import pandas as pd apd.DataFrame(data[[A,19],[B,19],[C,20],[A,19],[C,20]],columns[name,age]) print(a) print(--------------------------) aa.duplicated() print(a)只判断全局不判断每个 any() import p…

如何用海伦公式快速判断点在直线的哪一侧

一、海伦公式的定义与推导 1. 海伦公式的定义 海伦公式&#xff08;Heron’s Formula&#xff09;是用于计算三角形面积的一种方法&#xff0c;适用于已知三角形三边长度的情况。公式如下&#xff1a; S s ( s − a ) ( s − b ) ( s − c ) S \sqrt{s(s - a)(s - b)(s - c…

python推箱子游戏

,--^----------,--------,-----,-------^--,-------- 作者 yty---------------------------^----------_,-------, _________________________XXXXXX XXXXXX XXXXXX ______(XXXXXXXXXXXX(________(------ 0 [[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], [1,0,0,0,0,0,0,0,0,0,0,0,…

使用Python建模量子隧穿

引言 量子隧穿是量子力学中的一个非常有趣且令人神往的现象。在经典物理学中,我们通常认为粒子必须克服一个势垒才能通过它。但是,在量子力学中,粒子有时可以“穿越”一个势垒,即使它的能量不足以克服这个势垒。这种现象被称为“量子隧穿”。今天,我们将通过 Python 来建…

Vuex Actions 多参数传递的解决方案及介绍

Vuex Actions 多参数传递的解决方案及介绍 引言 在Vuex状态管理模式中&#xff0c;Actions 扮演着至关重要的角色。它主要用于处理异步操作&#xff0c;并且可以提交 Mutations 来修改全局状态。然而&#xff0c;在实际开发中&#xff0c;我们常常会遇到需要向 Actions 传递多…

设计模式 --- 策略模式

​策略模式&#xff08;Strategy Pattern&#xff09;是一种 ​​行为型设计模式​​&#xff0c;用于动态切换算法或策略​​&#xff0c;使得算法可以独立于客户端变化。它通过封装算法策略并使其可互换&#xff0c;提升了系统的灵活性和扩展性&#xff0c;尤其适用于需要多种…

【论文阅读】RMA: Rapid Motor Adaptation for Legged Robots

Paper: https://arxiv.org/abs/2107.04034Project: https://ashish-kmr.github.io/rma-legged-robots/Code: https://github.com/antonilo/rl_locomotion训练环境&#xff1a;Raisim 1.方法 RMA&#xff08;Rapid Motor Adaptation&#xff09;算法通过两阶段训练实现四足机器…

QQ风格客服聊天窗口

QQ风格客服聊天窗口 展示引入方式 展示 引入方式 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title&g…

【家政平台开发(37)】家政平台蜕变记:性能优化与代码重构揭秘

本【家政平台开发】专栏聚焦家政平台从 0 到 1 的全流程打造。从前期需求分析,剖析家政行业现状、挖掘用户需求与梳理功能要点,到系统设计阶段的架构选型、数据库构建,再到开发阶段各模块逐一实现。涵盖移动与 PC 端设计、接口开发及性能优化,测试阶段多维度保障平台质量,…

PostgreSQL 的 COPY 命令

PostgreSQL 的 COPY 命令 PostgreSQL 的 COPY 命令是高效数据导入导出的核心工具&#xff0c;性能远超常规 INSERT 语句。以下是 COPY 命令的深度解析&#xff1a; 一 COPY 命令基础 1.1 基本语法对比 命令类型语法示例执行位置文件访问权限服务器端COPYCOPY table FROM /p…

Sa-Token 自定义插件 —— SPI 机制讲解(一)

前言 博主在使用 Sa-Token 框架的过程中&#xff0c;越用越感叹框架设计的精妙。于是&#xff0c;最近在学习如何给 Sa-Token 贡献自定义框架。为 Sa-Token 的开源尽一份微不足道的力量。我将分三篇文章从 0 到 1 讲解如何为 Sa-Token 自定义一个插件&#xff0c;这一集将是前沿…

论文精度:基于LVNet的高效混合架构:多帧红外小目标检测新突破

论文地址:https://arxiv.org/pdf/2503.02220 目录 一、论文背景与结构 1.1 研究背景 1.2 论文结构 二、核心创新点解读 2.1 三大创新突破 2.2 创新结构原理 2.2.1 多尺度CNN前端 2.2.2 视频Transformer设计 三、代码复现指南 3.1 环境配置 3.2 数据集准备 3.3 训…

解决 Ubuntu 上 Docker 安装与网络问题:从禁用 IPv6 到配置代理

解决 Ubuntu 上 Docker 安装与网络问题的实践笔记 在 Ubuntu&#xff08;Noble 版本&#xff09;上安装 Docker 时&#xff0c;我遇到了两个常见的网络问题&#xff1a;apt-get update 失败和无法拉取 Docker 镜像。通过逐步排查和配置&#xff0c;最终成功运行 docker run he…

指针的进阶2

六、函数指针数组 字符指针数组 - 存放字符指针的数组 char* arr[10] 整型指针数组 - 存放整型指针的数组 int* arr[10] 函数指针数组 - 存放函数指针的数组 void my_strlen() {} int main() {//指针数组char* ch[5];int arr[10] {0};//pa是是数组指针int (*pa)[10] &…

速盾:高防CDN节点对收录有影响吗?

引言 搜索引擎收录是网站运营中至关重要的环节&#xff0c;它直接影响着网站的曝光度和流量。近年来&#xff0c;随着网络安全威胁的增加&#xff0c;许多企业开始采用高防CDN&#xff08;内容分发网络&#xff09;来保护其网站免受DDoS攻击和其他形式的网络攻击。然而&#x…

2025蓝桥杯省赛C/C++研究生组游记

前言 至少半年没写算法题了&#xff0c;手生了不少&#xff0c;由于python写太多导致行末老是忘记打分号&#xff0c;printf老是忘记写f&#xff0c;for和if的括号也老是忘写&#xff0c;差点连&&和||都忘记了。 题目都是回忆版本&#xff0c;可能有不准确的地方。 …

Quill富文本编辑器支持自定义字体(包括新旧两个版本,支持Windings 2字体)

文章目录 1 新版&#xff08;Quill2 以上版本&#xff09;2 旧版&#xff08;Quill1版本&#xff09; 1 新版&#xff08;Quill2 以上版本&#xff09; 注意&#xff1a;新版设置 style"font-family: Wingdings 2" 这种带空格的字体样式会被过滤掉&#xff0c;故需特…

dbt:新一代数据转换工具

dbt&#xff08;Data Build Tool&#xff09;一款专为数据分析和工程师设计的开源工具&#xff0c;专注于 ETL/ELT 流程的数据转换&#xff08;Transform&#xff09;环节&#xff0c;帮助用户以高效、可维护的方式将原始数据转换为适合分析的数据模型。 用户只需要编写查询&am…