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.pswp.cn/web/75543.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/75543.shtml
英文地址,請注明出處:http://en.pswp.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…