像写网页一样做客户端界面可能是很多客户端开发的理想。

做好一个可以实现和用户交互的动态网页应该包含两个部分:使用html做网页的布局,使用脚本如vbscript,javascript做用户交互的逻辑。当需求变化时,只需要在服务端把相关代码调整一下,用户即可看到新的内容(界面)。

传统的客户端程序开发流程和网页开发可能完全不同。

首先是界面的布局,在老式的界面布局过程中,程序员先在界面上放好各种控件,然后需要自己通过相应的代码来维护界面在不同状态下控件的显示状态及位置。当界面中元素很多时,单纯布局的代码可能就会非常的复杂。

界面做好了,程序员需要增加代码响应界面中UI元素对应的逻辑。如逻辑比较固定,一旦做好就不需要变化时,这样的实现的程序效率可能更高。然而做客户端的人可能都经历过修改界面的问题。简单的控件坐标调整还好办,如果程序的界面风格甚至整个程序的运行逻辑都要变化时,使用传统的开发方式实现的客户端程序更新起来会非常困难,有时甚至变得不可能实现。

近年来,在各种新兴的界面开发库中使用XML来描述布局已经得到了广泛的应用。使用XML描述布局的基本出发点应该和使用HTML描述网页布局类似,关键是解决界面元素的批量创建及元素间位置的自动布局及UI大小变化后的UI元素自适应。然而到目前为止在UI库中使用脚本来实现逻辑控制的还不多见,据我所知,在流行的UI库中只有qt, bolt这两个项目支持。无论是QT还是Bolt,这两个都是重量级的UI库,而且QT中使用脚本(Bolt不了解)的方式也和网页开发想去甚远。

尽管在DuiEngine时代的代码库中就包含了一个LUA脚本模块,但那个时候的脚本模块也仅限于简单的LUA脚本和C++控件代码之间简单的相互调用,还没有形成一个清晰的应用模式。

经过2015年春节期间对脚本模块的重构,终于在SOUI中实现了和网页开发基本一样的界面开发流程。

先看一个SOUI DEMO中100%使用LUA脚本实现的小游戏效果:

下面我们再看看实现上述效果用到的代码:

在SOUI中,脚本模块和其它如渲染模块等一样是使用类似插件形式实现的,当然首先需要我们在程序的入口加载LUA脚本模块:

int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR /*lpstrCmdLine*/, int /*nCmdShow*/)
{//必须要调用OleInitialize来初始化运行环境HRESULT hRes = OleInitialize(NULL);SASSERT(SUCCEEDED(hRes));
//     LoadLibrary(L"E:\\soui.taobao\\richedit\\Debug\\riched20.dll");int nRet = 0; SComMgr *pComMgr = new SComMgr;{//...//定义一个唯一的SApplication对象,SApplication管理整个应用程序的资源SApplication *theApp=new SApplication(pRenderFactory,hInstance);#ifdef DLL_CORE//加载LUA脚本模块,注意,脚本模块只有在SOUI内核是以DLL方式编译时才能使用。bLoaded=pComMgr->CreateScrpit_Lua((IObjRef**)&pScriptLua);SASSERT_FMT(bLoaded,_T("load interface [%s] failed!"),_T("scirpt_lua"));theApp->SetScriptFactory(pScriptLua);
#endif//DLL_CORE//加载全局资源描述XMLtheApp->Init(_T("xml_init")); {//创建并显示使用SOUI布局应用程序窗口,为了保存窗口对象的析构先于其它对象,把它们缩进一层。
            CMainDlg dlgMain;  dlgMain.Create(GetActiveWindow(),0,0,800,600);dlgMain.GetNative()->SendMessage(WM_INITDIALOG);dlgMain.CenterWindow();dlgMain.ShowWindow(SW_SHOWNORMAL);nRet=theApp->Run(dlgMain.m_hWnd);}//应用程序退出
        delete theApp; //...
    }
exit:delete pComMgr;OleUninitialize();return nRet;
}

其次我们需要在XML布局中的SOUI节点下增加一个script的子节点:

<SOUI trCtx="dlg_main" title="SOUI-DEMO version:%ver%" bigIcon="LOGO:32" smallIcon="LOGO:16" width="600" height="400" appWnd="1" margin="5,5,5,5"  resizable="1" translucent="1" alpha="255"><skin><!--局部skin对象--><gif name="gif_penguin" src="gif:gif_penguin"/><apng name="apng_haha" src="apng:apng_haha"/></skin><style><!--局部style对象--><class name="cls_edit" ncSkin="_skin.sys.border" margin-x="2" margin-y="2" /></style><script src="lua:lua_test"><!--当没有指定src属性时从cdata段中加载脚本--><![CDATA[function on_init(args)SMessageBox(0,T "execute script function: on_init", T "msgbox", 1);endfunction on_exit(args)SMessageBox(0,T "execute script function: on_exit", T "msgbox", 1);endfunction onEvtTest2(args)SMessageBox(0,T "onEvtTest2", T "msgbox", 1);return 1;endfunction onEvtTstClick(args)local txt3=SStringW(L"append",-1);local sender=toSWindow(args.sender);sender:GetParent():CreateChildrenFromString(L"<button pos=\"0,0,150,30\" on_command=\"onEvtTest2\">lua btn 中文</button>");sender:SetVisible(0,1);return 1;end]]></script><root class="cls_dlg_frame" cache="1" on_init="on_init" on_exit="on_exit"><caption pos="0,0,-0,30" show="1" font="adding:8"><icon pos="10,8" src="LOGO:16"/><text class="cls_txt_red">SOUI-DEMO version:%ver%</text><imgbtn id="1" skin="_skin.sys.btn.close"    pos="-45,0" tip="close" animate="1"/><imgbtn id="2" skin="_skin.sys.btn.maximize"  pos="-83,0" animate="1" /><imgbtn id="3" skin="_skin.sys.btn.restore"  pos="-83,0" show="0" animate="1" /><imgbtn id="5" skin="_skin.sys.btn.minimize" pos="-121,0" animate="1" /><imgbtn name="btn_menu" skin="skin_btn_menu" pos="-151,2" animate="1" /></caption><other/></root>
</SOUI>

在script节点中,可以使用src属性来指定脚本所在的资源文件,也可以将脚本直接使用cdata写在script节点中,但是src中指定的脚本优先。

注意,上述XML中我们还在root结点中增加了两个新的属性:on_init, on_exit,这两个属性告诉程序在界面初始化完成及界面销毁前需要执行的两个脚本函数。

实现了上面两步,在SOUI中使用脚本的准备工作已经就绪。

为了实现上图的跑马机效果,首先我们需要在一个界面中使用XML实现基本的界面元素布局:

<?xml version="1.0" encoding="utf-8"?>
<include><window size="full,full" name="game_wnd" on_size="on_canvas_size" id="300"><text pos="0,0,-0,@30" colorBkgnd="#cccccc" colorText="#ff0000" align="center">SOUI + LUA 跑马机</text><window pos="0,[0,-0,-50"><window pos="0,0,-64,-0" name="game_canvas" clipClient="1" colorBkgnd="#ffffff"><!--比赛场地--><gifplayer name="player_1" float="1" skin="gif_horse"><text pos="0,%1" colorText="rgb(255,0,0)" font="size:20">1</text></gifplayer><gifplayer name="player_2" float="1" skin="gif_horse"><text pos="0,0" colorText="rgb(255,0,0)" font="size:20">2</text></gifplayer><gifplayer name="player_3" float="1" skin="gif_horse"><text pos="0,0" colorText="rgb(255,0,0)" font="size:20">3</text></gifplayer><gifplayer name="player_4" float="1" skin="gif_horse"><text pos="0,0" colorText="rgb(255,0,0)" font="size:20">4</text></gifplayer><gifplayer name="flag_win" float="1" skin="gif_win" show="0" id="400"/><text pos="|0,0">赔率:</text><text pos="[0,0,@20,[0" colorText="#ff0000" name="txt_rate">4</text><hr pos="-1,0,-0,-0" mode="vertical" colorLine="#ff0000"/></window><window pos="[0,0,-0,-0"><window pos="0,%12.5,@64,@64" offset="0,-0.5" skin="img_coin" id="1" tip="下注1号马" on_command="on_bet">0</window><window pos="0,%37.5,@64,@64" offset="0,-0.5" skin="img_coin" id="2" tip="下注2号马" on_command="on_bet">0</window><window pos="0,%62.5,@64,@64" offset="0,-0.5" skin="img_coin" id="3" tip="下注3号马" on_command="on_bet">0</window><window pos="0,%87.5,@64,@64" offset="0,-0.5" skin="img_coin" id="4" tip="下注4号马" on_command="on_bet">0</window></window></window><window pos="10,[5,-0,-0" name="game_toolbar"><button name="btn_run" pos="0,|0,@100,@30" offset="0,-0.5" tip="run the game" on_command="on_run">run</button><text pos="]-5,0,@-1,-0" offset="-1,0">现有金币:</text><text pos="-64,0,@64,-0" name="txt_coins" align="center">100</text></window></window>
</include>

在上述XML中,我们使用gifplayer控件定义了4匹马,但是我们并没有使用pos属性定义它的位置,因为它的位置是在变化的,我们需要使用脚本在控制。注意上述XML中为几个按钮控件指定的on_command属性。on_command属性是窗口的点击事件属性,指定后可以执行脚本中对应的函数。

事实上在SOUI中每一个事件都定义了一个在XML中可以映射到脚本脚本函数的属性,这里简单介绍一下这个属性在哪里实现的:

    class SOUI_EXP EventCmd : public TplEventArgs<EventCmd>{SOUI_CLASS_NAME(EventCmd,L"on_command")public:EventCmd(SObject *pSender):TplEventArgs<EventCmd>(pSender){}enum{EventID=EVT_CMD};};

通过上面代码,可以发现,这个属性实际上就是这个事件类在SOUI中的字符串名字。

下面我们看看实现这个跑马机真正使用的LUA脚本代码:

win = nil;
tid = 0;
gamewnd = nil;
gamecanvas = nil;
players = {};
flag_win = nil;coins_all = 100;    --现有资金
coins_bet = {0,0,0,0} --下注金额
bet_rate = 4;        --赔率
prog_max     = 200;    --最大步数
prog_all = {0,0,0,0} --马匹进度function on_init(args)--初始化全局对象win = toHostWnd(args.sender);gamewnd = win:GetRoot():FindChildByNameA("game_wnd",-1);gamecanvas = gamewnd:FindChildByNameA("game_canvas",-1);flag_win = gamewnd:FindChildByNameA("flag_win",-1);players = {gamecanvas:FindChildByNameA("player_1",-1),gamecanvas:FindChildByNameA("player_2",-1),gamecanvas:FindChildByNameA("player_3",-1),gamecanvas:FindChildByNameA("player_4",-1)};--布局on_canvas_size(nil);math.randomseed(os.time());--SMessageBox(0,T "execute script function: on_init", T "msgbox", 1);
endfunction on_exit(args)--SMessageBox(0,T "execute script function: on_exit", T "msgbox", 1);
endfunction on_timer(args)if(gamewnd ~= nil) thenlocal rcCanvas = gamecanvas:GetWindowRect2();local heiCanvas = rcCanvas:Height();local widCanvas = rcCanvas:Width();local rcPlayer =  players[1]:GetWindowRect2();local wid = rcPlayer:Width();local hei = rcPlayer:Height();local win_id = 0;for i = 1,4 dolocal prog = prog_all[i];if(prog<prog_max) thenprog = prog + math.random(0,5);prog_all[i] = prog;local rc = players[i]:GetWindowRect2();rc.left = rcCanvas.left + (widCanvas-wid)*prog/prog_max;players[i]:Move2(rc.left,rc.top,-1,-1);elsewin_id = i;local rc = players[i]:GetWindowRect2();rc.left = rcCanvas.left + (widCanvas-wid);players[i]:Move2(rc.left,rc.top,-1,-1);endendif win_id ~= 0 thengamewnd:FindChildByNameA("btn_run",-1):FireCommand();coins_all = coins_all + coins_bet[win_id] * 4;gamewnd:FindChildByNameA("txt_coins",-1):SetWindowText(T(coins_all));coins_bet = {0,0,0,0};local rcPlayer = players[win_id]:GetWindowRect2();local szFlag = flag_win:GetDesiredSize(rcPlayer);rcPlayer.right = rcPlayer.left + szFlag.cx;rcPlayer.bottom = rcPlayer.top + szFlag.cy;rcPlayer:OffsetRect(-szFlag.cx,-szFlag.cy/3);flag_win:Move(rcPlayer);flag_win:SetVisible(1,1);flag_win:SetUserData(win_id);for i= 1,4 dogamewnd:FindChildByID(i,-1):SetWindowText(T("0"));endendend
endfunction on_bet(args)if tid ~= 0 thenreturn 1;endlocal btn = toSWindow(args.sender);if coins_all >= 10 thenid = btn:GetID();coins_bet[id] = coins_bet[id] + 10;coins_all = coins_all -10;btn:SetWindowText(T(coins_bet[id]));gamewnd:FindChildByNameA("txt_coins",-1):SetWindowText(T(coins_all));endreturn 1;
endfunction on_canvas_size(args)if win == nil thenreturn 0;endlocal rcCanvas =  gamecanvas:GetWindowRect2();local heiCanvas = rcCanvas:Height();local widCanvas = rcCanvas:Width();local szPlayer = players[1]:GetDesiredSize(rcCanvas);local wid = szPlayer.cx;local hei = szPlayer.cy;local rcPlayer = CRect(0,0,wid,hei);local interval = (heiCanvas - hei*4)/5;rcPlayer:OffsetRect(rcCanvas.left,rcCanvas.top+interval);for i = 1, 4 dolocal rc = rcPlayer;rc.left = rcCanvas.left + (widCanvas-wid)*prog_all[i]/prog_max;rc.right = rc.left+wid;players[i]:Move(rc);rcPlayer:OffsetRect(0,interval+hei);endlocal win_id = flag_win:GetUserData();if win_id ~= 0 thenlocal rcPlayer = players[win_id]:GetWindowRect2();local szFlag = flag_win:GetDesiredSize(rcPlayer);flag_win:Move2(rcPlayer.left-szFlag.cx,rcPlayer.top-szFlag.cy/3,-1,-1);endreturn 1;endfunction on_run(args)local btn = toSWindow(args.sender);if tid == 0 thenprog_all = {0,0,0,0};on_canvas_size(nil);tid = win:setInterval("on_timer",200);btn:SetWindowText(T"stop");flag_win:SetVisible(0,1);elsewin:clearTimer(tid);btn:SetWindowText(T"run");tid = 0;endreturn 1;
endfunction on_btn_select_cbx(args)local btn = toSWindow(args.sender);local cbxwnd = btn:GetWindow(2);--get previous siblinglocal cbx = toComboboxBase(cbxwnd);cbx:SetCurSel(-1);
end

在on_init中,我们获得必须的几个UI控件,并保存到LUA的全局变量中。

在on_canvas_size中,我们根据UI大小,自动调整4匹马的位置。

然后在几个按钮的事件响应函数中响应用户操作。

至此一个简单的跑马机效果就完成了。

也许有人觉得这个示例太简单,但请想象一下,采用类似的方法,以后UI及逻辑都可以在服务器控件,客户端就像网页一样每天都可以从服务器更新界面会是什么场景,那时客户端可真就成了客户了。

后记:在SOUI中实现和网页开发类似的脚本功能算是SOUI 2015年非常重要一次更新,本来早就想写一篇这样的博客来介绍,无奈由于原来使用的lua_tinker 0.5c版本导出C++类到LUA还有这样那样的问题,一直没有找到合适的解决方案。

虽然也有比较成熟的方法如,luabind, tolua++等,但是总觉得太庞大,不适合SOUI这个非常轻量级的UI库。

这期间也尝试了如luawrapper, fflua,以及网上找到的别人修改的各版本lua_tinker,但都不理想。

使用其它导出方法就不说了,我也没有深入。使用lua_tinker主要的问题就是导出的子类不能正常调用基类的方法,这一点很头痛,虽然用变通的方法可以获得调用基类方法的能力,但是看起来有点背叛了OOP的思想。

直到今天才搞明白不能调用基类成员最主要的问题来自C++对象的多继承,明白了这个问题才能规避问题,才敢把这个LUA模块拿出来讲解,也顺便把SOUI中使用的LUA内核和5.1升级到了5.2.3。

转载于:https://www.cnblogs.com/setoutsoft/p/4340851.html

第二十三篇:在SOUI中使用LUA脚本开发界面相关推荐

  1. 九宫怎么排列和使用_广告视频配音剪映零基础入门教程第二十三篇:剪辑之九宫格教程...

    朋友圈最火九宫格视频你们知道是怎样制作的吗?我们常常在玩朋友圈的时候想用九宫格照片,但是你们有没有遇到这种情况,想玩九宫格却发现找不到那么多能用的照片,那这时候怎么办呢?玩腻了平常图片的发法,今天我们 ...

  2. STM32F429第二十三篇之电容按键

    文章目录 前言 硬件分析 原理 源程序 主函数 TpadInit GetTimeUntoched GetTimeCharge TpadScan 前言 本文主要介绍电容按键的原理与使用方法,主要使用的A ...

  3. 在Cocos2dX游戏中使用Lua脚本进行游戏开发(基础篇)

    对于游戏公司而言,采用游戏脚本lua.python等进行开发也很常见,但是很多童鞋对脚本并没有很熟悉的概念,本篇则向大家简单介绍脚本的用途以及在Cocos2dx基础用法: Lua和python这些详细 ...

  4. 如何在C++中集成LUA脚本(LuaWrapper For C++篇)

    为什么要用Lua作脚本? 使用Lua作脚本,主要是因为它小巧玲珑(体积小,运行快),而且它的语法又比较简单明了.不过,使用LuaAPI将Lua引擎集成到程序中,确实有一些不方便--用落木随风网友的话来 ...

  5. 在windows程序中嵌入Lua脚本引擎--建立一个简易的“云命令”执行的系统

    在<在windows程序中嵌入Lua脚本引擎--使用VS IDE编译Luajit脚本引擎>开始处,我提到某公司被指责使用"云命令"暗杀一些软件.本文将讲述如何去模拟一个 ...

  6. java中使用lua脚本

    第一步: windows下,先下载安装lua(其他操作系统自行百度,我只说主要基本的流程) 下载地址 我选了lua-5.3.4_Win64_bin.zip为例 第二步: 解压到D盘根路径的lua文件夹 ...

  7. Redis中使用Lua脚本(续)- Linux下Lua-cjson开源库的安装和使用

    Redis中使用Lua脚本(续)- Lua-cjson开源库的安装和使用 问题 原因 解决方案 在Redis的lua脚本编写中,我们可能会用到json的序列化和反序列化. Json序列化: -- Re ...

  8. redis中使用lua脚本

    一.概述 1.什么是lua脚本 Lua是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放. 其设计目的就是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能.因为广泛的应用于:游戏开 ...

  9. 在C/C++中调用LUA脚本简介

    简介 LUA脚本是一种可以嵌入C/C++模块的解释型语言,解释性语言与C/C++之类的编译型语言比较优点在于它无需重新编译即可生效,在处理得当的情况下甚至不需要停下程序.LUA只内置了一个功能简单的库 ...

最新文章

  1. spring原始注解开发-01
  2. 扩展云存储边界,阿里云推出全球首个云定义存储产品
  3. Android Picasso最详细的使用指南
  4. java 地址传递 返回值_Java中的值传递和引用传递
  5. 用jq实现移动端滑动轮播以及定时轮播效果
  6. sift计算效率优化_【计算机视觉】9. 小结
  7. 【转载】国产手机MTK平台MRP软件应用安装大全
  8. okhttp请求使用cookie
  9. 8瓶药水3只小白鼠问题
  10. simplest_ffmpeg_streamer加注释版
  11. 用excel做机器学习
  12. 王牌竞速安装后显示服务器维护,王牌竞速进不去游戏怎么办 如何解决进不去游戏...
  13. 2018乌镇峰会 -- 完美世界萧泓论道互联网与文化交流
  14. 支付宝存漏洞?这10招保护个人信息赶紧保存起来!
  15. windows 10 文件资源管理器 打开ftp乱码怎么破???
  16. 循环冗余校验(CRC)之verilog实现
  17. 南卫理公会大学 计算机排名,南卫理公会大学美国大学排名及专业排名汇总(USNEWS美国大学排名版)...
  18. Windows server 2012R2 设置文件共享目录报错:无法连接到C$管理共享已验证文件夹xxx在计算机xxx上是否存在
  19. Visual Stuido 2005 VSTS Developer Edition 的小虫
  20. 定时闹钟功能(带铃声 可延时)

热门文章

  1. 乐播投延迟很高_定投基金有变化
  2. if循环java语句_java-条件判断和循环语句
  3. python中transform用法_Python Wand transform()用法及代码示例
  4. 如何在html网页中嵌入一段语法高亮的代码?_工程师私藏的代码比较工具,好用!...
  5. android百分比布局失效,Android 百分比布局库【原创】
  6. 【BZOJ4542】大数, 莫队
  7. 19.内在摄像机校准——介绍,理想与真实固有参数之比,改善内在参数_1
  8. 连接器与加载器pdf_pdf转换为excel,你不会,同事点点鼠标2分钟就搞定了
  9. 2017.10.10 Perm 排列计数 失败总结
  10. 【Level 08】U07 Mixed Feelings L4 Learning by heart