HelloBolt4:响应事件
在界面中加入交互,响应各种事件,在脚本代码中更新界面效果.
当鼠标悬浮到正中的Hello,Bolt!文本时,字体变为带下划线的粗体,鼠标变为手型,移出时变回初始的字体和鼠标图案;界面中多了一个关机图标,点击时弹出对话框;点击关闭按钮后,程序退出。
在HelloBolt3中,我们已经通过在布局xml中向UIObjectTree Template上加入各种UIObject的方式建立了一个虽小但五脏俱全的界面。但是仅仅通过布局xml定义的界面,是静态的,不可交互的。要让界面可以动态的交互,还需要加入对各种输入事件的响应代码,在事件响应代码中执行交互逻辑。
在界面中响应各种事件 ,在Bolt中是通过在适当的UIObject对象上加入以lua定义的事件响应函数实现的。
Bolt
中的事件,分为输入事件和非输入事件。用Bolt搭建的界面被激活后,当用户操作界面时,操作系统会向容器窗口发送各种输入消息,比如鼠标移动,点击,键盘输入等等,Bolt会依这些输入消息按照一定的规则触发特定UIObject对象上的特定事件,决定输入消息触发哪些UIObject对象上的事件的规则,称为消息路由。比如鼠标消息的消息路由很直观的就是鼠标在界面上的哪些UIObject对象范围内,就触发其中指定了响应鼠标事件的最顶层(zorder最大)的UIObject对象上的鼠标事件。比如当鼠标在正中的Hello,Bolt!文本上移动时,如果在对应的TextObject对象上定义了OnMouseMove事件的响应函数,该对象就会接收到OnMouseMove事件,该函数会被调用。
非输入事件是Bolt中自定义的,并非输入消息触发的事件。比如当某个UIObject对象被加入到对象树上之时,会触发该对象的OnInitControl事件(这是一个常用的初始化事件)。
在布局xml中,通过在<obj>节点中加入<eventlist>节点,指定对象对特定事件的响应函数。
HelloBolt4 XAR
包MainWnd.xml中的UIObjectTree Template定义中,<obj id="msg" class="TextObject">节点下加入了如下xml节点:
<eventlist>
<event name="OnMouseMove" file="MainWnd.xml.lua" func="MSG_OnMouseMove" />
<event name="OnMouseLeave" file="MainWnd.xml.lua" func="MSG_OnMouseLeave" />
</eventlist>
<eventlist>
节点下有若干<event>子节点,每个<event>节点指定对象对该对象类型支持的一个特定事件的响应函数(每种对象支持的事件参照Bolt元对象介绍),其中name属性指定要响应的事件名,可以是Bolt中定义的各种输入事件和非输入事件, file属性指定定义事件响应函数的文件(路径相对于当前的布局xml), func指定在文件中的事件响应函数的函数名。上面的xml片段就指定 id为msg的对象接收OnMouseMove事件,当事件触发时,调用MainWnd.xml.lua中的定义的MSG_OnMouseMove函数;OnMouseLeave事件亦然。当用户操作鼠标在
”
Hello,Bolt!
”
的文本范围内移动时,MSG_OnMouseMove函数会被调用,同样鼠标离开文本范围时,MSG_OnMouseLeave函数会被调用。
转入MainWnd.xml.lua中OnMouseMove响应函数的定义如下:
function MSG_OnMouseMove(self)
self:SetTextFontResID ("msg.font.bold")
self:SetCursorID ("IDC_HAND")
end
|
这样,当鼠标悬浮到文本之上时,字体变为带下划线的粗体,鼠标变为手型,移出时字体变为初始字体,鼠标变回箭头。
要达到点击下方的关闭按钮退出程序的效果,需要在代表按钮的对象上加入OnLButton事件响应,在MainWnd.xml中的如下xml节点:
<event name="OnLButtonDown" file="MainWnd.xml.lua" func="close_btn_OnLButtonDown" />
注意这里设置<obj id="close.btn" class="LayoutObject">代表的逻辑对象响应鼠标事件,而不是其下的按钮背景接收,是因为在逻辑上我们视这些对象复合成为一个逻辑上的按钮对象,点击按钮关闭程序,而不是点击按钮背景关闭程序,因为按钮上除了背景之外还有其他元素,背景在逻辑上不能代表整个按钮,所以我们加入LayoutObject这样的逻辑对象隔离逻辑层次在定义事件响应时也是非常有必要的。
转到MainWnd.xml.lua, close_btn_OnLButtonDown函数定义中,简单的调用lua内建的os包中的exit方法,简单粗暴的退出进程,这里仅作演示,真正的应用开发中不推荐这么做,因为这样跳过了进程入口函数的正常退出流程,无法执行各种资源释放流程,比如我们在主程序中加入的Bolt反初始化流程。
当UIObject对象被加入到对象树上时,会触发该对象的OnInitControl事件。为对象树的根对象加入非输入事件OnInitControl事件响应,如MainWnd.xml中如下xml节点所示:
<event name="OnInitControl"/>
这是event定义中的又一种形式,如果函数定义的脚本文件为同名布局文件加上.lua后缀(如MainWnd.xml和MainWnd.xml.lua)同时事件响应函数与事件名相同,可以同时省略file以及func属性;否则必须同时显式指明。这样做有时可以减少代码,但是会影响Bolt的加载效率,一般不推荐使用。这里要指定OnInitControl事件的响应函数为MainWnd.xml.lua中定义的OnInitControl函数,可以省略。
转到MainWnd.xml.lua中OnInitControl函数的定义如下:
local owner = self:GetOwner()
local objFactory = XLGetObject("Xunlei.UIEngine.ObjectFactory")
local newIcon = objFactory:CreateUIObject("icon2","ImageObject")
local xarManager = XLGetObject("Xunlei.UIEngine.XARManager")
newIcon:SetResProvider(xarManager)
newIcon:SetObjPos(45,165,45+70,165+70)
newIcon:SetResID("app.icon2")
local function onClickIcon()
XLMessageBox("Don't touch me!")
end
newIcon:AttachListener("OnLButtonDown",true,onClickIcon)
self:AddChild(newIcon)
|
这段代码通过编程方式动态在对象树上加入一个UIObject对象(对象的动态创建),虽然这种方式相较于在布局xml中定义对象来说非常枯燥,但有些时候还是有动态创建UIObject对象的需求的,遵循完备性原则,Bolt依然提供了这样的能力。以上代码中,self参数指向触发事件的对象,这里就是UIObjectTree上的显示底图的根对象, "Xunlei.UIEngine.ObjectFactory"标识的全局对象即是Bolt中用于创建UIObject对象的工厂对象,调用其上的CreateUIObject接口创建id为icon2 , class 为ImageObject的对象。Xunlei.UIEngine.XARManager标识的全局对象是Bolt的XAR包管理器,因为ImageObject需要使用XAR资源包中的位图资源,要通过调用SetResProvider接口将XAR包管理器设置到对象上,对象才可以通过资源id获取到正确的位图资源。之后设置位置和位图资源,并通过调用AttachListener接口加入事件响应函数,最后调用根对象上的AddChild接口,将这个新创建出的对象作为根对象的子对象加入到对象树之上。响应函数经常定义在闭包中,关于lua的闭包请参考lua要快点学。
这样,我们的界面上就多出了一个没有在布局xml中定义的对象,它在界面中显示了一个关机图标,点击它之后会弹出对话框。
从上面动态创建对象的代码中,我们可以看出,动态创建对象跟在布局xml中静态定义对象的能力和流程是相同的,都是确定对象的层次,要加入到哪个对象之下;确定对象的各种属性,如位置,资源等;确定对象的事件响应。其实本质上,在布局xml中静态定义的对象,实际上是在UIObjectTree Template模板被实例化时,Bolt的加载器解析布局中的<obj>节点定义,按照上述流程迭代的将布局xml中定义的对象加入到对象树上,并设置好对象在布局xml中定义的属性和事件响应,两种方式是完全等效的。
MainWnd.xml中
<!--XML最好存储为UTF-8编码-->
<xlue>
<objtreetemplate id="HelloBolt.Tree" class="ObjectTreeTemplate">
<attr>
<left>-200</left>
<top>-200</top>
<width>2000</width>
<height>2000</height>
</attr>
<obj id="app.bkg" class="ImageObject">
<attr>
<left>0</left>
<top>0</top>
<width>429</width>
<height>267</height>
<!--资源相关的属性使用资源定义xml中设置的资源名-->
<image>app.bkg</image>
<alpha>255</alpha>
</attr>
<children>
<obj id="msg" class="TextObject">
<attr>
<left>135</left>
<top>100</top>
<width>250</width>
<height>50</height>
<text>Hello,Bolt!</text>
<textcolor>system.orange</textcolor>
<font>msg.font</font>
</attr>
<eventlist>
<event name="OnMouseMove" file="MainWnd.xml.lua" func="MSG_OnMouseMove" />
<event name="OnMouseLeave" file="MainWnd.xml.lua" func="MSG_OnMouseLeave" />
</eventlist>
</obj>
<!--标题栏,可以模拟App的Titlebar-->
<obj id="title" class="CaptionObject">
<attr>
<left>0</left>
<top>0</top>
<height>32</height>
<width>father.width</width>
<zorder>100</zorder>
</attr>
<children>
<obj id="title.text" class="TextObject">
<attr>
<!-- 使用表达式局中-->
<left>father.width/2-86/2</left>
<top>8</top>
<width>86</width>
<height>24</height>
<text>Hello,Bolt!</text>
<textcolor>system.white</textcolor>
<font>default.font</font>
</attr>
</obj>
</children>
</obj>
<obj id="icon" class="ImageObject">
<attr>
<left>45</left>
<top>100</top>
<width>60</width>
<height>60</height>
<image>app.icon</image>
<!--设置成拉伸模式,ImageObject默认是不会拉伸其对应的位图的-->
<drawmode>1</drawmode>
</attr>
</obj>
<obj id="close.btn" class="LayoutObject">
<attr>
<left>178</left>
<top>214</top>
<width>80</width>
<height>30</height>
</attr>
<children>
<!--对象树布局中直接定义的对象id即使在不同层次上也不能同名-->
<obj id="close.btn.bkg" class="TextureObject">
<attr>
<left>0</left>
<top>0</top>
<width>father.width</width>
<height>father.height</height>
<texture>button.normal</texture>
</attr>
</obj>
<obj id="close.btn.msg" class="TextObject">
<attr>
<left>0</left>
<top>0</top>
<width>father.width</width>
<height>father.height</height>
<font>default.font</font>
<halign>center</halign>
<valign>center</valign>
<text>关闭</text>
</attr>
</obj>
</children>
<eventlist>
<event name="OnLButtonDown" file="MainWnd.xml.lua" func="close_btn_OnLButtonDown" />
</eventlist>
</obj>
</children>
<eventlist>
<!--定义事件响应时,如果方法被定义在同名的.xml.lua中同时方法名为事件名时,可以省略file 及 func属性,会损失加载效率, 不推荐-->
<event name="OnInitControl"/>
</eventlist>
</obj>
</objtreetemplate>
<hostwndtemplate id="HelloBolt.Wnd" class="FrameHostWnd">
<attr>
<mainwnd>1</mainwnd>
<title>Bolt</title>
<layered>1</layered>
<left>200</left>
<top>100</top>
<!--注意这是容器窗口的大小,设置的要合理-->
<width>429</width>
<height>327</height>
<cacheleft>0</cacheleft>
<cachetop>0</cachetop>
<cachewidth>1000</cachewidth>
<cacheheight>720</cacheheight>
<center>1</center>
<topmost>0</topmost>
<visible>1</visible>
<enable>1</enable>
<active>1</active>
<maxbox>0</maxbox>
<minbox>0</minbox>
<minwidth>100</minwidth>
<minheight>72</minheight>
<maxwidth>1000</maxwidth>
<maxheight>720</maxheight>
<appwindow>1</appwindow>
<fps>30</fps>
</attr>
</hostwndtemplate>
</xlue>
MainWnd.xml.lua中:
--lua文件必须是UTF-8编码的(最好无BOM头)
function close_btn_OnLButtonDown(self)
----os.exit 效果等同于windows的exit函数,不推荐实际应用中直接使用
os.exit()
end
function OnInitControl(self) --self参数指向触发事件的对象,这里就是UIObjectTree上的显示底图的根对象
--动态创建一个ImageObject,这个Object在XML里没定义
local objFactory = XLGetObject("Xunlei.UIEngine.ObjectFactory") --"Xunlei.UIEngine.ObjectFactory"标识的全局对象即是Bolt中用于创建UIObject对象的工厂对象
local newIcon = objFactory:CreateUIObject("icon2","ImageObject") --调用其上的CreateUIObject接口创建id为icon2 , class 为ImageObject的对象
local xarManager = XLGetObject("Xunlei.UIEngine.XARManager") --Xunlei.UIEngine.XARManager标识的全局对象是Bolt的XAR包管理器,因为ImageObject需要使用XAR资源包中的位图资源,
newIcon:SetResProvider(xarManager) --要通过调用SetResProvider接口将XAR包管理器设置到对象上,对象才可以通过资源id获取到正确的位图资源。
newIcon:SetObjPos(45,165,45+70,165+70)
newIcon:SetResID("app.icon2")
local function onClickIcon() --在闭包中定义函数,最直观的说明是,将一个函数定义写在另一个函数定义之内,那么这个位于内部的函数定义中便可以直接使用外部函数中的局部变量(称为非局部变量)。
XLMessageBox("Don't touch me!")
end
--绑定鼠标事件的响应函数到对象
newIcon:AttachListener("OnLButtonDown",true,onClickIcon) --并通过调用AttachListener接口加入事件响应函数
self:AddChild(newIcon) --最后调用根对象上的AddChild接口,将这个新创建出的对象作为根对象的子对象加入到对象树之上
end
function MSG_OnMouseMove(self)
self:SetTextFontResID ("msg.font.bold")
self:SetCursorID ("IDC_HAND")
end
function MSG_OnMouseLeave(self)
self:SetTextFontResID ("msg.font")
self:SetCursorID ("IDC_ARROW")
end