内容


XUL - 快速开发跨平台易用用户接口的新途径

Comments

目前,大部分系统用户接口的开发都是和运行平台特性紧密相关的,这使得构建跨平台的系统非常费时并大大增加了开发成本和风险。当用户希望把系统迁移到其它平台比如 PDA 等手持设备时,这个潜在问题会显得更加突出,因为修改甚至重新构建系统往往需要巨大的额外花费。尽管一些解决方案比如 JAVA 的 UI 库已为广大开发人员使用,但在开发跨平台易用用户接口时,一种更精巧的轻量级方式显得愈加迫切。XUL (XML User-interface Language - 基于XML的用户接口语言)正是应这种需要而出现的一个能更有效方便地开发跨平台应用用户接口的新技术。

1. XUL 基本概念和其应用领域

XUL (XML User-interface Language - 基于 XML 的用户接口语言)是一种新的富客户端(Rich Client)技术,是 Mozilla 和 Firefox 的核心语言,是一种用来快速开发跨平台用户接口的新途径。其实不难发现很多新出现的语言都是基于 XML 的,比如 FIXML(Financial Information Exchange ML 金融信息交互描述语言),ECML(Electronic Commerce ML 电子商务描述语言)等。XUL 也不例外,它完全遵循 XML 国际标准,套用面向对象的说法就是 XUL 继承了 XML。任何能使用和解析 XML 的地方 XUL 都可以出现。

XUL 为什么具有是跨平台的特性?

这是因为 XUL 和 Mozilla&Firefox 浏览器的"血缘关系"。Mozilla&Firefox 的出现和 XUL 是密不可分的,Mozilla&Firefox 本身就是基于并且用 XUL 开发的。目前所有用 XUL 开发的界面程序都必须通过 Mozilla&Firefox 浏览器访问,而后者是跨平台甚至可以运行在手持设备 PDA上。这就注定了 XUL 跨平台的优越特性。

XUL 的易用性在于它非常简单易学,因为 XUL 所需要的技术仅仅是 XML,JavaScript 和 CSS。只需要有基本的 html 网页开发经验,要是开发过 JSP,ASP 乃至 Portlet,那就更能轻松掌握。当然要实现非常复杂的功能还是需要一段时间的积累。

运行基于 XUL 开发的应用程序可以通过以下途径访问:

(1) 将 XUL 应用程序运行在 Web 服务器上,以普通 HTTP URL 的形式访问。但这种方式会有很大的权限限制,因为这毕竟不是 XUL 作为富客户端的典型特性。

(2) 让用户在 Web 页面下载 XUL 应用程序 Package,在客户端本地安装注册,通过 XUL 特有的 Chrome URL 方式运行。这种则是 XUL 作为富客户端的典型运行方式。

需要注意的是 XUL 应用程序只能通过 Mozilla&Firefox 访问,也就说 Mozilla&Firefox 是所有 XUL 应用程序的运行平台。

在全球化方面,由 XUL 构建的用户接口能通过其 Locale 和在线安装更新机制,根据不同国家的 locale 信息改变显示语言,很好地支持了全球化。

用 XUL 完成用户接口开发后,需要把它和其他系统整合。XUL 能通过 Web Service 或在线安装等方法,很好地和其它系统集成(例如目前流行的 J2EE 系统)。我们甚至可以把简单的 XUL 用户接口和普通 Html 和 JSP 一样使用(尽管会有权限限制)。

作为用户接口语言,XUL 提供了流行的图形元素,这些通用的图形组件可以满足不同开发者的需求。这些组件包括输入控制组件,工具栏,菜单,表格(普通和树形),快捷键等。XUL 界面元素的特点是朴实易用。

下面就让我们逐步解开 XUL 的神秘面纱吧!

2. XUL 应用程序样例

2.1. 运行 XUL 应用程序

上文已经提到过运行 XUL 应用程序主要有两种途径:

1) 把 XUL 应用程序运行在 Web 服务器上,以普通 HTTP URL 的形式访问,这种访问会有很大的权限限制,例如:

2) 通过 XUL 的 Chrome URL(本文3.2有详细介绍),在客户端本地运行访问。具体又有两种方式:

以浏览器方式访问(请注意 URL 的特点):

以应用程序形式访问(从开始菜单-运行窗口访问,也可以创建桌面快捷方式):

2.2. XUL 源代码

下面这个例子展示了如何开发一个简单的 XUL 用户接口:

界面效果图:

对应的源码如下

从上面的代码可以发现 XUL 的编写非常简单,声明 DTD 实体,定义脚本函数,然后就是定义主窗口元素。

<!DOCTYPE window [
	<!ENTITY Sample "Add DTD Entity Declearation">
]>

是对XML DTD实体的声明。

<window id="findfile-window" title="Find Files" orient="horizontal"
	xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
</window>

定义了主窗口<window>元素,所有其他界面元素的定义都包含在这里面。

<script>  <![CDATA[     
    		function sample(){ 
//Add your functions 
	 	}   
 ]]>   </script>

定义了JavaScript脚本函数,负责对窗口事件的响应并执行相应操作。

增加其他元素也非常简单,在主窗口上顺序添加即可,详细的元素列表和属性请参考XUL元素列表

下面是一些XUL界面效果图以供参考:

3. XUL 关键功能和机制

3.1. XUL 应用程序 Package

XUL 应用程序架构和 Eclipse 的插件(Plugin)结构很相似,可以动态加载组件。XUL 中每个功能组件相对独立,以 Package 的形式存在(这里的 package 一般是 JAR 包以便下载安装,也可以是普通的文件目录)。在 Mozilla 或 Firefox 中,主要的功能组件都存放在"Mozilla&Firefox 安装目录/chrome"下。这个"chorme"目录下有一个"chrome.rdf"和"installed-chrome.txt" 文件,"chrome.rdf"文件记录了所有安装组件(Package)的信息(包括存放路径),"installed-chrome.txt"记录了所有已安装的组件列表和它们的访问类型。因为 XUL 安装程序会将路径信息注册到该"chrome.rdf"文件中,所以当增加新的组件 Package 时,你可以把它放在任何目录下,甚至包括可以加载(mount)到本地文件系统的远程站点。

一个典型的XUL组件Package的目录结构如下:

Content 目录

包含一些以".xul"为后缀的文件,它们定义了 XUL 界面窗口元素。另外还有定义了处理界面事件响应以及负责和其它系统交互的 JavaScript 脚本文件,都是以".js"为文件后缀。需要注意的是 Content 目录下可以有多个 XUL 文件,但定义主窗口的 XUL 文件名必须和组件Package 名相同。

Skin 目录

负责界面外观的显示细节,比如色彩,图片等。主要包含了 CSS 文件和图片。这样就可以独立地修改界面外观而不影响其功能。

Locales 目录

存放和全球化相关的翻译信息。每个 locale 会有一个独立的文件目录,例如"en_US"(美国), "fr_FR"(法国)等。这些子目录下则存放了和各自 locale 相关的 DTD 和 property 文件。DTD 文件包含了 XML 实体声明,这些实体的值用其对应国家的语言表示,被 Content 目录下的 XUL 文件引用并显示在界面上。例如多国语言的快捷键和菜单的内容等。Property 文件和 DTD 类似,不同的是它被脚本所使用。当新增加一个语言时,只需添加新的 locale 子目录即可,无需任何额外的修改。

另外需要注意的是这三个目录下都有一个"contents.rdf"文件,这是一个必须的配置文件,它定义了每个子目录例如 Content 子目录所属的应用程序 Package,相对路径以及作者和版本号等信息。Mozilla&Firefox 就是用这些 contents.rdf 文件中的信息来安装、注册并运行 XUL 应用程序。

3.2. Chrome URL

Chrome URL 是 XUL 或者说 Mozilla&Firefox 的访问和注册机制,所有在本地 Mozilla&Firefox 注册安装的应用程序,都是用这种方式访问的。以 Chrome URL 方式运行的应用程序能获得广泛的访问权限。另一个优点是用 Chrome URL 访问时,Mozilla 会自动去上文提到的"chrome.rdf", "cintents.rdf"文件中寻找相关信息并将数据正确地返回。Chrome URL 本身是和物理路径无关的,因此可以把新应用程序安装在任何目录下。下面是一个具体的 Chrome URL 例子:

Chrome URL 语法是:chrome://<package name>/<part>/<file name>

这里的<package name>是应用程序package名称,<part>是指要访问的子目录,例如上文提到过的"Content","Skin","Locale",<file name>是对应子目录下的文件名。可以看到 Chrome URL 不包含任何物理路径相关的信息,因为在安装新的应用程序时,所有信息(包括安装路径)都注册到"chrome.rdf"和"installed-chrome.txt"文件中了。

以下是访问 Mozilla 自带组件的 Chrome URL 例子:
chrome://messenger/content/messenger.xul
chrome://navigator/skin/navigator.css
chrome://messenger/locale/messenger.dtd

另一种格式是例如:
chrome://navigator/content/
chrome://navigator/Skin/
chrome://navigator/Locale

当应用程序 Package 下的每个"Content","Skin"和"Locale"子目录中都有一个和 Package名称相同的文件时(例如"navigator.xul"," navigator.css"," navigator.dtd"),就可以用这种方式访问,这样用户就只需知道应用程序 Package 的名字即可。

3.3. XPCOM (Cross-platform Component Object Model)

到目前为止,我们已经知道 XUL 能够构建各种复杂的用户接口,并用 JavaScript 负责窗口的事件响应。但如果我们想实现更复杂的功能,比如要求 XUL 应用程序和独立运行的 Mail Server 交互,负责发送和收取信件,JavaScript 便显得捉襟见肘了,XPCOM 便是扩展 XUL 功能的途径。

XPCOM 作为一个对象模型,也具备了基本的面向对象理念。其中最重要的基本单位便是接口(Interface)和组件(Component)概念。每个接口(Interface)声明了它所具备的功能和属性,组件(Component)可以继承多个接口,并实现这些接口中的所有功能。这样做的目的是在公用接口中清楚地声明一套 API,由组件(Component)根据需要加以组合和实现,组件实现的改变不会对接口有任何地影响。

XUL本身已经实现了很多组件(Component),XUL 组件列表上有详细的说明,我们可以在Mozilla&Firefox 平台上直接使用这些功能。下面是一个使用 XPCOM 的例子:

例如需要对本地文件进行操作,

  • 首先获得已经实现了文件操作功能的组件:
    var localFile  = 
    Components.classes["@mozilla.org/file/local;1"].
    createInstance(Components.interfaces.nsILocalFile);

    请注意,这里的 Components本身也是一个组件,它负责其它组件的生成,查询和管理。"@mozilla.org/file/local;1" 其实是 Mozilla 根据组件各自功能进行分类的规则,从某种意义上也可以称为命名空间。Components.interfaces.nsILocalFile 是我们所需要的具备文件操作功能的接口,最后我们通过 createInstangce() 函数得到了组件引用 localFile。
  • 接下来可以用这个组件实现我们所需要的功能:
       function deletFile(filePath){
    var localFile  = 
    Components.classes["@mozilla.org/file/local;1"].
    createInstance(Components.interfaces.nsILocalFile);
       //定位文件或目录路径,filePath例如"/mozilla/ToRemove" 
    localFile. initWithPath(filePath);
    //删除该目录所有 (true) 内容
       localFile.remove(true);

此外我们还可以开发并添加新的功能组件(Component),由于篇幅限制,先不作介绍,读者可以参见 XPCOM

3.4. XPI (Cross-platform Installation)

XPI (跨平台安装程序) 是一种非常灵活方便的下载安装机制,只需将开发好的应用程序 Package和 XPI 特定安装脚本(install.js)这两个文件压缩到一个以".xpi"为后缀的文档中,这样一个简单的 XPI 安装程序就完成了(注意:最好使用 Winzip 进行压缩,其他压缩工具如 Winrar可能导致下载时无法读取 XPI 包)。

下面是具体的步骤(假设要安装的应用程序名称是"top"):

  • 完成 install.js 安装脚本:

    //初始化,三个参数分别是应用程序名称,应用程序索引,版本号
    initInstall("top", "top", "0.1");
    //找到 Mozilla 安装目录下的"chrome"目录。
    var folder = getFolder("chrome");
    //设置应用程序安装目录即 Mozilla 安装目录下的 Chrome
    setPackageFolder(folder);
    //指定将安装的应用程序,如果应用程序是目录结构则调用 addDirectory() 方法,
    //如果已打成 JAR 包的则调用 addFile() 方法,
    addDirectory("top");
    //注册应用程序 top 的 Chrome URL,第一个参数是 Chrome 注册类别(Content, Skin ,Locale)
    //第二个参数指明 contents.rdf 文件所在的目录,contents.rdf 文件包含了 Chrome 注册信息
    //第三个参数是安装完毕后 contents.rdf 文件所在目录
    registerChrome(Install.CONTENT|Install.DELAYED_CHROME, 
    getFolder(folder, "content"));
    //如果还有 Skin 和 Locale 目录,还要注册 Chrome Skin 和 Locale
    // registerChrome(Install.SKIN | Install.DELAYED_CHROME, 
    //getFolder(folder, "skin"));
    // registerChrome(Install.LOCALE | Install.DELAYED_CHROME, 
    //getFolder(folder, "locale"));
    //开始安装注册应用程序,如果发现以上步骤有错误还可以取消并退出安装程序。
    //getLastError() 方法返回最近出现的错误信息。
    if (0 == getLastError()){
    	performInstall();
    }else{
    	  cancelInstall();

    }
  • 更新提供下载服务的 Web 页面 首先在对应的 Web 页面上增加一个连接:

    <a href="Install new XUL application!" 
    onclick="install()"> Install new XUL application!</a>

    然后为该页面增加两个 Javascript 方法,内容如下:

    function install( ){   
    //设置参数-应用程序名称(top)和安装程序名称(top.xpi)
       var xpi={'top':'top.xpi'};
       //启动安装程序,并将作为参数传入,callback 是安装结束回调的方法名
       InstallTrigger.install(xpi, callBack);
    }
    function callBack (name, result)
    {//安装完成后 XPI 回调该方法。
       if (result == 0)
    		alert("Installation complete, please restart your browser.");
       else
    		alert("Installation failed, Error - "+result);
    }

XPI 的在线安装注册可以概括为:

1) 编写 install.js 安装脚本,将应用程序和 install.js 压缩成以 .xpi 为后缀的包。

2) 更新提供下载服务的 Web 页面,包括增加新的下载链接和脚本函数(如上文所述)。

3) 用户在线点击下载,XPI 安装程序启动,解压 .xpi 安装包,执行 install.js

4) 新应用程序安装完毕,所有信息注册到本地 Mozilla&Firefox Chrome 中。

5) 用户重新启动 Mozilla&Firefox, 在本地运行新安装的程序。

安装过程如下:

点击安装链接
点击安装链接
点击安装链接
选择安装
选择安装
选择安装
安装成功
安装成功
安装成功

以上只是一个非常简单的安装样例,XPI 提供了更多的方法来管理并保证整个安装过程的顺利进行,比如记录安装日志,读取操作系统信息,文件的解压缩等等。更详尽的功能请参考 XPI。

4. XUL 用户接口的全球化

作为用户接口的设计语言,支持全球化是一个非常重要的性能。而 XUL 本身的特点也很好的支持了全球化。XUL 中最方便的支持全球化的途径便是利用 XML 实体引用机制(DTD Entities)。

我们都知道 XML 的实体机制的最大特点便是,"一处修改,多处呈现"。也就是说只需要在定义实体的地方修改其内容而不影响使用实体处的代码。这就很好的支持了全球化,只要我们将接口上显示的部分用 XML 实体抽取,统一地翻译成多种语言。不同版本的应用程序引用对应国家的实体,这就实现了接口的多语言化。

下面是一个典型的支持多语言的 XUL 应用:

英语版本的实体声明:

<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="./CalculateVolumetricWeight.css" type="text/css"?>
<!DOCTYPE window [
<!ENTITY dp1 "Sometimes, large items with a light overall weight can be 
charged according to the space they take up on aircraft.">
<!ENTITY dp2 "In these cases, Volumetric Weight,or dimensional (Dim) weight, 
is used to calculate the shipment cost.">
<!ENTITY dp3 "It is recommended that you calculate the Volumetric Weight for
every shipment that you send, and then compare">
<!ENTITY dp4 "this to its actual weight. The greater weight of the two is 
used to work out the price that we charge you.">
<!ENTITY dp5 "International Volumetric Weights are calculated using the formula below:">
<!ENTITY dp6cm "Length * Width * Height in centimeters / 6000 = Volumetric Weight"> 
<!ENTITY dp6inch "Length * Width * Height in inches / 166 = Volumetric Weight"> 
<!ENTITY dp7 "Alternatively, please feel free to use our quick calculator below.">
<!ENTITY title "Volumetric Weight Calculator">
<!ENTITY UnitOfMeasure "Unit of Measure">
<!ENTITY Cmskgs "Cms/kgs">
<!ENTITY InchesPounds "Inches/Pounds">
<!ENTITY Length "Length">
<!ENTITY Width "Width">
<!ENTITY Height "Height">
<!ENTITY VolumetricWeight "Volumetric Weight">
<!ENTITY Calculate "Calculate">
<!ENTITY Reset "Reset">
<!ENTITY errorLength "Invalid length format. Only positive integers are allowed.
Please validate your input and try again.">
<!ENTITY errorWidth "Invalid width format. Only positive integers are allowed.
Please validate your input and try again.">
<!ENTITY errorHeight "Invalid height format. Only positive integers are allowed.
Please validate your input and try again.">
<!ENTITY cm "cm">
<!ENTITY kg "kg">
<!ENTITY Inch "Inch">
<!ENTITY Pound "Pound">
]>

中文版本的实体声明:

<?xml version="1.0" encoding="GBK" ?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="./CalculateVolumetricWeight.css" type="text/css"?>
<!DOCTYPE window [
<!ENTITY dp1 "总重量较轻的大件物品可以根据其在飞机上占据的空间付费,在这个情况下,
体积重量将作为货物运费的参考依据。">
<!ENTITY dp2 "建议您计算每件发送货物的体积重量,并和实际数据比较。较大的体积重量
将作为您付费的依据。">
<!ENTITY dp5 "国际标体积准重量计算公式如下:">
<!ENTITY dp6cm "长度 * 宽度 * 高度 (单位厘米) / 6000 = 重量"> 
<!ENTITY dp6inch "长度 * 宽度 * 高度 (单位英寸)/ 166 = 重量"> 
<!ENTITY dp7 "或者您可以使用下面的计算器实现快速计算。">
<!ENTITY title "体积重量计算器">
<!ENTITY UnitOfMeasure "度量单位">
<!ENTITY Cmskgs "厘米/千克">
<!ENTITY InchesPounds "英寸/磅">
<!ENTITY Length "长">
<!ENTITY Width "宽">
<!ENTITY Height "高">
<!ENTITY VolumetricWeight "体积重量">
<!ENTITY Calculate "计算">
<!ENTITY Reset "重置">
<!ENTITY errorLength "长度错误! 只允许正数, 请检查并重新输入!">
<!ENTITY errorWidth "宽度错误! 只允许正数, 请检查并重新输入!">
<!ENTITY errorHeight "高度错误! 只允许正数, 请检查并重新输入!">
<!ENTITY cm "厘米">
<!ENTITY kg "千克">
<!ENTITY Inch "英寸">
<!ENTITY Pound "磅">
]>

两中语言版本中实体引用代码是相同的,例如:

<row flex="1" id="row2">
<label value="&Length;" />
<textbox id="length" maxlength="6" />
<label id="unit1" value="&cm;" />
</row>
<row flex="1" id="row3">
<label value="&Width;" />
<textbox id="width" maxlength="6" />
<label id="unit2" value="&cm;" />
</row>

最后显示的效果如下:

英语版本效果图:

中文版本效果图:

此外借助上文提到的 XPI 安装程序,我们可以实现语言包的动态下载,这使得增加新语言包非常容易,以下是一个简要的步骤:

1) 将新语言包的 XPI 安装程序添加到 Web 页面上。

2) 用户选择下载新语言包,XPI 安装程序激活,并开始下载安装和注册。

3) 语言包安装完毕,信息注册到 Mozilla 的 Chrome.rdf 中。

4) 用户重新启动 XUL 应用程序。

请注意,如果只是更新语言包,我们可以使用 XPI 的下面两个方法:

Install.RegisterChrome(LOCALE,srcDir, xpiPath) ;     
InstallTrigger.installChrome(InstallTrigger.LOCALE,URL,Displayname);

具体的安装过程和 3.4 部分类似,就不再累述。

5. 用 Web Service 实现 XUL 应用程序和服务器的集成

XUL 的主要任务是构建用户接口,负责界面的显示和事件响应。为了增加其扩展性,XUL 提供了一系列和其他系统的集成方法(包括 3.4 介绍的在线安装),这里介绍一种比较典型而且比较流行的途径 - Web Service。

XUL Web Service 调用的核心是 SOAP(Simple Object Access Protocol)。SOAP 是一种基于 XML的 Web Service 通讯协议。XUL 本身实现了 Web Service 相关的一系列组件(Components),具体 API 请参考 XUL Web Services。Mozilla1.0 以后的版本都包含了基于 SOAP 的 Web Service 操作组件,可以在 JavaScript 中直接使用(但必须得到安全访问权限)。

请看下面的简单样例:

将一个简单的学生学号查询方法发布成 Web Service(RPC/Encode方式),函数如下:

public class Finder {
public String findStudentName(String num) {
int[] studentNums = { 1, 2, 3, 4, 5, 6 };
String[] studentNames 
= { "Jack", "Tony", "Evan", "Mike", "John", "Betty" };
int stuNum = Integer.parseInt(num);
for (int i = 0; i < studentNums.length; i++) {
	if (studentNums[i] == stuNum) {
	System.out.println("Found the student: " + studentNames[i]);
	return studentNames[i];
	}
}
return "No such student!";
}
}

相关 WSDL 文件请参见附件列表:

执行脚本如下(具体请参见附件列表):

<script>  <![CDATA[             
function find(){
//得到用户输入的检索信息
var num = document.getElementById("num").value;	
//参数列表,Web Service 函数的 参数名/参数值
var p = new Array();
p[0] = new SOAPParameter(num,"num");	
//得到脚本的跨平台访问安全权限
netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");  

//新的 SOAP 调用组件
var soapCall = new SOAPCall();
//Web Service 地址
soapCall.transportURI 
= "http://localhost:9080/xul_webservice/services/Finder";

//调用 Web Service
soapCall.encode(0, "findStudentName", 
"http://string.xul", 0, null, p.length, p);
//跟踪返回结果
var returnObject = soapCall.invoke(); 	
response = returnObject.getParameters(false, {});
alert("Return value: " + response[0].value);	 
}
]]>   </script>

注:

  • soapCall.transportURI = "http://localhost:9080/xul_webservice/services/Finder"; 在 wsdl 中为 <wsdlsoap:address location="http://localhost:9080/xul_webservice/services/Finder"/>
  • encode 函数的参数分别为调用的 WebService 函数名,命名空间 URI,参数个数,参数列表。

在 wsdl 中为:

<wsdl:definitions targetNamespace=http://string.xul
<wsdl:operation name="findStudentName" parameterOrder="num">
  • 本例 Web Service 构建工具是 IBM WSAD5.1,仅供演示测试使用。
  • 为了最简单地说明 XUL 中 Web Service 调用,这里使用了本地服务器模式。如果要采用跨平台模式测试,请用"about:config"访问 Mozilla 或 Firefox,找到 "signed.applets.codebase_principal_support"选项,确保其值为 true,这样就关闭了Mozilla & Firefox 对跨平台权限的限制。

成功调用界面如下:

6. 总结

XUL是一个充满活力的崭新技术,对XUL的学习和研究伴随着 Mozilla & Firefox 的诞生和发展。本文只是对 XUL 的一个初步介绍,希望能起到穿针引线的作用,让更多的朋友加入到这一领域,发掘 XUL 更深入,更强大的功能。

7. 参考资料


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML
ArticleID=99078
ArticleTitle=XUL - 快速开发跨平台易用用户接口的新途径
publish-date=11102005