AWTK-MVVM的一些使用技巧总结(1)
在项目中用了一段时间的AWTK-MVVM框架,由于AWTK-MVVM本身的文档十分欠缺,自己经过一段时间的研究折腾出了几个技巧,在此记录总结。
用fscript启用传统UI代码
AWTK-MVVM里面重新设计了navigator机制,重定位了navigator_to的调用方向,原版AWTK的navigator_to调用很简单,是直接向navigator中请求窗口并对窗口做初始化:
demo.exe!home_page_init(_widget_t * win, void * ctx) Line 25
demo.exe!navigator_window_init(const char * name, _widget_t * win, void * ctx) Line 10
demo.exe!navigator_window_open_and_close(const char * name, _widget_t * to_close, void * ctx) Line 32
demo.exe!navigator_to_with_context(const char * target, void * ctx) Line 51
demo.exe!navigator_to(const char * target) Line 45
demo.exe!application_init() Line 56
demo.exe!wWinMain(HINSTANCE__ * hinstance, HINSTANCE__ * hprevinstance, wchar_t * lpcmdline, int ncmdshow) Line 255
demo.exe!invoke_main() Line 123
demo.exe!__scrt_common_main_seh() Line 288
demo.exe!__scrt_common_main() Line 331
demo.exe!wWinMainCRTStartup(void * __formal) Line 17
kernel32.dll!00007fff4c88259d() (Unknown Source:0)
ntdll.dll!00007fff4dc6af78() (Unknown Source:0)
但是MVVM里面就重定义成了对窗口绑定的v-model的初始化,原先的窗口init函数没法直接进入了:
awtk.dll!tk_calloc(unsigned int nmemb, unsigned int size, const char * func, unsigned int line) Line 152
demo.exe!m_home_create(_navigator_request_t * req) Line 19
demo.exe!m_home_view_model_create(_navigator_request_t * req) Line 155
mvvm.dll!view_model_factory_create_model_one(const char * type, _navigator_request_t * req) Line 106
mvvm.dll!view_model_factory_create_model(const char * type, _navigator_request_t * req) Line 115
mvvm.dll!binding_context_awtk_create_one_view_model(_view_model_t * parent, const char * type, _navigator_request_t * req) Line 68
mvvm.dll!binding_context_awtk_create_view_model(_view_model_t * parent, const char * type, _navigator_request_t * req) Line 100
mvvm.dll!binding_context_awtk_create(_binding_context_t * parent, const char * vmodel, _navigator_request_t * req, _widget_t * widget) Line 1266
mvvm.dll!ui_loader_mvvm_build_widget(_ui_loader_mvvm_t * loader, _rbuffer_t * rbuffer, _ui_builder_t * builder, unsigned int * cursor, const _value_t * id) Line 515
mvvm.dll!ui_loader_mvvm_load_a_snippet(_ui_loader_mvvm_t * loader, _rbuffer_t * rbuffer, _ui_builder_t * builder, _widget_t * end_widget) Line 971
mvvm.dll!ui_loader_mvvm_load_bin(_ui_loader_t * l, const unsigned char * data, unsigned int size, _ui_builder_t * b) Line 1030
mvvm.dll!ui_loader_mvvm_load(_ui_loader_t * l, const unsigned char * data, unsigned int size, _ui_builder_t * b) Line 1074
mvvm.dll!ui_loader_mvvm_load_widget_with_parent(_navigator_request_t * req, _widget_t * parent) Line 1124
mvvm.dll!ui_loader_mvvm_load_widget(_navigator_request_t * req) Line 1091
mvvm.dll!navigator_handler_awtk_window_open_and_close(_navigator_request_t * req, _widget_t * to_close) Line 217
mvvm.dll!navigator_handler_awtk_window_open(_navigator_request_t * req) Line 253
mvvm.dll!navigator_handler_awtk_on_request(_navigator_handler_t * handler, _navigator_request_t * req) Line 321
mvvm.dll!navigator_handler_on_request(_navigator_handler_t * handler, _navigator_request_t * req) Line 51
mvvm.dll!navigator_handle_request(_navigator_t * nav, _navigator_request_t * req) Line 90
mvvm.dll!navigator_to(const char * args) Line 138
demo.exe!application_init() Line 67
demo.exe!wWinMain(HINSTANCE__ * hinstance, HINSTANCE__ * hprevinstance, wchar_t * lpcmdline, int ncmdshow) Line 255
demo.exe!invoke_main() Line 123
demo.exe!__scrt_common_main_seh() Line 288
demo.exe!__scrt_common_main() Line 331
demo.exe!wWinMainCRTStartup(void * __formal) Line 17
kernel32.dll!00007fff4c88259d() (Unknown Source:0)
ntdll.dll!00007fff4dc6af78() (Unknown Source:0)
有些场景还是需要直接操作界面widget来实现一些效果的,而model文件显然不能也不应该(否则UI和model代码就耦合了)直接获取这些widget,怎么办呢?
AWTK的fscript脚本提供了一个解决方案,步骤如下:
1.在需要使用界面代码的窗口顶部windows标签上,比如home_page.xml,声明一个on:window_open="func_navigator_window_init()"
<window v-model="m_home" name="home_page" on:window_open="func_navigator_window_init()">
...
</window>
2.在application.c上注册fscript函数。
ret_t application_init(void) {
...
fscript_register_func("func_navigator_window_init", func_navigator_window_init);
...
}
3.修改common/navigator.c,把navigator_window_init函数从#ifndef WITH_MVVM的宏范围里面提取出来,这是AWTK原版的窗口路由函数,func_navigator_window_init可以直接通过调用这个函数来路由到对应的窗口,缺点就是每次添加新窗口的时候都要手动修改下navigator_window_init内部代码。
common/navigator.c
extern ret_t home_page_init(widget_t* win, void* ctx);
ret_t navigator_window_init(const char* name, widget_t* win, void* ctx) {
if (tk_str_eq(name, "home_page")) {
return home_page_init(win, ctx);
}
return RET_OK;
}
ret_t func_navigator_window_init(fscript_t *fscript, fscript_args_t *args, value_t *result)
{
widget_t *widget = WIDGET(tk_object_get_prop_pointer(fscript->obj, STR_PROP_SELF));
widget_t *win = widget_get_window(widget);
return navigator_window_init(win->name, win, NULL);
}
#ifndef WITH_MVVM
...
这样程序启动的时候,窗口打开会触发fscript函数,在fscript里面能通过fscript自带的对象获取到绑定的widget指针,就能路由到原来的页面init文件了。
跨页面同步数据的model
有些页面, 比如声纳的画面->声纳菜单->某某设置条,几个页面数据来源大量重复, 而且这些页面之间还有线性导航关系,这个时候用AWTK-MVVM原来文档的数据同步的方法显得就有些麻烦了,进页面和出页面都要在给下一个窗口的传递对象中指明传输数据,两个页面就是两处修改,三个页面就是四处修改。
干脆让这些页面都采用同一个model好了,看起来更好处理,但是怎么让这几个页面的model在页面进出时都能共享上一个页面的数据呢?
实现如下,利用app_conf的全局持久化特性(也可以是自定义的用于持久化的类)来同步,用一个栈或者链表来给下一个页面的model传递同类的上一个页面的model。
static std::list<m_sonar_t*> g_model_list;
m_sonar_t* get_m_sonar_model(void) {
if(g_model_list.empty()){
return NULL;
}
//栈最顶层为当前的model
return g_model_list.back();
}
m_sonar_t* m_sonar_create() {
m_sonar_t* m_sonar = TKMEM_ZALLOC(m_sonar_t);
return_value_if_fail(m_sonar != NULL, NULL);
emitter_init(EMITTER(m_sonar));
m_sonar_t* prev_model = get_m_sonar_model();
//通过AWTK的app conf持久化机制中转数据,实现prev_model到目前model的数据同步
m_sonar_config_save(prev_model);
m_sonar_config_load(m_sonar);
//当前model入栈
g_model_list.push_back(m_sonar);
return m_sonar;
}
ret_t m_sonar_destroy(m_sonar_t* m_sonar) {
return_value_if_fail(m_sonar != NULL, RET_BAD_PARAMS);
m_sonar_config_save(m_sonar);
//退出,当前model出栈
g_model_list.remove(m_sonar);
m_sonar_t* prev_model = get_m_sonar_model();
m_sonar_config_load(prev_model);
emitter_deinit(EMITTER(m_sonar));
return RET_OK;
}
ret_t m_sonar_config_load(m_sonar_t* m_sonar) {
m_sonar_set_zoom_mode(m_sonar, app_conf_get_int(CONF_KEY_GAIN_MODE, DEFAULT_VALUE_GAIN_MODE));
m_sonar_set_zoom_mode(m_sonar, app_conf_get_int(CONF_KEY_FREQENCY, DEFAULT_VALUE_FREQENCY));
m_sonar_set_zoom_mode(m_sonar, app_conf_get_int(CONF_KEY_ZOOM_MODE, DEFAULT_VALUE_ZOOM_MOD));
emitter_dispatch_simple_event(EMITTER(m_sonar), EVT_PROPS_CHANGED);
return RET_OK;
}
ret_t m_sonar_config_save(m_sonar_t* m_sonar) {
if(m_sonar == NULL){
return RET_FAIL;
}
app_conf_set_int(CONF_KEY_GAIN_MODE, m_sonar->gain_mode);
app_conf_set_int(CONF_KEY_FREQENCY, m_sonar->freqenct);
app_conf_set_int(CONF_KEY_ZOOM_MODE, m_sonar->zoom_mode);
return RET_OK;
}
图示:

这个方法有局限性,只适用于需要同步的这些页面的v-model都是全局的唯一的的情况下, 如果一个页面多次声明同一个v-model就不行了,得另作修改。
写model getter和setter将UI代码和model代码联系到一起
有时候UI交互造成的一些数据变化需要同步到model里面去,而且这些数据变化没办法或者是不想直接在xml上做命令绑定。
比如需要考虑灵活维护,想直接从UI代码接口获取可变量做参数,不想在xml里用给定参数写死的命令绑定,通过用户UI代码事件回调而不是view_model来触发model更新;
<!-- Don't -->
<window v-model="calculator">
<edit name="expr" x="c" y="10" w="90%" h="30" focus="true" readonly="true" input_type="custom" text="" tips="expression" v-data:text="{expr}"/>
<view y="60" x="c" w="90%" h="-60" is_keyboard="true"
children_layout="default(r=4,c=4,m=5,s=5)" >
<button name="0" text="0" v-on:click="{addChar, Args=0}"/>
<button name="1" text="1" v-on:click="{addChar, Args=1}"/>
<button name="2" text="2" v-on:click="{addChar, Args=2}"/>
<button name="3" text="3" v-on:click="{addChar, Args=3}"/>
<button name="4" text="4" v-on:click="{addChar, Args=4}"/>
<button name="5" text="5" v-on:click="{addChar, Args=5}"/>
<button name="6" text="6" v-on:click="{addChar, Args=6}"/>
<button name="7" text="7" v-on:click="{addChar, Args=7}"/>
<button name="8" text="8" v-on:click="{addChar, Args=8}"/>
<button name="9" text="9" v-on:click="{addChar, Args=9}"/>
<button name="+" text="+" v-on:click="{addChar, Args=+}"/>
<button name="-" text="-" v-on:click="{addChar, Args=-}"/>
<button name="*" text="*" v-on:click="{addChar, Args=*}"/>
<button name="/" text="/" v-on:click="{addChar, Args=/}"/>
<button name="=" text="=" v-on:click="{eval}"/>
<button name="backspace" text="<=" v-on:click="{removeChar}"/>
</view>
</window>
// Do
calculator.xml
<window v-model="calculator">
<edit name="expr" x="c" y="10" w="90%" h="30" focus="true" readonly="true" input_type="custom" text="" tips="expression" v-data:text="{expr}"/>
<view y="60" x="c" w="90%" h="-60" is_keyboard="true"
children_layout="default(r=4,c=4,m=5,s=5)" >
<button name="button" text="0" />
<button name="button" text="1" />
<button name="button" text="2" />
<button name="button" text="3" />
<button name="button" text="4" />
<button name="button" text="5" />
<button name="button" text="6" />
<button name="button" text="7" />
<button name="button" text="8" />
<button name="button" text="9" />
<button name="button" text="+" />
<button name="button" text="-" />
<button name="button" text="*" />
<button name="button" text="/" />
<button name="button" text="=" />
<button name="button" text="<=" />
</view>
</window>
//calculator.c
ret_t on_button_button_click(void* ctx, event_t* e) {
pointer_event_t* evt = pointer_event_cast(e);
m_calculator *m_calculator = get_m_calculator();
return_value_if_fail(m_calculator != NULL, RET_BAD_PARAMS);
widget_t *btn = WIDGET(e->target);
int index = widget_index_of(btn);
// 直接通过界面文字给计算器model提供符号数据
char text[32];
widget_get_text_utf8(btn, text, sizeof(text));
m_calculator_set_expr(m_calculator, text);
}
static ret_t visit_init_child(void* ctx, const void* iter) {
widget_t* win = WIDGET(ctx);
widget_t* widget = WIDGET(iter);
const char* name = widget->name;
// 初始化指定名称的控件(设置属性或注册事件),请保证控件名称在窗口上唯一
if (name != NULL && *name != '\0') {
if (tk_str_eq(name, "button")) {
widget_on(widget, EVT_CLICK, on_button_button_click, win);
}
}
return RET_OK;
}
又或者菜单几十个单选按钮,按钮功能大致相同,xml上几十个按钮标签,与其一个个在xml写命令绑定(这样维护性很差),不如直接给这些按钮设置相同的名字(可以设置相同名字来遍历控件设置同一个回调,QT上好像做不到这么方便),然后在visit_init_child函数上遍历这些控件注册同一个回调,然后给这些按钮设置一个类似唯一id的自定义属性(或者干脆就是按钮上的文本)用于分支判断,在回调中决定设置哪一个数据;
怎么做?
方法很简单,手动实现这些model的getter和setter函数,setter属性完成之后,通过emitter_dispatch_simple_event来通知模型更新界面。
<view name="v_setting_zoom_mode" style="onwa_setting" x="c" y="m" w="562" on:key_down="menu_rbtn_set_scroll_view(widget_get('self','name'))" h="184">
<label name="setting_title" h="34" style="setting_title" focusable="true" x="0" y="0" w="562" tr_text="Zoom mode"/>
<view name="radio_view2_2" h="150" children_layout="default(c=2,r=2,s=10)" style:normal:bg_color="#00244400" x="0" y="b:0" w="562">
<!-- 设定自定义的key属性 -->
<radio_button name="radio_button" style="onwa_radio2" key="zoom_mode" v-data:value="{zoom_mode==0}" focusable="true" value="true" tr_text="No Zoom"/>
<radio_button name="radio_button" style="onwa_radio2" key="zoom_mode" v-data:value="{zoom_mode==1}" focusable="true" tr_text="Bottom Lock"/>
<radio_button name="radio_button" style="onwa_radio2" key="zoom_mode" v-data:value="{zoom_mode==2}" focusable="true" tr_text="Auto"/>
<radio_button name="radio_button" style="onwa_radio2" key="zoom_mode" v-data:value="{zoom_mode==3}" focusable="true" tr_text="Manual"/>
</view>
</view>
ret_t on_radio_button_click(void* ctx, event_t* e) {
pointer_event_t* evt = pointer_event_cast(e);
m_sonar_t *m_sonar = get_m_sonar_model();
return_value_if_fail(m_sonar != NULL, RET_BAD_PARAMS);
int sonar_mode = m_sonar->sonar_mode;
widget_t *btn = WIDGET(e->target);
int index = widget_index_of(btn);
//获取控件上自定义的key属性
const char *key = widget_get_prop_str(btn, "key", "unknown");
m_sonar_t* m_sonar = get_m_sonar_model();
if (index >= 0) {
// Call the m_sonar function with the title and button index
m_sonar_set_prop_int(m_sonar, key, index);
}
}
ret_t m_sonar_set_prop_int(m_sonar_t* m_sonar, const char* key, int value) {
if (!m_sonar) {
printf("Error: Could not get m_sonar model\r\n");
return RET_FAIL;
}
if (tk_str_end_with(key, CONF_KEY_GAIN_MODE)) {
m_sonar_set_gain_mode(m_sonar, value);
}
else if (tk_str_end_with(key, CONF_KEY_FREQUENCY)) {
m_sonar_set_frequency(m_sonar, value);
}
else if (tk_str_end_with(key, CONF_KEY_ZOOM_MODE)) {
m_sonar_set_zoom_mode(m_sonar, value);
}
...
// 设置完成后,同步model
emitter_dispatch_simple_event(EMITTER(m_sonar), EVT_PROPS_CHANGED);
return;
}
动态加载绑定model的UI片段
在AWTK,可以通过navigator_request_t给model设置初始化的参数,但是设置初始化参数的场景AWTK-MVVM原文档里只写明了从一个窗口导航到另一个窗口,对于重复的UI片段就语焉不详了,而如果遇到了一个页面就有这些重复的UI片段,外加这些UI片段都绑定了某个同样的model的场景呢?
项目开发就遇到了这种情况,声纳本身有多个类型,海图机产品有一种能把这些不同的声纳类型画面都显示出来的分屏模式,最多支持3个不同类型声纳的同时显示,要命的是,由于这些声纳类型的参数和模型逻辑高度重合,我都是用的同一个模型来处理的,没能把它们分开(一个声纳模型有几十个参数设置,总共3个类型,要分开就是两百多个参数,十分臃肿了,倒霉的是AWTK-MVVM没有一种类似于继承的机制能让派生类复用父类的数据和函数去做代码绑定),只通过导航到某个类型单个声纳显示的窗口时,通过指定初始化参数来确定不同的声纳类型。
最简单直接的方法就是,一个分屏就是一个model绑定的UI片段,窗口三个分屏就是窗口里面有3个model, 这样可以直接使用原来单屏模式就写好的model代码。
但是业务限制,分屏的数量和各个分屏的模式都不是固定的,这部分的UI只能在代码中动态生成。
对于UI复用,AWTK有提供一个组件机制(本质是简单粗暴的include替换)来实现,通过ui_loader_load_widget_with_parent(组件xml名,父widget)可把对应的组件加载到UI中, 但是接口是属于原版AWTK的,并没有考虑到mvvm的绑定机制,那么这种API有没有MVVM的版本?
查找AWTK-MVVM代码,还真发现有这么一个API:
/**
* @method ui_loader_mvvm_load_widget_with_parent
* 加载导航请求指定的控件,并指定父控件对象。
* @param {navigator_request_t*} req 导航请求。
* @param {widget_t*} parent 父控件对象。
*
* @return {widget_t*} 控件对象。
*/
widget_t* ui_loader_mvvm_load_widget_with_parent(navigator_request_t* req, widget_t* parent);
声纳模式m_sonar是通过mode来区分不同类型的,那么对于分屏同时显示多个类型的声纳,就可以这么处理:
// 伪代码,看看理解就行了,可以自己试试
sonar_combo.xml
<view v-model="m_sonar"></view>
split_window.xml
<view name="combo_views" x="0" y="0" w="800" h="480">
<!-- 假如要动态生成下面三个标签: -->
<view v-model="m_sonar"></view>
<view v-model="m_sonar"></view>
<view v-model="m_sonar"></view>
</view>
split_window.c
ret_t init_window(widget_t *win)
{
widget_t *combo_views = widget_lookup(win, "combo_views", TRUE);
...
for(int i = 0; i < split_num; i++){
navigator_request_t *req = navigator_request_create("sonar_combo", NULL);
tk_object_set_prop_pointer(TK_OBJECT(req), "sonar_mode", get_sonar_mode(i));
widget_t *combo_view = ui_loader_mvvm_load_widget_with_parent(req, combo_views);
return_value_if_fail(combo_view != NULL, RET_BAD_PARAMS);
}
return RET_OK;
}
这种方法也有缺陷,如果加载的组件标签里面有来自其他model的数据/命令绑定,那么就算当前窗口顶层已经声明了这个model,也是没法绑定的。
而且上一章的model数据同步到这里就不管用了,必须修改,好在这些声纳model只是一层类型不同而已,让声纳model的model链表都只管理同类型的声纳model就行了。
修改已经加载了的UI片段的绑定属性
研究AWTK-MVVM代码的的另一个小收获,可以用于解决上面提到的顶层model属性没法绑定的问题。
直接放测试代码,另外也可以看mvvm控件的binding_context_test.cc来了解awtk-mvvm怎么实现绑定。
static binding_context_t* binding_context_create(binding_context_t* parent, const char* vmodel,
widget_t* widget) {
navigator_request_t* req = navigator_request_create("test", NULL);
binding_context_t* ctx = binding_context_awtk_create(parent, vmodel, req, widget);
tk_object_unref(TK_OBJECT(req));
return ctx;
}
static binding_rule_t* binding_rule_create(widget_t* widget, const char* name, const char* val) {
binding_rule_t* rule = binding_rule_parse(name, val, widget->vt->inputable);
rule->widget = widget;
return rule;
}
void mvvm_test(widget_t* win, void* ctxx){
binding_context_t* ctx;
binding_rule_t* rule;
ctx = widget_get_prop_pointer(win, WIDGET_PROP_V_MODEL);
return_if_fail(ctx != NULL);
widget_t *slider = widget_lookup(win, "slider", TRUE);
return_if_fail(slider != NULL);
rule = binding_rule_create(slider, "v-data:value", "{conf_test.value, Trigger=Changing}");
tk_object_set_prop_bool(TK_OBJECT(rule), BINDING_RULE_PROP_INITED, TRUE);
binding_context_bind_data(ctx, rule);
binding_context_set_bound(ctx, TRUE);
// 必须声明这个,不然model的数据没法更新到view
binding_context_update_to_view(ctx);
}
/**
* 初始化窗口
*/
ret_t test_mvvm_dynamic_load_init(widget_t* win, void* ctx) {
(void)ctx;
return_value_if_fail(win != NULL, RET_BAD_PARAMS);
widget_foreach(win, visit_init_child, win);
mvvm_test(win, ctx);
return RET_OK;
}
binding_context_t可以理解为对xml上声明的v-model功能的实际对象,内部对于数据绑定和命令绑定各自维护了一套链表。
对于数据绑定,当数据事件发生时,就会通过查找链表data_binding来找到绑定属性指定的控件,并更改控件数据。
附录
窗口启动时fscript函数执行原理解析
AWTK在扫描页面xml的时候,如果检测到on:函数名就会去判断这是否是一个fscript调用,并将on对应的事件注册到widget_exec_code等待触发,同时将on:函数名作为属性,以widget_set_prop函数保存到对应的widget中。
窗口打开时将触发widget_exec_code,widget_exec_code经过逐层调用,将解析出的函数名在fscript的注册表中比对查找出对应的注册函数。
附调用栈和相关函数:
demo.exe!home_page_init(_widget_t * win, void * ctx) Line 35
demo.exe!navigator_window_init(const char * name, _widget_t * win, void * ctx) Line 19
demo.exe!func_navigator_window_init(_fscript_t * fscript, _fscript_args_t * args, _value_t * result) Line 30
awtk.dll!fscript_exec_ext_func(_fscript_t * fscript, _fscript_func_call_t * iter, _value_t * result) Line 867
awtk.dll!fscript_exec_func_default(_fscript_t * fscript, _fscript_func_call_t * iter, _value_t * result) Line 904
awtk.dll!fscript_exec_func(_fscript_t * fscript, const char * name, _fscript_func_call_t * iter, _value_t * result) Line 258
awtk.dll!fscript_eval_arg(_fscript_t * fscript, _fscript_func_call_t * iter, unsigned int i, _value_t * d) Line 624
awtk.dll!fscript_exec_ext_func(_fscript_t * fscript, _fscript_func_call_t * iter, _value_t * result) Line 853
awtk.dll!fscript_exec_func_default(_fscript_t * fscript, _fscript_func_call_t * iter, _value_t * result) Line 904
awtk.dll!fscript_exec_func(_fscript_t * fscript, const char * name, _fscript_func_call_t * iter, _value_t * result) Line 258
awtk.dll!fscript_exec(_fscript_t * fscript, _value_t * result) Line 924
awtk.dll!fscript_info_exec(_fscript_info_t * info) Line 2079
awtk.dll!widget_exec_code(void * ctx, _event_t * evt) Line 2121
awtk.dll!emitter_dispatch(_emitter_t * emitter, _event_t * e) Line 130
awtk.dll!widget_dispatch(_widget_t * widget, _event_t * e) Line 1370
awtk.dll!widget_dispatch_callback(void * ctx, const void * data) Line 1408
awtk.dll!widget_foreach(_widget_t * widget, _ret_t(*)(void *, const void *) visit, void * ctx) Line 3415
awtk.dll!widget_dispatch_recursive(_widget_t * widget, _event_t * e) Line 1412
awtk.dll!window_manager_dispatch_window_event(_widget_t * window, _event_type_t type) Line 722
awtk.dll!window_manager_dispatch_window_open(_widget_t * curr_win) Line 674
awtk.dll!window_manager_idle_dispatch_window_open(const _idle_info_t * info) Line 683
awtk.dll!idle_info_on_idle(_idle_info_t * idle, unsigned int dispatch_id) Line 129
awtk.dll!idle_manager_dispatch_one(_idle_manager_t * idle_manager, unsigned int dispatch_id) Line 171
awtk.dll!idle_manager_dispatch(_idle_manager_t * idle_manager) Line 193
awtk.dll!event_source_idle_dispatch(_event_source_t * source) Line 39
awtk.dll!event_source_dispatch(_event_source_t * source) Line 34
awtk.dll!event_source_manager_default_dispatch_no_fd(_event_source_manager_t * manager) Line 130
awtk.dll!event_source_manager_default_dispatch(_event_source_manager_t * manager) Line 147
awtk.dll!event_source_manager_dispatch(_event_source_manager_t * manager) Line 64
awtk.dll!main_loop_simple_step(_main_loop_t * l) Line 231
awtk.dll!main_loop_step(_main_loop_t * l) Line 120
awtk.dll!main_loop_simple_run(_main_loop_t * l) Line 251
awtk.dll!main_loop_run(_main_loop_t * l) Line 32
awtk.dll!tk_run(...) Line 457
demo.exe!wWinMain(HINSTANCE__ * hinstance, HINSTANCE__ * hprevinstance, wchar_t * lpcmdline, int ncmdshow) Line 256
demo.exe!invoke_main() Line 123
demo.exe!__scrt_common_main_seh() Line 288
demo.exe!__scrt_common_main() Line 331
demo.exe!wWinMainCRTStartup(void * __formal) Line 17
kernel32.dll!00007fff4c88259d() (Unknown Source:0)
ntdll.dll!00007fff4dc6af78() (Unknown Source:0)
static ret_t fscript_func_call_init_func(fscript_func_call_t* call, tk_object_t* obj,
tk_object_t* funcs_def, const char* name, uint32_t size)
{
...
if (func == NULL && s_global_funcs != NULL) {
func = (fscript_func_t)general_factory_find(s_global_funcs, func_name);
}
...
call->func = func;
}
ret_t fscript_exec_func_default(fscript_t* fscript, fscript_func_call_t* iter, value_t* result) {
...
fscript_func_call_init_func(iter, obj, fscript->funcs_def, name, tk_strlen(name));
...
}
static ret_t fscript_info_prepare(fscript_info_t* info, event_t* evt) {
tk_object_t* obj = NULL;
obj = object_default_create();
...
// 将widget和fscript对象绑定
tk_object_set_prop_pointer(obj, STR_PROP_SELF, info->widget);
info->obj = obj;
...
}
static ret_t widget_exec_code(void* ctx, event_t* evt) {
ret_t ret = RET_OK;
fscript_info_t* info = (fscript_info_t*)ctx;
if (info->busy) {
/*多次触发,只执行一次*/
return RET_OK;
}
if (!tk_is_ui_thread()) {
log_debug("not supported trigger in none ui thread\n");
return RET_OK;
}
info->busy = TRUE;
return_value_if_fail(fscript_info_prepare(info, evt) == RET_OK, RET_BAD_PARAMS);
ret = fscript_info_exec(info);
info->busy = FALSE;
return ret;
}
ret_t widget_set_prop(widget_t* widget, const char* name, const value_t* v){
...
if (strncmp(name, STR_ON_EVENT_PREFIX, sizeof(STR_ON_EVENT_PREFIX) - 1) == 0) {
//解析xml上获取到的on:xxxx属性(为便于理解,代码略做修改,省去无关部分)
const char* p_str = STR_ON_EVENT_PREFIX "" STR_GLOBAL_VARS_CHANGED;
bool_t is_global_vars_changed = tk_str_eq(name, p_str);
const char* event_name = name + sizeof(STR_ON_EVENT_PREFIX) - 1;
int32_t etype = event_from_name(event_name);
...
if (etype != EVT_NONE) {
fscript_info_t* info = fscript_info_create(value_str(v), widget);
if (info != NULL) {
char new_prop_name[TK_NAME_LEN + 1] = {0};
name += sizeof(STR_ON_EVENT_PREFIX) - 1;
// 根据事件类型(全局/普通),注册对应事件到widget_exec_code函数
if (tk_str_start_with(name, STR_GLOBAL_EVENT_PREFIX)) {
// on:global_xxx (如on:global_key_down)
widget_t* wm = window_manager();
widget_on(wm, etype, widget_exec_code, info);
info->emitter = EMITTER(wm->emitter);
} else {
// on:xxx (on:window_open事件走的地方)
widget_on(widget, etype, widget_exec_code, info);
info->emitter = NULL;
}
}

浙公网安备 33010602011771号