一、模板的定义
目前支持三种模板:
a) Hostwnd模板,节点名为hostwndtemplate
b) Object模板,节点名为objecttemplate
c) ObjectTree模板,节点名为objtreetemplate
以上三种模板都是xlue根节点下面的二级节点,这里需要注意的是xlue下面的二级节点配置的HostWnd、Object和ObjectTree都是模板,也就是不存在单纯的名为hostwnd、object和objecttree的节点了。例如:
<xlue>
<objtemplate id="xxxx" class="xxxx">
……..
</objtemplate>
<hostwndtemplate id=”xxx” class=”xxxx”>
……..
</hostwndtemplate>
<objtreetemplate id=”xxx”>
……..
</objtreetemplate>
</xlue>
关于模板是否需要配置class字段
a) 在配置模板时候,hostwndtemplate一般情况下需要配置class,因为调用CreateInstance来实例化模板时候,需要知道该hostwnd的class;但假如该模板不是用来实例化,只是用来被其它模板引用的,那么class可以不用配置,但是同时该hosttemplate就失去了被实例化的能力了。
b) objtreetemplate 不需要配置class,因为objtree是一系列object组合起来的概念,没有具体的class,所以不需要指定class。
c) objtemplate 可以配置class,也可以不配置,因为在object A引用object模板B时候,并不会去判断模板B的类别是否和当前的object的类别A是否匹配。并且由于objtemplate不可以单独被实例化,只能用作引用,所以class可有可无。一般作为提示作用,可以参照下面的指导:
i. 假如写了一个objtemplate,并且希望告诉其他使用该模板的用户,该objtemplate期望被相同类型的obj引用,那么可以显示写明class为”xxx”,这样使用者在使用该模板时候,就会注意到。
ii. 假如写了一个objtemplate,并且希望告诉其他使用该模板的用户,该objtemplate可以被任何类型的的obj引用,比如一个textobject可以引用,imageobject也可以引用,那么最好不要配置class,以免误导模板使用者。
二、模板的引用
a) 模板引用的方法:在需要引用模板的其它模板或者object节点的属性里面,使用templateid=”指定模板的id”
b) 模板引用的一个基本原则是模板只可以被同类型的object或者其他模板来引用。
c) hostwndtemplate 只可以被其它的hostwndtemplate来引用,例如
<hostwndtemplate id="BoltFrameWnd" >
<attr>
…….
</attr>
< /hostwndtemplate>
<hostwndtemplate id=”Main” class=”FrameHostWnd” templateid=” BoltFrameWnd”>
<attr>
……..
</attr>
</hostwndtemplate>
上面例子中,id为Main的模板引用了id为BoltFrameWnd的模板,并且两个模板的类型都一致,都为hostwndtemplate
d) objtreetemplate只可以被其它的objtreetemplate模板来引用,例如
<objtreetemplate id=”GlobalObjTree”>
<attr>
……
</attr>
<obj id=”xxx” templateid=”xxxx”> //这里的obj也引用了一个模板
…….
</obj>
</objtreetemplate>
<objtreetemplate id=”MainObjTree” templateid=” GlobalObjTree”>
<attr>
……
</attr>
<obj id=”yyy” >
……
</obj>
</objtreetemplate>
上面的例子中,id为MainObjTree的模板引用了id为GlobalObjTree的模板,并且两个模板的类型都一致,都为objtreetemplate。
e) objtemplate相比上面两个模板,情况要复杂些。Objtemplate可以被以下三种情况来引用:
在说明之前,我们先配置好一个objtemplate模板:
<objtemplate id="AnyObj" >
<attr>
……
</attr>
<eventlist>
<event name=”OnBind”>
…….
</event>
</eventlist>
</objtemplate>
i. 被其它的objtemplate来引用,例如
<objtemplate id="ImageObj" templateid=”AnyObj” >
<attr>
……
</attr>
</objtemplate>
上面又定义了一个objtemplate模板,其中引用了id为AnyObj的模板
ii. 被objtree里面的obj引用,例如
<objtreetemplate id="objtree">
<attr>
……
</attr>
<obj id="Bkn" class="ImageObject" templateid=”AnyObj”>
<attr>
……
</attr>
<eventlist>
……
</eventlist>
</obj>
</objtreetemplate>
iii. 被control定义里面的objtemplate下面的obj引用,例如
<control class="LoginPanel">
<attr_def>
……
</attr_def>
<method_def>
……
</method_def>
<event_def>
……
</event_def>
<objtemplate>
<children>
<obj id="Icon" class="ImageObject" templateid=”AnyObj”>
……
</obj>
</children>
</objtemplate>
</control>
f) 模板的引用不可以出现环状引用,比如模板A引用了模板B,模板B又引用了模板C,而模板C又反过来引用了模板A,这样便形成了一个环状的引用;程序内对环状引用做了处理,不会导致无限递归,但是可能会导致不可预期的结果出现。
顺便说一下我们对于环状引用的处理逻辑:模板展开最多到当前正在开展的节点。比如要展开模板A了,由于A引用了B,此时会先去展开B,而B又导致C的展开,C会不会又导致A展开?答案是不会,因为目前A是正在展开的节点,所以C此时不会展开了,这个结果可能是使用者所预期的,也可能是写错了,但是建议不要这么做,除非你很明确这样做会导致的结果。
另外需要注意的是,所有模板只会展开一次,所以一旦A展开了,那么B、C也就同时展开了,以后再使用模板B、C时候,不会再次展开了,所以哪怕C模板以后再单独使用,也不会再去合并所引用的模板A了。
三、模板的合并merge
我们先事先给出两个称呼:假如模板或者obj A使用了模板B,那么我们称呼A为实体,B为模板,下面在讨论合并过程中,会经常用到“实体和模板”这两个概念,简化描述。
模板的合并按照最小合并粒度分割,可以分为以下几个粒度:
a) 属性列表的合并(attr节点)
属性列表合并的依据是:属性名,也就是节点名称相同的属性进行合并,比如下面例子中的left、top等
属性列表的合并策略是:对于某个属性,假如实体和模板都存在,那么实体的会覆盖模板的;假如模板的存在但是实体的不存在,那么实体的将继承模板的;
假如只是实体的存在,那么很明显会使用实体的。下面是一个例子:
<objtemplate id=”A”>
<attr>
<left>10</left>
<top>10</top>
</attr>
</objtemplate>
<objtemplate id=”B” templateid=”A”>
<attr>
<left>20</left>
<width>1000</width>
<height>800</height>
</attr>
</objtemplate>
那么模板B merge之后,将会是如下定义
<objtemplate id=”B” >
<attr>
<left>20</left> // 覆盖模板A的left属性
<top>10</top> // 继承模板A的top属性
<width>1000</width> // 使用自有的width属性
<height>800</height> // 使用自有的height属性
</attr>
</objtemplate>
b) 事件event的合并
event的合并策略:属性name值相同的进行合并,也就是<event name=”xxx”>
xxx相同的两个event进行合并
注意:Event的合并可能会出现多个chunk,例如:
<event name=”OnBind”>
XLMessagexBox(“OnBindFirst”)
</event>
<event name=”OnBind”>
XLMessageBox(“OnBindSecond”)
</event>
那么这两个event合并之后,根据不同的合并策略,可能会出现下面的情形
<event name=”OnBind”>
<chunk >
XLMessagexBox(“OnBindFirst”)
return true
</chunk>
<chunk >
XLMessagexBox(“OnBindSecond”)
return true
</chunk>
</event>
这种chunklist对使用者来是透明的,多个chunk会被当成多个响应函数,挂接到OnBind事件上面,并且根据在xml里面的定义顺序来响应,比如上面的代码运行之后,在出发OnBind事件时候,会先弹出OnBindFirst的提示框,然后弹出OnBindSecond的提示框,这里需要特别注意的是,如果希望在执行完一个chunk代码之后,继续执行下一个chunk代码,需要显示返回true,否则将终止执行,下面的chunk永远没有执行的机会,这也是在第一个chunk里面返回true的缘故,假如不显示写明return true,那么默认的是返回false的。
event的合并比较复杂,是基于策略的合并,需要在event节点的属性字段里面配置属性mergetype=”xxx” 有如下三种合并策略:
i. mergetype=”front” 表示当前的event和模板对应的event合并生成chunklist时候,自己的chunk放在chunklist的最前面,模板的相应的chunk需要放在后面,例如
<objtemplate id=”src” >
<eventlist>
<event name=”OnBind”>
XLMessagexBox(“OnBindSecond”)
return true
</event>
</eventlist>
</objtemplate>
<objtemplate id=”dest” templateid=”src” >
<eventlist>
<event name=”OnBind” mergetype=”front”>
XLMessagexBox(“OnBindFirst”)
return true
</event>
</eventlist>
</objtemplate>
其中dest的OnBind Event指明了合并策略为mergetype=”front”,那么模板dest和src合并之后将是下面的样子:
<objtemplate id=”dest” templateid=”src” >
<eventlist>
<event name=”OnBind”>
<chunk> //dest的event放在前面
XLMessagexBox(“OnBindFirst”)
return true
</chunk>
<chunk> //src的event放在后面
XLMessagexBox(“OnBindSecond”)
return true
</chunk>
</event>
</eventlist>
</objtemplate>
ii. mergetype=”back” 表示当前的event和模板对应的event合并生成chunklist时候,自己的chunk放在chunklist的最后面,模板的相应的chunk需要放在前面,例如
<objtemplate id=”src” >
<eventlist>
<event name=”OnBind”>
XLMessagexBox(“OnBindSecond”)
return true
</event>
</eventlist>
</objtemplate>
<objtemplate id=”dest” templateid=”src” >
<eventlist>
<event name=”OnBind” mergetype=”back” >
XLMessagexBox(“OnBindFirst”)
return true
</event>
</eventlist>
</objtemplate>
其中dest的OnBind Event指明了合并策略为mergetype=”back”,那么模板dest和src合并之后将是下面的样子:
<objtemplate id=”dest” templateid=”src” >
<eventlist>
<event name=”OnBind”>
<chunk> //src的event放在前面
XLMessagexBox(“OnBindSecond”)
return true
</chunk>
<chunk> //dest的event放在后面
XLMessagexBox(“OnBindFirst”)
return true
</ chunk >
</event>
</eventlist>
</objtemplate>
iii. mergetype=”overlay” 标识当前的event将覆盖所有模板的对应的event,也就是只保留自己新定义的event,默认是overlay,也就是不注明mergetpye=”xxx”时候,就是采用overlay的合并策略,例如
<objtemplate id=”src” >
<eventlist>
<event name=”OnBind”>
XLMessagexBox(“OnBindSecond”)
return true
</event>
</eventlist>
</objtemplate>
<objtemplate id=”dest” templateid=”src” >
<eventlist>
<event name=”OnBind” mergetype=”overlay”>
XLMessagexBox(“OnBindFirst”)
return true
</event>
</eventlist>
</objtemplate>
其中dest的OnBind Event指明了合并策略为mergetype=”overlay”,那么模板dest和src合并之后将是下面的样子:
<objtemplate id=”dest” templateid=”src” mergetype=”front” >
<eventlist>
<event name=”OnBind”> //只保留了dest的event
XLMessagexBox(“OnBindFirst”)
return true
</event>
</eventlist>
</objtemplate>
c) 事件列表的合并
事件列表的合并策略是:尝试对实体和模板里面每个event进行合并,也就是合并的最小粒度是event。
i. 对于实体里面存在但是模板里面不存在的event,保留实体里面的event
ii. 对于实体里面不存在,但是模板里面存在event,继承模板里面的event
iii.对于实体里面和模板里面都存在的event,则按照event的合并策略对两个event进行合并
例如
实体template定义如下
<eventlist>
<event name=”A”>
A’s code
</event>
<event name=”B”>
B’s code
</event>
</eventlist>
模板template定义如下
<eventlist>
<event name=”A”>
A2’s code
</event>
<event name=”C”>
C’s code
</event>
</eventlist>
那么实体和模板的eventlist合并后如下
<eventlist>
<event name=”A”>
<chunk>
A’s code
</chunk>
<chunk>
A2’s code
</chunk>
</event>
<event name=”B”>
B’s code
</event>
<event name=”C”>
C’s code
</event>
</eventlist>
d) obj 节点的合并,分别对里面的属性列表attr、孩子列表children和事件列表eventlist进行合并
e) children节点也就是孩子列表的合并,尝试对里面的id相同的obj进行合并,存在下面几种情况:
i. 假如都是有id的obj,那么如果实体和模板的obj id相同,则进行合并,如果实体存在而模板不存在,则保留实体的;假如实体不存在而模板存在,则继承自模板。
ii. 对于没有id的obj,那么就很简单了,所有实体的没有id的obj保留,所有模板的没有id的obj继承下来。
例如:
实体的children节点:
<children>
<obj id=”A”>
</obj>
<obj id=”B”>
</obj>
<obj > // 实体的没有id的obj
</obj>
</children>
模板的children节点:
<children>
<obj id=”A”>
</obj>
<obj id=”C”>
</obj>
<obj > // 模板的没有id的obj
</obj>
</children>
那么实体和模板合并后的结果是:
<children>
<obj id=”A”>
// 这里是实体的A obj和模板的A obj合并后的结果
</obj>
<obj id=”B”> //实体里面存在而模板里面不存在的obj,则保留实体的
</obj>
<obj id=”C”> //模板里面存在而实体里面不存在的obj,则保留模板的
</obj>
<obj > // 实体的没有id的obj,保留
</obj>
<obj > // 模板的没有id的obj,直接继承
</obj>
</children>
f) objtreetemplate节点的合并
分别对下面属性列表attr和根节点obj进行合并,两种节点的合并策略如上所述
g) hostwndtemplate 节点的合并
分别对下面的属性列表attr和根节点eventlist节点进行合并,两种节点的合并策略如上所述
四、模板的展开
我们把一个节点和它所有的子节点都依照相应的策略merge的过程叫做展开,假如一个模板展开以后,该模板就是已经是可以直接进行解析的xml,里面不包含任何的templateid=”xxx” 的引用了。需要注意的是,一个节点的开展可能会导致下面多个子节点的展开,比如子节点也有引用了其它的template。我们给出明确的展开策略:自底向上,由外向内的展开策略
a) 由外向内:假如模板A引用了模板B,模板B引用了模板C,那么模板A展开时候,模板B也要展开,并且先于A展开;同理,假如模板C也引用了其它的模板,那么模板C也要先于模板B展开,例如
<objtemplate id=”C”>
</ objtemplate >
<objtemplate id=”B” templateid=”C”>
</ objtemplate >
<objtemplate id=”A” templateid=”B”>
</ objtemplate >
那么模板A展开时候,步骤如下:
1、C没有引用其它模板,则不用展开,可以直接被B模板使用
2、B先展开,展开结果如下
<objtemplate id=”B”>
// B和C合并后的结果
</ objtemplate >
<objtemplate id=”A” templateid=”B”>
</ objtemplate >
3、A最后展开,展开结果如下
<objtemplate id=”A”>
// A、B和C合并后的结果
</ objtemplate >
b) 自底向上:假如模板A引用了模板C,A的子节点B引用了模板D,那么模板A展开时候,模板B也要展开,并且模板B的展开要先于父节点A的展开。例如:
<objtemplate id=”C”>
</ objtemplate >
<objtemplate id=”D”>
</ objtemplate >
<objtemplate id=”A” templateid=”C”>
<attr>
</attr>
<children>
<obj id=”B” templateid=”D”>
</obj>
</children>
</ objtemplate >
那么模板A展开时候,步骤如下:
1、模板B先展开,展开结果如下:
<objtemplate id=”C”>
</ objtemplate >
<objtemplate id=”A” templateid=”C”>
<attr>
</attr>
<children>
<obj id=”B”>
// 模板B和D合并后的结果
</obj>
</children>
</ objtemplate >
2、模板A最后展开,结果如下:
<objtemplate id=”A”> // 模板A和C合并后的结果。
<attr>
</attr>
<children>
<obj id=”B”>
// 模板B和D合并后的结果
</obj>
</children>
</ objtemplate >
c) 对于有多层嵌套和多层引用的模板,都遵循上面两个合并策略。
五、模板的展开相对比较复杂,尤其是对于多层嵌套的模板的展开,假如引用的模板很复杂,展开结果可能和预期的结果不一致。所以为了方便大家使用,我会提供一个工具用以在编写时候提前展开,看看展开后的结果和你们所预期的时候一致。