PowerBuilder编程新思维5:包装(界面美化与WebUI+React)
前一节,分析了三种界面美化方案,都是控件级的美化。今天再来分析一下窗口级的美化。上一次讲的DirectUI,大家反响一般,效果和创新都不足。那是没有讲完的原故。今天……还是没有讲完,不过效果应该还行吧:)。
PB界面美化方案四:DirectUI包装
Hook的方式使用DirectUI,是局限在控件里。如果创建一个类似Canvas的可视控件,把所有控件包装在一个(或几个)控件里面,我把这种方式称之包装(Wrap)。
在现有界面上改造,不需要改动原有代码,只需在界面open事件中打开u_dui控件,并of_load("ui.xml")即可,所有消息转到相应名称的控件的事件中。ui.xml是DirectUI的界面定义文件,如下图(所有资源来源于网络)

PB界面美化方案五:Sciter(WebUI)包装
相比DirectUI的界面包装,WebUI的界面包装更为流行。使用Web控件替代原有控件,可以做出非常好的效果。
Sciter及其前身HtmlLayout,都是大名鼎鼎的WebUI控件,使用广泛。Sciter的Powerbuilder封装,在PowerFramework中有提供。
Sciter是最早一批专业的WebUI解决方案,相比Cef近百兆的体积,它的只有几兆。所以在通用软件中广泛使用。
Sciter的缺点就是不完全与Js兼容,在体积与速度方面有优势,在兼容性方面就有劣势了。由于前端饱受IE的折磨,建议对体积与速度方面不太敏感的应用不要使用。
Sciter的效果还是很赞的,下图是QQ动态登录界面。

PB界面美化方案六:Miniblink(WebUI)包装
Miniblink是这两年冒出的嵌入式Web新秀,更新非常快,所以用起来很放心。它的大小20多兆,比Cef小很多,而且使用的是比较新的Chrome内核版本,相比Sciter,Miniblink几乎没有兼容性的问题。可以顺利运行几乎所有的前端框架。
PowerFramework也封装了Miniblink,而且还全面导出了Powerbuilder的JS接口,使用方便。在PowerPlume里面,也封装了Miniblink(u_wui),但是没有像n_lua对象,或者PowerFramework一样导出JS接口。至于为什么,往下看就知道了。使用效果如下图:

PB界面美化方案七:DirectUI自动包装
前面所说的包装(Wrap)全部都手工进行的,就是手工编写相应的XML,或者HTML。对于个别的界面美化是可行的,如果对于大型管理软件做这样的美化修改是不可能的。界面的数量太多,手工编写工作量太大,而且测试工作量同样巨大。这时,我们要求对现有界面进行自动包装,生成相应的XML或者HTML。
DriectUI的自动包装工作,比较好做。只需在控件的相应位置,添加一个控件,把原有控件置为底层即可。
u_dui控件使用方法如下:
- 1 if not isvalid(iu_dui) then
- 2 cb_unwrap.event clicked()
- 3 OpenUserObject(iu_dui, "u_dui", 0, 0)
- 4 iu_dui.of_load("ui2.xml")
- 5
- 6 iu_dui.of_wrap(cb_dui, "")
- 7 iu_dui.of_wrap(cb_wui, "")
- 8 iu_dui.of_wrap(cb_unwrap, "")
- 9
- 10 iu_dui.of_wrap(st_1, "")
- 11 iu_dui.of_wrap(st_2, "")
- 12 iu_dui.of_wrap(sle_1, "")
- 13 iu_dui.of_wrap(sle_2, "")
- 14 end if
对于Datawindow这样的控件,暂时没有包装方案,所以不动即可。

PB界面美化方案八:WebUI自动包装
这个部分最难。涉及的方面非常多。
要求生成的代码必须要有通用性、可定制化、简单清晰。要达到这个要求,必须是动态页面,而非静态HTML。所以选型的时候,重点考虑了React、Vue、Angular,而不是传统的JQuery+Bootstrap。
React作为大热的前端框架,这里就不详细介绍了。React只负责View的部分,这正是我们需要的,相比大而全的Angular更适合WebUI包装。而基于MVVM模型的Vue,则可能在接口方面有更底层的修改需求,所以最终选择了React。
完整的MVC框架,应该是React+Redux组合,则使用PB替换Redux就是我们的目标。首先,我们来分析一下Redux。下面是简化版Redux:
- // Mini Redux
- const createStore = (reducer) => {
- let state;
- let listeners = [];
- const getState = () => state;
- const dispatch = (action) => {
- state = reducer(state, action);
- listeners.forEach(listener => listener());
- };
- const subscribe = (listener) => {
- listeners.push(listener);
- return () => {
- listeners = listeners.filter(l => l !== listener);
- }
- };
- dispatch({});
- return { getState, dispatch, subscribe };
- };
- const reducer = function (state, action) {
- ...
- return null;
- };
- const store = createStore(reducer);
- const state = store.getState();

由于Redux的灵感来自于Flux的单向数据流,所有的数据只有一个入口Reducer。所以,最终设计的JS接口只有一个(又双叒叕是一个接口?!)
- pbSend( { type: 'clicked', payload: {name: 'button', value: ''} } );
u_wui的使用方式与u_dui类似,只多了一个of_render(template, output)接口
- 1 OpenUserObject(iu_wui, "u_wui", 0, 0)
- 2 iu_wui.width = parent.width
- 3 iu_wui.height = parent.height
- 4
- 5 iu_wui.of_wrap(cb_dui, "")
- 6 iu_wui.of_wrap(cb_wui, "")
- 7 iu_wui.of_wrap(cb_unwrap, "")
- 8
- 9 iu_wui.of_wrap(st_1, "")
- 10 iu_wui.of_wrap(st_2, "")
- 11 iu_wui.of_wrap(sle_1, "")
- 12 iu_wui.of_wrap(sle_2, "")
- 13
- 14 iu_wui.of_render("res/html/wui.tpl", "res/html/wui.html")
WebUI在使用时,加载会有一段时间的空白页面, 可以使用各种技术手段来解决,比如加载动画,比如服务端渲染(缓存的方式)等等,本节就不深入了。

预告:PowerPlume共分为内外上下后前6部分18篇,敬请期待。

有人留言说,PB9能不能支持,当然能支持(多了pblua.pbd,pbdui.pbd,pbwui.pbd三个文件)。PB8及以下的由于不支持PBNI,所以不能支持。
WindowsXP不能运行的问题,没有测试过,本次试着降级VC++版本为VC2010进行编译。
源码: PowerPlumeDemoV0.5.3.rar
提供了PB9.0 PB10.5 PB11.5 PB12.5四个版本
Miniblink node.dll: node.rar
Miniblink由于太大,所以没有包含在源码内,采用的是miniblink-190226版本,使用其它版本可能会找不到API。
<本节完>