应用程序通常有多个窗口,窗口之间的切换以及参数的传递,这就是窗口导航。窗口导航是个常用的功能,有没有 MVVM 框架都需要这个功能,为什么我们要把它纳入 MVVM 框架内来讨论呢?
大家知道,模型是不能操作界面的,但是很多时候模型必须和用户进行交互。比如:
- 在写文件的过程中磁盘满了,要询问用户是中断操作还是清理垃圾。
- 购买的过程中,发现余额不足,要询问用户是放弃购买还是充值。
- 在执行某个命令时,需要打开另外一个窗口。
由此可见,模型有时就得操作界面!如果不能解决这个问题,就没有达到彻底隔离用户界面和业务逻辑的目标。这是 MVVM 窗口导航器要做的事情,所以把它放在 MVVM 中来讨论。
在传统分层设计中,将应用程序分成 3 层。分层设计的基本原则是,上层可以直接调用下层的函数,下层则只能通过回调函数或消息或事件通知上层,让上层执行某些动作。
提供回调函数给下层其实就是依赖注入,对于协议栈 (如 TCP/IP) 来说,是非常方便的,因为需要提供的回调函数并不多。但是对应用程序来说,各种情况都有,虽然回调函数的方式不是不可能,但实际操作确实很困难,这样做的话,API 接口会很复杂,调用者理解也很困难。
Bob 大叔在《架构整洁之道》17 章中说:
只要 GUI 是以插件形式插入业务逻辑的,那么 GUI 这边所发生的变更就不会影响系统的业务逻辑。
AWTK-MVVM 中的窗口导航器就是一个插件系统,在模型中通过 MVVM 的导航器打开窗口,并不会导致模型与视图的耦合。
假设我们要打开 settings 窗口:
AWTK 传统的打开方法是:
window_open("settings");
AWTK-MVVM 的打开方法是:
navigator_to("settings");
两者从外在看起来,除了函数名不同,好像也没有什么差别啊。但是两者本质上是不同的:
-
前者就是执行打开窗口的动作,如果执行成功就会打开窗口。这是编译的时候就定死了的,相当于硬编码,如果在模型中调用它,那模型就和 GUI 直接耦合到一起了。
-
后者则只是表达一个意图,发出一个请求,至于做什么,谁来做,调用者是完全不用关心的。在正常运行时,我们可以打开一个窗口,也可以执行另外一个应用程序。在测试时,我们可以什么也不做,让自动测试在没有用户的干预下继续往下运行。也就是说,它的行为完全由导航器这个插件系统中当前注册的插件决定。
AWTK-MVVM 中的导航器就是一个全局的依赖注入系统,一个视图的插件系统,让模型可以请求视图,同时又避免和视图的耦合到一起。
AWTK-MVVM 中的导航器目前支持如下请求处理器(插件):
类型 | 说明 |
---|---|
default_handler | 打开窗口,也是导航器默认的请求处理器。 |
_close_ | 关闭窗口。 |
_home_ | 回到主窗口。 |
_back_ | 关闭最上面的窗口。 |
_toast_ | 打开内置的 toast 窗口。 |
_info_ | 打开内置的提示窗口。 |
_warn_ | 打开内置的告警窗口。 |
_confirm_ | 打开内置的确认窗口。 |
_pick_file_ | 打开内置的文件选择窗口。 |
_pick_dir_ | 打开内置的文件夹选择窗口。 |
_count_view_model_ | 获取 View Model 实例的数量。 |
_get_view_model_ | 获取 View Model 实例。 |
_notify_view_model_ | 触发 View Model 实例的变化事件。 |
_get_locale_ | 获取本地化信息(国家和语言) |
_set_locale_ | 设置本地化信息(国家和语言) |
_get_theme_ | 获取当前主题 |
_set_theme_ | 设置当前主题 |
_set_screen_saver_time_ | 设置屏保时间 |
可以在导航请求中通过 request 参数指定请求的类型,如果没有指定,则为default_handler。
下面详细介绍一下上述插件。
打开窗口一般是界面操作中的第一步操作,因此将其作为导航器默认的插件。发送请求时可以设置如下参数:
参数 | 说明 |
---|---|
target | 表示目标窗口的名称。 |
close_current | 表示是否关闭当前窗口。缺省为 false。 |
open_new | 表示存在同名窗口时,是否打开新的窗口。当为 true 时则打开一个新的窗口,否则切换到已存在的窗口。缺省为 true。 |
导航请求中也可以添加其它自定义的参数,会一起传递给新窗口的 View Model,具体用途由新窗口的 View Model 解释。
下面我们来看看各种情况的处理:
这是最简单的情况,一般打开窗口都用这种方式。
在代码中可以用 navigator_to 函数来请求打开窗口。
函数原型如下:
/**
* @method navigator_to
* 发送指定的请求。
* 发送请求时可以用"string?"为前缀、用"&"分隔的格式传递参数。
* 比如,"string?arg1=xx&arg2=yy"表示有两个参数,参数arg1的值为"xx",参数arg2的值为"yy",
* 如果没有用上述格式指定参数,则默认为target参数的值。
*
* @annotation ["static"]
*
* @param {const char*} args 发送请求时要传递的参数。
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t navigator_to(const char* args);
使用示例:
// 打开名称为temperature1的窗口
navigator_to("temperature1");
// 打开名称为temperature1的窗口并关闭当前窗口
navigator_to("string?target=temperature1&close_current=true");
在 XML 中,也可以通过 navigate 命令来打开指定的窗口。比如:
<window>
<button text="Temperature" x="center" y="middle:-40" w="40%" h="40" v-on:click="{navigate, Args=temperature9}"/>
<button text="Humidity" x="center" y="middle:40" w="40%" h="40" v-on:click="{navigate, Args=humidity}"/>
<button text="Home" x="center" y="middle:40" w="80%" h="30" v-on:click="{navigate, Args=fscript?target=demo33&close_current=true&open_new=false&mykey=myvalue}"/>
</window>
"fscript?"形式的参数会自动转换为"string?"形式。
关于参数格式的更多信息,请参阅命令绑定的11.2.1章节。
在代码中还有其他简化函数可以适用于其他特定场景。函数原型如下:
/**
* @method navigator_to_by_object
* 发送指定的请求。
*
* @annotation ["static"]
*
* @param {const char*} args 发送请求时要传递的参数。
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t navigator_to_by_object(tk_object_t* args);
/**
* @method navigator_to_with_key_value
* 请求打开指定的窗口。
*
* @annotation ["static"]
*
* @param {const char*} target 目标窗口的名称。
* @param {const char*} key 参数名。
* @param {const char*} value 参数值。
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t navigator_to_with_key_value(const char* target, const char* key, const char* value);
/**
* @method navigator_replace
* 请求打开指定的窗口,并关闭当前窗口。
*
* @annotation ["static"]
*
* @param {const char*} target 目标窗口的名称。
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t navigator_replace(const char* target);
/**
* @method navigator_switch_to
* 如果目标窗口已经存在,直接切换到该窗口,否则打开新窗口。
*
* @annotation ["static"]
*
* @param {const char*} target 目标窗口的名称。
* @param {bool_t} close_current 是否关闭当前窗口。
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t navigator_switch_to(const char* target, bool_t close_current);
此时,需创建一个 navigator_request_t 对象,设置相应的参数,并调用函数 navigator_to_ex 发起打开窗口的请求。
navigator_to_ex 发起请求的方式在任何时候都是适用的,但是用起来有点繁琐,因此如无特殊需求,一般使用简化的函数即可。
比如:
static ret_t home_on_room_result(navigator_request_t* req, const value_t* result) {
tk_object_t* res = value_object(result);
home_t* h = (home_t*)(tk_object_get_prop_pointer(TK_OBJECT(req), STR_HOME));
const char* room_name = tk_object_get_prop_str(TK_OBJECT(req), ROOM_SETTINGS_REQ_ARG_ROOM);
double temp = tk_object_get_prop_float(res, ROOM_SETTINGS_RESULT_TEMP, 0);
double humidity = tk_object_get_prop_float(res, ROOM_SETTINGS_RESULT_HUMIDITY, 0);
if (tk_str_eq(room_name, h->bed_room->name)) {
h->bed_room->temp = temp;
h->bed_room->humidity = humidity;
} else {
h->living_room->temp = temp;
h->living_room->humidity = humidity;
}
emitter_dispatch_simple_event(EMITTER(h), EVT_PROPS_CHANGED);
return RET_OK;
}
static ret_t adjust_room_params(home_t* h, room_info_t* info) {
navigator_request_t* req = navigator_request_create("room_settings", home_on_room_result);
tk_object_set_prop_pointer(TK_OBJECT(req), STR_HOME, h);
tk_object_set_prop_str(TK_OBJECT(req), ROOM_SETTINGS_REQ_ARG_ROOM, info->name);
tk_object_set_prop_float(TK_OBJECT(req), ROOM_SETTINGS_REQ_ARG_TEMP, info->temp);
tk_object_set_prop_float(TK_OBJECT(req), ROOM_SETTINGS_REQ_ARG_HUMIDITY, info->humidity);
navigator_to_ex(req);
tk_object_unref(TK_OBJECT(req));
return RET_OK;
}
在这个例子中,下面这行代码,请求打开窗口 room_settings 设置房间的参数,设置完成后,通过回调函数 home_on_room_result 返回新的参数。
navigator_request_create("room_settings", home_on_room_result);
可以通过 object_set_prop_xxx 设置请求参数,可以通过 object_get_prop_xxx 获取处理结果。
Windows 的命令行下,读者可以运行 demo12 来查看实际的效果。
bin\demo12.exe
发送请求时可以设置如下参数:
参数 | 说明 |
---|---|
target | 表示目标窗口的名称。 |
force | 表示是否强制关闭窗口。 |
在代码中可以用函数 navigator_close 和 navigator_request_close 来请求关闭指定的窗口。
函数原型如下:
/**
* @method navigator_close
* 关闭指定窗口。
*
* @param {const char*} target 目标窗口的名称。
* @annotation ["static"]
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t navigator_close(const char* target);
/**
* @method navigator_request_close
* 请求关闭关闭指定窗口。
*
* > 窗口是否被关闭,取决于窗口本身的处理逻辑。
*
* @param {const char*} target 目标窗口的名称。
* @annotation ["static"]
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t navigator_request_close(const char* target);
在 XML 中也可以用 navigate 命令来关闭指定的窗口。比如:
<button text="Close" x="c" y="m:40" w="80%" h="30" v-on:click="{navigate, Args=string?request=_close_&target=demo33_a}"/>
发送请求时可以无需设置其他参数。
在代码中可以用函数 navigator_back_to_home 来请求回到主窗口。
函数原型如下:
/**
* @method navigator_back_to_home
* 回到主屏。
*
* @annotation ["static"]
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t navigator_back_to_home(void);
在 XML 中也可以用 navigate 命令来回到主窗口。比如:
<button text="Home" x="c" y="m:40" w="80%" h="30" v-on:click="{navigate, Args=string?request=_home_}"/>
发送请求时无需设置其他参数。
在代码中可以用函数 navigator_back 来请求回到主窗口。
函数原型如下:
/**
* @method navigator_back
* 关闭当前窗口,回到前一窗口。
*
* @annotation ["static"]
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t navigator_back(void);
在 XML 中也可以用 navigate 命令来关闭最上面的窗口。比如:
<button text="Home" x="c" y="m:40" w="80%" h="30" v-on:click="{navigate, Args=string?request=_back_}"/>
发送请求时可以设置如下参数:
参数 | 说明 |
---|---|
content | 表示窗口显示的内容。 |
timeout | 表示窗口的显示时间(单位:ms)。 |
在代码中可以用函数 navigator_toast 来请求打开内置的 toast 窗口。
函数原型如下:
/**
* @method navigator_toast
* 显示toast信息。
*
* @annotation ["static"]
*
* @param {const char*} content 信息内容。
* @param {uint32_t} timeout 显示时间。
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t navigator_toast(const char* content, uint32_t timeout);
在 XML 中也可以用 navigate 命令来打开内置的 toast 窗口。比如:
<button text="Home" x="c" y="m:40" w="80%" h="30" v-on:click="{navigate, Args=string?request=_toast_&content=something&timeout=1000}"/>
发送请求时可以设置如下参数:
参数 | 说明 |
---|---|
content | 表示窗口显示的内容。 |
title | 表示窗口的标题。 |
在代码中可以用函数 navigator_info 来请求打开内置的提示窗口。
函数原型如下:
/**
* @method navigator_info
* 显示信息。
*
* @annotation ["static"]
*
* @param {const char*} title 标题。
* @param {const char*} content 内容。
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t navigator_info(const char* title, const char* content);
在 XML 中也可以用 navigate 命令来打开内置的提示窗口。比如:
<button text="Home" x="c" y="m:40" w="80%" h="30" v-on:click="{navigate, Args=string?request=_info_&content=something&title=tips}"/>
发送请求时可以设置如下参数:
参数 | 说明 |
---|---|
content | 表示窗口显示的内容。 |
title | 表示窗口的标题。 |
在代码中可以用函数 navigator_warn 来请求打开内置的告警窗口。
函数原型如下:
/**
* @method navigator_warn
* 显示警告信息。
*
* @annotation ["static"]
*
* @param {const char*} title 标题。
* @param {const char*} content 内容。
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t navigator_warn(const char* title, const char* content);
在 XML 中也可以用 navigate 命令来打开内置的告警窗口。比如:
<button text="Home" x="c" y="m:40" w="80%" h="30" v-on:click="{navigate, Args=string?request=_warn_&content=something&title=tips}"/>
发送请求时可以设置如下参数:
参数 | 说明 |
---|---|
content | 表示窗口显示的内容。 |
title | 表示窗口的标题。 |
在代码中可以用函数 navigator_confirm 来请求打开内置的确认窗口。
函数原型如下:
/**
* @method navigator_confirm
* 显示确认信息。
*
* @annotation ["static"]
*
* @param {const char*} title 标题。
* @param {const char*} content 内容。
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t navigator_confirm(const char* title, const char* content);
在 XML 中也可以用 navigate 命令来打开内置的确认窗口。比如:
<button text="Home" x="c" y="m:40" w="80%" h="30" v-on:click="{navigate, Args=string?request=_confirm_&content=something&title=tips}"/>
发送请求时可以设置如下参数:
参数 | 说明 |
---|---|
title | 表示窗口的标题。 |
for_save | 表示是否为保存文件的窗口,true表示用于保存,false表示用于打开。缺省为false。 |
filter | 表示要过滤的文件的后缀。 |
default | 表示默认打开的文件夹的路径。 |
在代码中可以用函数 navigator_pick_file 来请求打开内置的文件选择窗口。
函数原型如下:
/**
* @method navigator_pick_file
* 选择文件。
*
* @annotation ["static"]
*
* @param {const char*} title 标题。
* @param {const char*} filter 文件过滤(如:.txt.html), NULL表示不过滤。
* @param {bool_t} for_save TRUE表示用于保存,FALSE表示用于打开。
* @param {str_t*} result 用于传递缺省值和返回结果。
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t navigator_pick_file(const char* title, const char* filter, bool_t for_save, str_t* result);
发送请求时可以设置如下参数:
参数 | 说明 |
---|---|
title | 表示窗口的标题。 |
default | 表示默认打开的文件夹的路径。 |
在代码中可以用函数 navigator_pick_dir 来请求打开内置的文件夹选择窗口。
函数原型如下:
/**
* @method navigator_pick_dir
* 选择目录。
*
* @annotation ["static"]
*
* @param {const char*} title 标题。
* @param {str_t*} result 用于传递缺省值和返回结果。
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t navigator_pick_dir(const char* title, str_t* result);
发送请求时可以设置如下参数:
参数 | 说明 |
---|---|
target | 表示 View Model 实例的路径,不指定时表示当前全部的 View Model 实例,为空字符串时表示最上面的窗口绑定的全部 View Model实例, 其他则表示具体的路径,比如 “a.[0]” 表示 name 属性值为 "a" 的窗体的第0个子控件上绑定的 View Model 实例。 |
在代码中可以用函数 navigator_count_view_models 来请求获取 View Model 实例的数量。
函数原型如下:
/**
* @method navigator_count_view_models
* 获取指定的ViewModel实例的个数。
* > target为NULL时,表示当前全部的ViewModel实例;
* > target为空字符串时,表示最上面的窗口绑定的全部ViewModel实例;
* > target也可以为具体的路径,比如:
* > 1."window.widget":表示name属性为"window"的窗体中name属性为"widget"的子控件上绑定的ViewModel实例。
* > 2."window.[0]":表示name属性为"window"的窗体的第0个子控件上绑定的ViewModel实例。
* > 注意:使用数字作为界面路径索引时必须用中括号括起来。
*
* @annotation ["static"]
*
* @param {const char*} target 与ViewModel实例绑定的控件的路径。
*
* @return {ret_t} 返回实例的个数。
*/
int32_t navigator_count_view_models(const char* target);
发送请求时可以设置如下参数:
参数 | 说明 |
---|---|
target | 表示 View Model 实例的路径,不指定时表示当前全部的 View Model 实例,为空字符串时表示最上面的窗口绑定的全部 View Model实例, 其他则表示具体的路径,比如 “a.[0]” 表示 name 属性值为 "a" 的窗体的第0个子控件上绑定的 View Model 实例。 |
在代码中可以用函数 navigator_get_view_models 来请求获取 View Model 实例。
函数原型如下:
/**
* @method navigator_get_view_models
* 获取指定的ViewModel实例。
* > target为NULL时,表示当前全部的ViewModel实例;
* > target为空字符串时,表示最上面的窗口绑定的全部ViewModel实例;
* > target也可以为具体的路径,比如:
* > 1."window.widget":表示name属性为"window"的窗体中name属性为"widget"的子控件上绑定的ViewModel实例。
* > 2."window.[0]":表示name属性为"window"的窗体的第0个子控件上绑定的ViewModel实例。
* > 注意:使用数字作为界面路径索引时必须用中括号括起来。
*
* @annotation ["static"]
*
* @param {const char*} target 与ViewModel实例绑定的控件的路径。
* @param {darray_t*} result 返回ViewModel实例。
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t navigator_get_view_models(const char* target, darray_t* result);
发送请求时可以设置如下参数:
参数 | 说明 |
---|---|
target | 表示 View Model 实例的路径,不指定时表示当前全部的 View Model 实例,为空字符串时表示最上面的窗口绑定的全部 View Model实例, 其他则表示具体的路径,比如 “a.[0]” 表示 name 属性值为 "a" 的窗体的第0个子控件上绑定的 View Model 实例。 |
props_changed | 表示是否触发属性改变的事件。 |
items_changed | 表示是否触发数据集合中项目的数量改变的事件。 |
event_source | 表示项目的数量发生改变的数据集合。 |
在代码中可以用如下函数来请求触发 View Model 实例的变化事件。
函数原型如下:
/**
* @method navigator_notify_view_props_changed
* 触发指定的View实例的props改变事件。
* > target为NULL时,表示当前全部的ViewModel实例;
* > target为空字符串时,表示最上面的窗口绑定的全部ViewModel实例;
* > target也可以为具体的路径,比如:
* > 1."window.widget":表示name属性为"window"的窗体中name属性为"widget"的子控件上绑定的ViewModel实例。
* > 2."window.[0]":表示name属性为"window"的窗体的第0个子控件上绑定的ViewModel实例。
* > 注意:使用数字作为界面路径索引时必须用中括号括起来。
*
* @annotation ["static"]
*
* @param {const char*} target 与ViewModel实例绑定的控件的路径。
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t navigator_notify_view_props_changed(const char* target);
/**
* @method navigator_notify_view_items_changed
* 触发指定的View实例的items改变事件。
* > target为NULL时,表示当前全部的ViewModel实例;
* > target为空字符串时,表示最上面的窗口绑定的全部ViewModel实例;
* > target也可以为具体的路径,比如:
* > 1."window.widget":表示name属性为"window"的窗体中name属性为"widget"的子控件上绑定的ViewModel实例。
* > 2."window.[0]":表示name属性为"window"的窗体的第0个子控件上绑定的ViewModel实例。
* > 注意:使用数字作为界面路径索引时必须用中括号括起来。
*
* @annotation ["static"]
*
* @param {tk_object_t*} items 发生变化的items对象。
* @param {const char*} target 与ViewModel实例绑定的控件的路径。
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t navigator_notify_view_items_changed(tk_object_t* items, const char* target);
发送请求时可以设置如下参数:
参数 | 说明 |
---|---|
target | 表示 ViewModel 实例的界面路径,不指定时表示窗口管理器,为空字符串时表示最上面的窗口, 其他则表示具体的路径,比如 “a.[0]” 表示 name 属性值为 "a" 的窗体的第0个子控件。 |
在代码中可以用函数 navigator_get_locale 来请求本地化信息(国家和语言)。
函数原型如下:
/**
* @method navigator_get_locale
* 获取当前的本地化信息(国家和语言)。
*
* @annotation ["static"]
*
* @param {const char*} target 控件的路径,固定为NULL。
*
* @return {tk_object_t*} 返回本地化信息对象,其中属性"language"表示语言,属性"country"表示国家或地区。
*/
object_t* navigator_get_locale(const char* target);
发送请求时可以设置如下参数:
参数 | 说明 |
---|---|
target | 表示 ViewModel 实例的界面路径,不指定时表示窗口管理器,为空字符串时表示最上面的窗口, 其他则表示具体的路径,比如 “a.[0]” 表示 name 属性值为 "a" 的窗体的第0个子控件。 |
language | 语言 |
country | 国家或地区 |
在代码中可以用函数 navigator_set_locale 来请求设置本地化信息(国家和语言)
函数原型如下:
/**
* @method navigator_get_locale
* 设置指定的本地化信息(国家和语言)。
*
* @annotation ["static"]
*
* @param {const char*} language 语言。
* @param {const char*} country 国家或地区。
* @param {const char*} target 控件的路径,固定为NULL。
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t navigator_set_locale(const char* language, const char* country, const char* target);
发送请求时可以设置如下参数:
参数 | 说明 |
---|---|
target | 表示 ViewModel 实例的界面路径,不指定时表示窗口管理器,为空字符串时表示最上面的窗口, 其他则表示具体的路径,比如 “a.[0]” 表示 name 属性值为 "a" 的窗体的第0个子控件。 |
在代码中可以用函数 navigator_get_theme 来请求当前主题。
函数原型如下:
/**
* @method navigator_get_theme
* 获取当前的主题。
*
* @annotation ["static"]
*
* @param {const char*} target 控件的路径,固定为NULL。
*
* @return {const char*} 返回主题名称。
*/
const char* navigator_get_theme(const char* target);
发送请求时可以设置如下参数:
参数 | 说明 |
---|---|
target | 表示 ViewModel 实例的界面路径,不指定时表示窗口管理器,为空字符串时表示最上面的窗口, 其他则表示具体的路径,比如 “a.[0]” 表示 name 属性值为 "a" 的窗体的第0个子控件。 |
theme | 主题 |
在代码中可以用函数 navigator_set_theme 来请求设置本地化信息(国家和语言)
函数原型如下:
/**
* @method navigator_set_theme
* 设置主题。
*
* @annotation ["static"]
*
* @param {const char*} theme 主题。
* @param {const char*} target 控件的路径,固定为NULL。
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t navigator_set_theme(const char* theme, const char* target);
发送请求时可以设置如下参数:
参数 | 说明 |
---|---|
time | 屏保时间(单位为毫秒),为0关闭屏保。 |
在代码中可以用函数 navigator_set_screen_saver_time 来请求设置屏保时间
函数原型如下:
/**
* @method navigator_set_screen_saver_time
* 设置屏保时间。
*
* @annotation ["static"]
*
* @param {uint32_t} time 屏保时间(单位毫秒), 为0关闭屏保。
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t navigator_set_screen_saver_time(uint32_t time);
一般情况下,我们并不需要自己编写插件,使用 AWTK-MVVM 导航器缺省的实现就行了。
但难以避免需要自己实现插件来处理一些特殊情况。比如:
-
实现前面提到的 toast/info/confirm 等窗口的处理插件。
-
测试程序不能打开真正的窗口,需要实现一些 Mock 的处理插件。
-
需要直接操作 widget 的界面,可以实现自己的处理插件,用传统的方式来开发界面。
编写自己的处理插件相当简单,先实现 on_request 函数,再注册到导航器中就行了。
下面我们来看几个例子:
先看看 toast 的实现(其它对话框的实现方式类似)。
- 实现 on_request 函数
我们可以从 req 中获取相应的参数,NAVIGATOR_ARG_CONTENT 是显示的文本内容,NAVIGATOR_ARG_DURATION 是显示的时间。参数是调用和实现者之间的约定,保持一致即可。
static ret_t navigator_handler_awtk_on_toast(navigator_handler_t* handler,
navigator_request_t* req) {
const char* content = tk_object_get_prop_str(TK_OBJECT(req), NAVIGATOR_ARG_CONTENT);
int duration = tk_object_get_prop_int(TK_OBJECT(req), NAVIGATOR_ARG_DURATION, 3000);
return dialog_toast(content, duration);
}
- 创建 navigator_handler
我们可以自己实现一个 navigator_handler 的构造函数,也可以直接调用 navigator_handler_create() 函数。
- 自定义 navigator_handler 构造函数,代码如下:
navigator_handler_t* navigator_handler_awtk_toast_create(void) {
tk_object_t* obj = NULL;
navigator_handler_t* handler = NULL;
obj = tk_object_create(&s_navigator_handler_awtk_vtable);
handler = NAVIGATOR_HANDLER(obj);
return_value_if_fail(handler != NULL, NULL);
handler->on_request = navigator_handler_awtk_on_toast;
return handler;
}
- 调用 navigator_handler_create()
navigator_handler_create(navigator_handler_awtk_on_toast);
- 注册到导航器:
/* 调用自定义的 navigator_handler 构造函数 */
navigator_register_handler(navigator(), NAVIGATOR_REQ_TOAST, navigator_handler_awtk_toast_create());
/* 调用 navigator_handler_create() */
//navigator_register_handler(navigator(), NAVIGATOR_REQ_TOAST, navigator_handler_create(navigator_handler_awtk_toast_create));
AWTK-MVVM 不一定能适用于所有的场景。如果遇到 AWTK-MVVM 无法处理的复杂场景时,可以采用传统的开发方式,通过导航器让 MVVM 窗口和传统窗口协同工作。
比如,实现一个自定义的窗口,一般步骤如下:
- 实现 on_request 函数
static ret_t mywindow_on_request(navigator_handler_t* handler, navigator_request_t* req) {
widget_t* win = window_create(NULL, 0, 0, 0, 0);
widget_t* ok = button_create(win, 0, 0, 0, 0);
widget_set_text(ok, L"Close");
widget_set_prop_str(win, WIDGET_PROP_ANIM_HINT, "htranslate");
widget_set_self_layout_params(ok, "center", "middle", "50%", "30");
widget_on(ok, EVT_CLICK, button_on_click, win);
widget_layout(win);
return RET_OK;
}
- 注册到导航器:
navigator_register_handler(navigator(), "mywindow",
navigator_handler_create(mywindow_on_request));
Windows 的命令行下,读者可以运行 demo36 来查看实际的效果。
bin\demo36.exe