第 7 章 ZUML页面及XUL组件集

目录

基本组件
标签
按钮
单选按钮和单选按钮组
图像
图像映射(Imagemap)
音频
输入控件
日历
进度条
Slider
计时器
分页
窗口
标题
closables属性
sizable属性
样式类
contentStyle属性
边框
重叠,弹出,Modal,标示和嵌入
position属性
通用对话框
布局组件
嵌套的borderlayout组件
sizeborder属性
splittablecollapsible 属性
flex 属性
open 属性
onOpen 属性
箱式模型
spacing属性
widthsheights 属性
分割器
Tab箱
嵌套tab box
The Accordion Tab Boxes
orient属性
Tabs的align属性
closable属性
disabled属性
Tab面板的随机存取
网格
滚动网格
可变列宽
分页网格
排序
实况数据
辅助表头
特殊属性
更多的布局组件
Separators 和空格
Group boxes
工具栏
菜单栏
执行一个菜单命令
像复选框一样使用菜单项目
autodrop属性
onOpen事件
更多的菜单特性
上下文菜单
定制的tooltip及弹出菜单
onOpen事件
列表框
多列列表框
栏头
栏尾
下拉列表
多选
滚动列表框
可变列表头
分页列表框
排序
特殊属性
实况数据
包含按钮的列表框
树控件
open属性和onOpen事件
多选
分页
特殊属性
Tree控件的打开时创建
下拉列表框
autodrop属性
description属性
onOpen事件
onChanging事件
Bandboxes
closeDropdown方法
autodrop属性
onOpen事件
onChanging事件
图表
实况数据
向下钻取(onClick事件)
操作区
拖放
draggabledroppable属性
onDrop 事件
使用多选拖曳
可拖曳组件的多种类型
HTML相关组件
html组件
Native命名空间,http://www.zkoss.org/2005/zk/native
XHTML命名空间, http://www.w3.org/1999/xhtml
include组件
style组件
script组件
iframe组件
用HTML FORM 和Java Servlets
name属性
支持name属性的组件
丰富用户界面
客户端行为
引用一个组件
onshowonhide 行为
CSA JavaScript工具
事件
鼠标事件
按键事件
输入事件
List和Tree 事件
Slider和Scroll事件
其它事件

本章描述了XUL组件集 。不同于其他的实现,ZK的XUL组件,经过了跨越网络合作的优化(co-operating across Internet)。有些组件可能不会完全兼容XUL技术标准。为方便起见,我们有时指它们为XUL组件。

基本组件

标签

Label组件用来呈现一段文字。

<window border="normal">
   Hello World
</window>

如果你想为Label指定一个属性,并且如下方式明确指定<label>。

<window border="normal">
   <label style="color: red" value="Hello World"/>
</window>

[提示]:ZUML为XML而不是HTML,所以并不接受&nbsp;。但是可以用&#160;代替。

pre, hyphen, maxlengthmultiline 属性

你可以使用 pre hyphen maxlengthmultiline 属性来控制如何展示一个label。例如,如果指定pre为true,所有的空格(white spaces),例如换行,空白,制表符(new line, space和tab)都会被保留。

hyphen

pre

maxlenth

描述

false

false

positive

切去超过指定最大长度的字符

true

any

positive

如果一个单词超过了最大长度,此单词会被截断(hypernated)

false

true

any

maxlenth被忽略

any

any

0

pyphen被忽略

<window border="normal" width="100px">
<vbox id="result">
 
</vbox>
   <zscript><![CDATA[
 
   String[] s = {"this is 9", "this is ten more to show",
   "this framework", "performance is everything"};
   for (int j = 0; j < s.length; ++j) {
      Label l = new Label(s[j]);
      l.maxlength = 9;
      l.hyphen = true;
      l.parent = result;
   }
   ]]></zscript>
</window>

multiline 属性与pre类似,除了multiline在每行的开始保留了换行和空格。

按钮

有两种类型的按钮:buttontoolbarbutton。除了外观,它们的功能使相似的。button组件使用HTML BUTTON标记,而toolbarbutton组件使用HTML A标记。

你可以使用labelimage属性来为一个按钮指定标签和图像。如果这两个属性都被指定,dir控制`哪个组件显示在前方,orient控制布局为横向或纵向。

<button label="Left" image="/img/folder.gif" width="125px"/>
<button label="Right" image="/img/folder.gif" dir="reverse" width="125px"/>
<button label="Above" image="/img/folder.gif" orient="vertical" width="125px"/>
<button label="Below" image="/img/folder.gif" orient="vertical" dir="reverse" width="125px"/>

除了通过URL来指定图片,你可以使用setImageContent方法为按钮指定一个动态生成图像。参考下面的章节获取细节。

提示:所有包含image属性的组件都提供了setImageContent方法。简单来说,setImageContent方法用于动态生成图像,而image用于通过URL指定图像。

onClick 事件 和href属性

有两种方式为buttontoolbarbutton添加行为。首先需要为onClick指定一个监听器。然后为href属性指定一个URL。如果都被指定,href属性拥有更高的优先级,也就是说 onClick 事件不会被发送。

<button onClick="do_something_in_Java()"/>
<button href="/another_page.zul"/>

org.zkoss.zk.ui.Execution接口的 sendRedirect 方法

当处理一个事件时, 你可以决定停止处理当前桌面,并且通过sendRedirect 方法来转到另一个页面。换句话说,下面的例子中两个按钮是等价的(从用户的观点来看)。

<button onClick="Executions.sendRedirect(&quot;another.zul&quot;)"/>
<button href="another.zul"/>

既然 onClick 事件被送到了服务器去处理,你可以在调用sendRedirect前添加更多的逻辑,例如,当特定的条件被满足时转到另一个页面。

另外,href属性在客户端方面被完全处理。当用户点击按钮时,你的应用程序并不会被察觉

单选按钮和单选按钮组

一个单选按钮是可以被打开或关闭的组件。单选按钮可以被分组,称为radiogroup。在相同的组内,同一时间仅可以有一个按钮被选中。

<radiogroup onCheck="alert(self.selectedItem.label)">
   <radio label="Apple"/>
   <radio label="Orange"/>
   <radio label="Banana"/>
</radiogroup>

多用途设计版面

你可以混合使用radiogroupradio来组成你想要的布局,如下所示。

<radiogroup>
   <grid>
   <rows>
      <row><radio label="Apple" selected="true"/> Fruit, music or computer</row>
      <row><radio label="Orange"/><textbox/></row>
      <row><radio label="Banana"/><datebox/></row>
   </rows>
   </grid>
</radiogroup>

单选按钮属于离其最近的radiogroup。你甚至你可按如下方式嵌套使用radiogroup。每个

radiogroup是独立的,虽然可能有某种视觉重叠(visual overlap)。

<radiogroup>
   <grid>
   <rows>
      <row><radio label="Apple" selected="true"/> Fruit, music or computer</row>
      <row><radio label="Orange"/>
         <radiogroup>
         <radio label="Small"/>
         <radio label="Large" selected="true"/>
         </radiogroup>
      </row>
      <row><radio label="Banana"/><datebox/></row>
   </rows>
   </grid>
</radiogroup>

图像

image 用于在浏览器端展示图像。有两种方式来为image组件指定一个图像。一种方法是使用src属性指定图像的URI,这种方法与HTML支持的相似。如果你想展示一张景台图像,或任何可以通过URL定位的图像,这种方法很有用。

<image src="/some/my.jpg"/>

本地图像

就像使用其他可以接受URI的属性一样,你可以指定"*"来定位一张本地图像。例如,如果对不同的地区对应不同的图像,你可以按如下方式使用。

<image src="/my*.png"

然后假定你的用户以de_DE作为首选地区访问你的页面。ZK会设法定位名称为/my_de_DE.png的图像。如果没有找到,则会尝试/my_de.png ,最后是/my.png。

参考国际化一章中浏览器和本地化URI一节来获取细节。

第二种方法是使用setContent方法来直接为image组件指定图像的内容。一旦被指定,图像会展示在浏览器端并会被动态更新。这种方法指定适用于动态生成的图像。

例如,你可以按如下方式为用户指定的位置生成一个映射。

Location: <textbox onChange="updateMap(self.value)"/>
Map: <image id="image"/>
<zscript>
   void updateMap(String location) {
      if (location.length() > 0)
         image.setContent(new MapImage(location));
   }
</zscript>

在上面的例子中,我们假定有一个MapImage类用于产生指定位置的映射,即所谓的商业逻辑(business logic)。

注意,image组件仅接受org.zkoss.image.Image中的内容。如果有工具生成的图像不是这个格式,可以使用org.zkoss.image.AImage类来将一个二进制阵列数据,文件或输入流包装成Image接口

在传统的Web应用程序中,缓存一个动态生成的图像是很复杂的。有了image组件,你就不需要担心这些了。一旦指定了一个图像的内容,它就属于image组件,而且当image组件不再被使用时,它所占用的内存会被自动释放。

提示:如果你想指定非图像音频文件的内容,例如PDF,可以使用iframe组件。参考相关章节获取细节。

图像映射(Imagemap)

imagemap 是一个特殊的image组件。它接受image组件的所有属性。但是不同于image,当用户点击一张图像时,onClick 事件及鼠标坐标的位置会一起被送回服务器。相比之下,image发送的onClick事件不包含坐标。

鼠标位置的坐标是屏幕像素,从图像的左上角开始计数,始于(0,0)。这将会作为

org.zkoss.zk.ui.event.MouseEvent的实例存储。一旦应用程序接收了onClick事件,就可以通过getXgetY方法来检查鼠标位置的坐标。

例如,如果用户点击了下列语句展示图像的(137,167)像素(从左上角开始) ,那么用户就会得到如下所示的结果:

<imagemap src="/img/sun.jpg" onClick="alert(event.x + &quot;, &quot; +event.y)"/>

应用程序通常使用坐标来决定用户点击了那个部分,然后作出相应的相应。

区域

通过为imagemap然后,组件添加area子组件,开发人员可以代替使用应用程序本身处理坐标的方法。

<imagemap src="/img/sun.jpg" onClick="alert(event.area)">
  <area id="First" coords="0, 0, 100, 100"/>
  <area id="Second" shape="circle" coords="200, 200, 100"/>
</imagemap>

然后,imagemap组件将鼠标的位置坐标翻译成一个逻辑名字:用户点击的区域标识。

例如,如果用户点击(150,150),将会得到如下描绘的结果:

shape属性

area 组件支持三种形状:圆,多边形和矩形(circle, polygon and rectangle)。鼠标位置的坐标是屏幕像素,从图像的左上角开始计数,始于(0,0)。

形状

坐标/描述

circle

coords="x, y, r"

x y定义圆心坐标, r 为半径,以像素为单位。

polygon

coords="x1, y1, x2, y2, x3, y3..."

一对x,y定义多边形的一个定点。定义三角形至少需要三对坐标。多边形会自动关闭,所以不需要为了关闭区域而在列表的末尾重复第一个坐标。

rectangle

coords="x1, y1, x2, y2"

第一对坐标为矩形的一个角,另一对为斜对角。矩形只是为多边性指定四个顶点的简单方式。

如果一个area组件内的坐标覆盖了另外一个,第一个拥有更高的优先权。

音频

audio 组件用来在浏览器端播放音频。就像image,你可以使用src属性来指定音频资源的URL,或使用setContent方法来指定一段动态生成的音频。

依靠浏览器和音频插件,开发人员可以使用play stoppause方法来控制播放一段音频。目前,包含多媒体的Internet浏览器都有这种控制机制。

输入控件

XUL组件支持一套输入控制组件:textbox intbox decimalbox doublebox datebox, comboboxbandbox,用于输入各种类型的数据 。

<zk>
   <textbox/>
   <datebox/>
</zk>

提示:comboboxbandbox 是特殊的输入框。他们共享这里描述的公用属性。关于它们各自独特的特点,将会在之后的 comboboxbandbox 章节中讨论。

type属性

你可以为textbox 组件指定值为passwordtype属性。这样将不会显示用户输入的内容。

Username: <textbox/>
Password: <textbox type="password"/>

format属性

通过使用format域,可以控制输入控件的格式。默认为null。对于datebox,意味着 yyyy/MM/dd。而对与intboxdecimalbox,则以为着根本没有格式。

<datebox format="MM/dd/yyyy"/>
<decimalbox format="#,##0.##"/>

就像其他的任何属性,你可以动态的改变格式,如下所示:

<datebox id="db"/><button label="set MM-dd-yyyy" onClick="db.setFormat(&quot;MM-dd-yyyy&quot;)"/>

无鼠标输入datebox

  1. Alt+DOWN 弹出日历 .

  2. LEFT RIGHT UPDOWN 改变日历中选中的日期.

  3. ENTER 将选中的日期复制到 date.

  4. Alt+UPESC 放弃选择并关闭日历. .

约束

使用constraint属性可以控制输入控件接受什么值。它可以使no positive no negative no zero no empty no future no past no today和一个正则表达式的集合。前三个约束金使用于intboxdecimalboxno future no past,和no today约束仅适用于dateboxno empty适用于任何组件。正则表达式约束仅适用于字符串类型组件,例如textbox comboboxbandbox

使用两个或更多的约束时,用逗号翻开约束,如下:

<intbox constraint="no negative,no zero"/>

为了指定一个正则表达式,你可以使用/来包围表达式。如下:

<textbox constraint="/.+@.+\.[a-z]+/"/>

注:

  1. 上面的语句为XML,所以不必使用\\来表示反斜杠。另一方面,如果写在Java代码中,则是需要的。

new Textbox().setContraint("/.+@.+\\.[a-z]+/");

  1. 允许混合使用正则表达式与其他约束,但要用逗号分隔。

`如果你想为应用程序展示定制的信息,而不是默认的,可以添加约束及验证失败后的信息,用

冒号隔开。

    如果你想为应用程序展示定制的信息,而不是默认的,可以添加约束及验证失败后的信息,用

    冒号隔开。

    <textbox constraint="/.+@.+\.[a-z]+/: e-mail address only"/>
    <datebox constraint="no empty, no future: now or never"/>
    

    注:

    1. 如果指定了错误信息,那么它必须为末尾元素,且以冒号开始。

    2. 为了支持多语言,你可以使用l函数,就像在国际化(Internationalization)一章中描述的那样。

    <textbox constraint="/.+@.+\.[a-z]+/: ${c:l('err.email.required')}"/>

    Datebox的约束

    除了在上述章节中描述的约束(例如no future和正则表达式),datebox支持一个日期范围。例如,

    <datebox constraint="between 20071225 and 20071203"/>
    <datebox constraint="before 20071225"/>
    <datebox constraint="after 20071225"/>
    

    注意

    1. 日期的格式被约束为yyyMMdd,独立于区域。

    2. 约束内指定的日期为包含。例如,"before 20071225"包括December 25, 2007 及之前的每一天。

    3. 约束实际上表示org.zkoss.zul.SimpleDateConstraint类的一个实例。你可以使用getBeginDategetEndDate方法分别获取开始和结束日期。

      ((SimpleDateConstraint)datebox.getConstraint()).getBeginDate();
      					

    定制属性

    如果你想使用更复杂的约束,可以指定一个实现了org.zkoss.zul.Constraint接口的对象。

    <window title="Custom Constraint">
       <zscript><![CDATA[
    Constraint ctt = new Constraint() {
       public void validate(Component comp, Object value) throws WrongValueException {
          if (value =e= null || ((Integer)value).intValue() < 100)
             throw new WrongValueException(comp, "At least 100 must be specified");
       }
    }
       ]]></zscript>
       <intbox constraint="${ctt}"/>
    </window>
    

    你可以在一个Java类中实现你的约束,例如my.EmailValidator,那么

    <?taglib uri="/WEB-INF/tld/web/core" prefix="c"?>
    <textbox constraint="${c:new('my.EmailValidator')}"/>
    

    org.zkoss.zk.ui.WrongValueException

    在上面的例子中,我们使用了org.zkoss.zk.ui.WrongValueException来表述一个错误。

    就像描绘的那样,你需要指定两个参数,第一个为引起错误的组件,第二个为错误信息。你可以随时丢出这个异常,如下面,当onChange事件被接收时:

    <textbox>
       <attribute name="onChange">
          if (!self.value.equals("good")) {
             self.value = "try again";
             throw new WrongValueException(self, "Not a good answer!");
          }
       </attribute>
    </textbox>
    

    定制方式显示错误信息

    通过实现org.zkoss.zul.CustomConstraintConstraint接口,你可以提供定制的外观,以代替前面例子中的默认错误框。CustomConstraint 有一个showCustomError方法,当抛出异常或验证失败时,此方法会被调用。下面是一个例子:

    <window title="Custom Constraint" border="normal">
       <zscript><![CDATA[
    class MyConst implements Constraint, CustomConstraint {
       //Constraint//
       public void validate(Component comp, Object value) {
          if (value == null || ((Integer)value).intValue() < 100)
             throw new WrongValueException(comp, "At least 100 must be specified");
       }
       //CustomConstraint//
       public void showCustomError(Component comp, WrongValueException ex) {
          errmsg.setValue(ex != null ? ex.getMessage(): "");
       }
    }
    Constraint ctt = new MyConst();
       ]]></zscript>
       <hbox>
          Enter a number at least 100:
          <intbox constraint="${ctt}"/>
          <label id="errmsg"/>
       </hbox>
    </window>
    

    提高响应能力

    在客户端验证更多的约束可以提高能力。你得实现org.zkoss.zul.ClientConstraintConstraint接口来实现此功能。如果完成了客户端的所有验证,可以通

    isClientComplete方法返回true,那么将不会有回调服务(server callback)。

    你也可以使用纯JavaScript代码来定制一个错误信息的显示,需要提供一个叫作Validate_errorbox的函数。如下:

    <script type="text/javascript"><![CDATA[
       //Running at the browser
       window.Validate_errorbox = function (id, boxid, msg) {
          var html = '<div style="display:none;position:absolute" id="'
             +boxid+'">'+zk.encodeXML(msg, true)+'</div>';
          document.body.insertAdjacentHTML("afterbegin", html);      return $e(boxid);   }
    ]]></script>
    

    [注]: script指定了在浏览器端运行的脚本代码类型, 而script 在服务器端运行的脚本代码类型.

    [注]:如果也实现了CustomConstraint,那么由于在服务端完成了所有的验证,所以 ClientConstraint 会被忽略。换言之, 如果你想使用ClientConstraint提高响应能力,重写Validate_errorbox是定制错误信息显示的唯一方式。

    onChange事件

    当用户已经改变了输入控件的内容,输入控件会使用onChange事件来通知应用程序。

    注意,当onChange的事件监听器被调用时,其值已经被设定。因此,如果当你想为onChange的事件监听器注入一个非法值时已经晚了,除非你适当地还原其值。建议使用在定制约束(Custom Constraints)章节描述的约束。

    onChanging事件

    当用户正在改变了输入控件的内容时, 输入控件会使用onChanging事件来通知应用程序

    注意,当onChanging的事件监听器被调用时,其值并没有被设定。换言之,value属性原来的值。为了获取用户输入的内容,你得按如下方式访问事件的value属性。

    <grid>
       <rows>
          <row>The onChanging textbox: 
             <textbox onChanging="copy.value = event.value"/></row>
          <row>Instant copy:
             <textbox id="copy" readonly="true"/></row>
       </rows>
    </grid>
    

    由于用户还没有更改,所以onChanging的事件监听器非法值就太早了。建议使用在定制约束(Custom Constraints)章节描述的约束。

    日历

    calendar 展示了一个日历并允许用户从中选择一个日期。

    <hbox>
    <calendar id="cal" onChange="in.value = cal.value"/>
    
    <datebox id="in" onChange="cal.value = in.value"/> </hbox>

    value 属性和onChange事件

    就像输入控件,calendar提供了value属性来开发人员设置及获取选中的日期。此外如果有必要的话,开发人员还可以监听onChange事件以便立即加以处理。

    compact属性

    calendar 支持两种不同的布局,可以通过compact属性控制。

    <calendar compact="true"/>
    

    默认值为本地。

    进度条

    progress 为指示任务完成进度的棒形图。其value属性必须在0100的范围内取值。

       <progressmeter value="10"/>
    

    Slider

    Slider以滚动(scrolling) 方式来指定值。

    <slider id="slider" onScroll="Audio.setVolume(slider.curpos)"/>
    

    slider接收在0至100范围内的值。你可以使用maxpos 属性来改变允许的最大值 。

    计时器

    timer是一个不可见的组件,用于在指定的时刻或一段时间内将onTimer事件发送到服务器。你可以使用 startstop方法来控制 timer

    <window title="Timer demo" border="normal">
       <label id="now"/>
       <timer id="timer" delay="1000" repeats="true"
          onTimer="now.setValue(new Date().toString())"/>
       <separator bar="true"/>
       <button label="Stops timer" onClick="timer.stop()"/>
       <button label="Starts timer" onClick="timer.start()"/>
    </window>
    

    分页

    paging组件用于将一段很长的内容分成多个页面。例如, 假定有100个项目,每次显示20个项目,那么可以按如下方式使用 paging 组件。

    <paging totalSize="100" pageSize="20"/>
    

    然后,当用户点击一个链接时,onPaging事件会和org.zkoss.zul.event.PagingEvent的一个实例被送到paging组件。可以为paging组件添加一个监听器以决定100个项目的哪部分是可见的。

    <paging id="paging"/>
    <zscript>
       List result = new SearchEngine().find("ZK");
          //assume SearchEngine.find() will return a list of items.
       paging.setTotalSize(result.size());
       paging.addEventListener("onPaging", new EventListener() {
          public void onEvent(Event event) {
             int pgno = event.getPaginal().getActivePage();
             int ofs = pgno * event.getPaginal().getPageSize();
             new Viewer().redraw(result, ofs, ofs + event.getPaginal().getPageSize() - 1);
                //assume redraw(List result, int b, int e) will display
                //from the b-th item to the e-th item
          }
       });
    </zscript>
    

    List Boxes 和 Grids的Paging

    listboxgrid组件本身支持paging,所以不必像上面一样明确地为其指定paging 组件,除非你想指定不同的布局(visual layout)或者使用一个paging组件控制多个listboxgrid

    参考网格一节获取更多的细节。