创建一种声明性XMLUI语言-用Java语言构建一个UI和配套框架

用编程代码编写 GUI 常常导致混乱的设计,这反过来导致业务逻辑和 UI 代码之间的混淆。本文探讨如何创建带有配套 Java™ 框架的声明性 XML UI 标记集合,以便在运行时解析、构造并最终将已声明的GUI 组件绑定到业务逻辑。

简介

GUI 开发可能是令人畏惧的任务。GUI 框架并不是总是拥有良好的文档,需要的代码量可能迅速增长,拖慢开发工作流。特别是支持这些 GUI 框架的拖放工具和 IDE 通常诱使 GUI 软件开发人员创建难以管理和阅读的代码。这可能会进一步混淆业务逻辑和 GUI 描述代码之间的界限,从而使软件维护更加困难。

常用缩略词

API:应用程序编程接口

DOM:文档对象模型

GUI:图形用户界面

HTML:超文本标记语言

IDE:集成开发环境

JAR:Java 压缩文档

UI:用户界面

URI:统一资源标识符

XML:可扩展标记语言

XSD:XML 模式信息集模型

W3C:万维网联盟

这就是声明性 UI 语言之所以方便的原因。UI 语言描述 “是什么”,而不是 “该如何”。例如, HTML 描述显示的内容,而不是描述用于呈现内容的呈现函数。声明性语言并不指定 “该如何”,从而省略了控制流。尽管这种省略听起来好像一种限制,但它实际上是一种优点,因为控制流的副作用 — 如修改全局状态(比如变量)或调用其它函数或方法 — 被消除了。选择声明性语言还有利于将 UI 代码和应用程序代码分隔开来。这种分离将来还能提供一些好处,比如明确区分项目和团队角色,这甚至有可能降低业务逻辑和多个视图或视图技术之间的集成成本。

目前正在使用的声明性 XML UI 的例子不在少数。使用 GNOME 桌面环境的Linux® 和 UNIX® 操作系统有 Glade。Microsoft® Windows® 用户拥有 Extensible Application Markup Language (XAML),该语言支持丰富的功能,包括在 XML 中插入代码。Adobe® Flex® Framework 的MXML 格式为 Adobe Shockwave (SWF) 播放器描述 GUI 并包含代码插入。参阅 参考资料 中的链接了解更多信息。

Java 技术中的基本声明性 UI 框架的必要组件可能包括:

验证:使用 XML Schema

一个 DOM:处理具体事宜的自定义 DOM,比如同步 GUI 组件状态和 XML 节点状态

持久性: GUI 的编组(marshalling)和解组(unmarshalling)

图像数据:存储为 Base64 数据

Swing 组件:GUI 开发常用的Swing 组件的表示

下面可以创建声明性 XML 了,创建过程中要谨记上述必要组件。

声明性 XML

第一个 XML 格式示例(见 清单 1)展示了一个简单的窗口、一个面板和一个按钮。清单 1 包含基本的必要属性,比如坐标、大小和引用单独内存组件的惟一标识符。

清单 1. 声明性 XML 概念

                

XML 模式

这个声明性 XML UI 将把 XML 元素映射到 Java Swing 框架,由于 Swing 可用于所有现有 Java 运行时环境,该框架提供了极大的可移植性。许多 Swing 组件将在 XML 格式中拥有相应的XML 元素。

这个框架使用一个 XML 模式。XML 模式允许在一个模式实例中使用指定顺序、基数(cardinality)和数据类型。这一点很重要,该框架将要求一个具有指定类型和特定顺序的XML 元素集。清单 2 展示了一个 XML 模式实例中的初始元素和属性的层次结构。

清单 2. 声明性 XML UI 模式:初始元素

                                                                                                                                                                                                                                   

下面详细检查一下这个模式。首先,根据 XML Recommendation 的建议,XML 声明必须出现在最前面 — 甚至在空格和注释前面。其次,schema 元素包含其他元素:

elementFormDefault=”qualified” 表明所有元素必须有一个名称空间 — 可以是前缀,也可以是默认名称空间。

targetNamespace=”http://xml.bcit.ca/PurnamaProject/2003/xui” 规定了目标名称空间 URI。

模式实例使用 W3C XML Schema Recommendation 和其中的所有元素(xmlns:xs=”http://www.w3.org/2001/XMLSchema”)。

xmlns:xui=”http://xml.bcit.ca/PurnamaProject/2003/xui” 识别另一个名称空间和它的对应前缀。

在 XSD 中使用名称空间很重要,这样就能消除名称空间冲突。当来自两个或多个 XML 格式的两个或多个元素的名称相同时,名称空间冲突 就会出现。这种冲突使对它的对应标记集感兴趣的应用程序感到困惑。通过使用名称空间和对应的名称空间前缀,您可以彻底避免这个问题。

再次,根级别(root-level)数据类型元素 XUI 表明:

它允许一个由 0 个到 128 个 Window 元素组成的序列,序列末尾是一个 Resource 元素。稍后您将发现,这两个元素将在模式实例中被引用。

它有一个 id 元素,该元素是必需的且其类型必须为 anyURI。

XUI 元素可能包含许多 Window 元素,它也可能没有任何 Window 元素(如果 minOccurs 元素的值为 0)。至于 Resource 元素:

它有一个空的内容模型,因为它的xs:sequence 元素为空。

它有 3 个属性,它们都是必需的。

最后的type 属性创建了一个从 XSD 的已定义类型(token)派生而来的简单类型,其中 restriction 片段是 enumeration,允许列举 java 和 groovy 的字面文本值。

Resource 元素的目的是向这个 Java 框架提供一个资源(本例中是一个 JAR)的URI,该资源包含运行时可以加载并绑定的已编译 Java 类。这个资源依赖于将被调用的一个特殊类(class 属性的值),该类主要用于提供一个已公开的类,以便响应从 GUI 生成的所有事件。

Window 元素:

包含一个 GridLayout 序列,该序列可以包括 GridLayout,BasicDialog、OpenFileDialog、SaveFileDialog、CustomDialog、Panel、SplitPane 和 TabbedPane 元素,以及 0 个或一个 MenuBar。

拥有 7 个属性 — 都是必需的— 它们使用 XML Schema Recommendation 中的各种已定义数据类型(注意 xs 前缀)。

Window 可以包含多个不同的顶级和中级容器。Window 元素引用了一个 GridLayout 元素。GridLayout 指定了由一个单元网格组成的维度,用于容纳组件。GridLayout 提供的布局特性类似于 Java 环境中的java.awt.GridBagLayout,但没有后者复杂。

如果不深入检查,这个 XML 模式的描述性似乎已经足够了。清单 3 展示了另外几个元素。

清单 3. 声明性 XML UI 模式:更多元素

...                                                                                                                                                                                                                                                  ...

注意,清单中没有存储任何易变的状态信息 — 只保护可能有助于 GUI 组件重建的状态信息。其中一个例子是 CustomDialog 元素的状态信息:

对话框中允许的Panel 元素的数量

对话框是否是模式对话框(模式对话框直到用户关闭对话框时才捕捉焦点)

桌面中的坐标(x 和 y,以像素为单位)

大小(宽度和高度,以像素为单位)

窗口的可见性

Panel 是一个中级容器,可以包含相当多的原子组件。再看一下 清单 3,Panel 拥有一个 GridLayout,可以选择在 Panel 内不放置原子组件,也可以根据需要放置任意数量的原子组件。Panel 本身具有 x 和 y 坐标。Panel 使用 x 和 y 坐标引用父容器的GridLayout 中的定位,而不是引用桌面中的像素(就像 CustomDialog 一样)。就像俄罗斯玩偶一样,这种嵌套构造法非常类似于 Swing 的布局规则。了解上述基本知识后,现在可以解决软件实现问题了。

支持的Java 框架

我们首先简要介绍建议的Java 框架。清单 4 展示了程序员创建应用程序时必须遵循的步骤:

清单 4. Java API 调用概念

try {   // Gain access to a XUI builder through factory   // In this framework the term XUI is going to represent the custom DOM   XUIBuilder builder = XUIBuilderFactory.getInstance().getXUIBuilder(); // (1)   // Validate and parse (unmarshal) the XML document   builder.parse("browser.xml"); // (2)   // Build a custom DOM   XUI xui = builder.getXUIDocument(); // (3)   // Create 1:1 GUI component mapping to custom DOM   xui.visualize(); // (4) (5)   // Create bindings to data model (i.e. JAR file from Resource element)   xui.bind(); // (6)   // Get root node from the XUI document   XUINode root = xui.getRoot();   // Save a copy of the DOM to file (marshal)   xui.marshalXUI("browser-marshalled.xml");} catch (XUIParseException xpe) {   xpe.printStackTrace();} catch (XUIBindingException xbe) {   xbe.printStackTrace();} catch (IOException ioe) {   ioe.printStackTrace();}

清单 4 清晰地定义了功能的分离,允许进一步优化框架的组件。图 1 试图可视化这个工作流。图 1 中的每个圈起来的数字对应 清单 4 中每个加注释的数字,但代码展示了两个额外步骤(检索对 XUI 根节点的引用和将 DOM 编组到文件)。这些步骤是:

文档对象模型(Document Object Model)是什么?

文档对象模型(简称 DOM)是 XML 元素到内存对象(由 API 提供)的映射,这种映射允许程序员将 XML 数据读入内存对象(称为解组),操作数据,然后将数据写回到 XML(称为编组)。最著名的常用 DOM 是 W3C DOM。

图 1 展示了以下步骤:

从 BuilderFactory 检索一个 Builder。

在允许检索一个 XUI 文档之前,Builder 首先确保 XML 文档已经被验证和解析。如果解析或验证失败,将出现一个 XUIParseException,框架将停止文档加载。

Builder 创建 DOM,其中的对象反映读入的XML 元素。

Realizer 对象(在内部由 XUI 对象调用)被实例化并准备执行下一步。

Realizing 是框架根据预先创建的XML 节点层级(框架引擎的真正核心)创建 GUI 组件层级的地方。

使用 Java 环境中的power of reflection,模型逻辑(应用程序中驱动 UI 的部分)被绑定到刚才实现的GUI 组件。

图 1. XUI API 用于构建 GUI 的框架流和详细步骤视图

这个共包含 6 个步骤的调用流易于使用,但其中包含大量信息和对象实例化,值得仔细研究。这个框架的核心位于步骤 5 和步骤 6。

GUI 组件 & XML 节点构成

在 图 1 中,步骤 5 创建了一个组件模型,支持将这个 XML 节点(现在是内存对象)和一个 GUI 组件组成一对。这种配对需要非常严格地同步以下事件:

对于框架读入的每个 XUINode(表示任意 XML 元素的内存对象),必须创建一个 XUIComponent 来包围 XUINode。

对于在内存中创建的每个 XUIComponent,必须创建一个 GUI 对等物,比如 javax.swing.JFrame。

每当一个 XUIComponent 实例 — 或它的一个子类型(比如 XUIButton)— 被修改时(比如修改大小),XUIComponent 必须确保 XUINode 和 GUI 对等物同时、同等地更新。

通过满足上述要求,这个框架允许程序员将多个 XML 文档读入内存(解组),修改 DOM,然后将更改保存回一个 XML 文档(编组)。程序员甚至可以以编程方式创建几个新的DOM 并对它们进行编组。

DOM 节点编组

这个框架提供了一个 toString 方法(见 清单 5),以便 XUINode 将自身编组为 XML。根节点可以包含多个子节点,子节点可以包含自己的子节点,依次类推。通过调用根级别节点的toString 方法,这个框架可以轻松编组整个 XML 文档。名称空间被添加进来,以便每个元素知道自己在层级中的级别(通过 level 变量)。这样,当 toString 方法被调用时,它可以实现缩进,以使这些文档更易于阅读。

清单 5. XUINode toString 方法实现

@Overridepublic String toString() {   StringBuffer sb = new StringBuffer();   String namespacePrefix = "";   // insert indenting ... 2 spaces for now.   if(isRoot) {     sb.append(XMLPI + "/n");     sb.append(API_COMMENT + "/n");   } else {     sb.append("/n");     for(int s = 0; s < level; s++) {       sb.append(" ");     }   }   sb.append(" 0) {     int length = attributes.getLength();     for(int i = 0; i ");   sb.append(cdata);   int size = childNodes.size();   for(int i = 0; i  0) {     sb.append("/n");     for(int s = 0; s  0) {     sb.append("");   } else {     sb.append("");   }   return sb.toString();}

添加到一个容器组件

另一个值得探讨的部分是容器类型 XUIWindow,它是 XUIComponent 的间接子类型。XUIWindow 实现表示一个 javax.swing.JFrame. 组件,因此它必须允许将子元素添加到布局中。清单 6 展示了它的实现。首先必须确保只有某些类型的子元素可以添加到 XUIWindow 中。这样,XUIComponent 的DOM 节点表示(XUINode)将被检索以便访问该元素的属性。注意,这要求所有 XUIComponent 的构造器初始化这些值。

下面进一步检查以确保以下两点:该组件是一个中级容器(比如 XUIPanel);这个中级容器能够包含在 XUIWindow 的行列网格中。最后,将组件添加到 XUIWindow 要确保以下两点:组件已启用并在布局网格中设置了正确的位置;XUIWindow 的XUINode(win 变量)获得了一个引用 —— 引用新的子组件的XUINode,即 addChildNode() 调用。

清单 6. XUIWindow addComponent 方法实现

public void addComponent(XUIComponent component) throws XUITypeFormatException {   if(component instanceof XUIBasicDialog     || component instanceof XUIOpenFileDialog     || component instanceof XUICustomDialog     || component instanceof XUIMenuBar     || component instanceof XUIPanel     || component instanceof XUISplitPanel     || component instanceof XUITabbedPanel     || component instanceof XUISaveFileDialog) {     // get the node     XUINode node = component.getNodeRepresentation();     if(!(component instanceof XUIMenuBar)) {       int x = Integer.parseInt(node.getAttributeValue("x"));       int y = Integer.parseInt(node.getAttributeValue("y"));       int width = Integer.parseInt(node.getAttributeValue("width"));       int height = Integer.parseInt(node.getAttributeValue("height"));       // can't add dialogs so need to check for type here.       if(component instanceof XUIBasicDialog         || component instanceof XUIOpenFileDialog         || component instanceof XUICustomDialog         || component instanceof XUISaveFileDialog) ; // nothing       else {         // check to make sure it fits within the grid.         Dimension localGrid = this.getGrid();         if(width > localGrid.getWidth() || height >           localGrid.getHeight()) {           throw new XUITypeFormatException(node.getName()             + " (id: " + node.getAttributeID()             + ") must be within this window's grid width and"             + "height (w: " + localGrid.getWidth()             + " + h: " + localGrid.getHeight() + ")");         }         Rectangle rect = new Rectangle(y, x, width, height);         component.getPeer().setEnabled(true);         frame.getContentPane().add(component.getPeer(), rect);         // for mapping components to the regions they occupy         childComponentMappings.put(component, rect);       }       component.setComponentLocation(x, y);     } else {       // do specifics for a menubar       frame.setJMenuBar((JMenuBar)component.getPeer());     }     frame.invalidate();     frame.validate();     // add the component's node     int level = win.getLevel();     node.setLevel(++level);     if(win.getParent() == null)       win.addChildNode(node);   } else {     StringBuffer sb = new StringBuffer();     sb.append("Type not supported in XUIWindow. ");     sb.appen("The following types are supported:/n");     for(int i = 0; i < supportedComponents.size(); i++) {       String s = (String)supportedComponents.get(i);       sb.append("- " + s + "/n");     }     throw new XUITypeFormatException(sb.toString());   }}

绑定

最后一个需要研究的代码部分是处理运行时绑定。调用 XUI 对象的bind 方法时,BindingFactory 的一个实例将被调用。

要将模型代码绑定到已构造的GUI,BindingFactory 的doBinding 方法(见 清单 7)必须执行以下操作:

捕获 URL,无论在本地、Internet 上或相对位置。

通过 JarURLConnection 类检查 JAR 并使用一个单独的自定义类加载器加载类。

从加载的XML 文档查找一个类,它匹配 Resource 元素的class 属性的名称。这个类是模型的入口点。

使用 Java 反射框架实例化这个充当入口点的类并调用它的init 方法。init 方法在概念上类似于一个典型 Java 类的main 方法,因为它们都是入口点。

如果 JAR 文件包含图像,还需要将图像载入内存。

清单 7. BindingFactory 的doBinding 方法

public void doBinding(XUINode resource, XUI xui) throws XUIBindingException,   MalformedURLException, IOException {   if(resource.getAttributeValue("type").equals("java")) {     String className = resource.getAttributeValue("class");     String aURLString = resource.getAttributeValue("uri");     URL url = null;     // get the url ... if it's not a valid URL, then try and grab      // it as a relative URL (i.e. java.io.File). If that fails     // re-throw the exception, it's toast     try {       url = new URL("jar:" + aURLString + "!/");     } catch (MalformedURLException mue) {       String s = "jar:file://" + new File(aURLString)         .getAbsolutePath().replace("//", "/") + "!/";       url = new URL(s);       if(url == null) {         // it really was malformed after all         throw new            MalformedURLException("Couldn't bind to: "           + aURLString);       }     }     // get a jar connection     JarURLConnection jarConnection = (JarURLConnection)url.openConnection();     // get the jar file     JarFile jarFile = jarConnection.getJarFile();     // jar files have entries. Cycle through the entries until finding     // the class sought after.     Enumeration entries = jarFile.entries();     // the class that will be the entry point into the model     JarEntry modelClassEntry = null;     Class modelClass = null;     XUIClassLoader xuiLoader =       new XUIClassLoader(this.getClass().getClassLoader());     while(entries.hasMoreElements()) {       JarEntry remoteClass = (JarEntry)entries.nextElement();       // load the classes       if(remoteClass.getName().endsWith(".class")) {         // have to get the second last word between period marks. This         // is because the convention allows for:          // org.purnamaproject.xui.XUI         // that is, the periods can represent packages.         StringTokenizer st =           new StringTokenizer(remoteClass.getName(), ".");         String previousToken = st.nextToken();         String currentToken = "";         String nameOfClassToLoad = previousToken;         while(st.hasMoreTokens()) {           currentToken = st.nextToken();           if(currentToken.equals("class"))             nameOfClassToLoad = previousToken;           else {             nameOfClassToLoad += currentToken;           }         }         // get an output stream (byte based) attach it to the         //inputstream from the jar file based on the jar entry.         ByteArrayOutputStream baos = new ByteArrayOutputStream();         InputStream is = jarFile.getInputStream(remoteClass);         final byte[] bytes = new byte[1024];         int read = 0;         while ((read = is.read(bytes)) >= 0) {           baos.write(bytes, 0, read);         }         Class c = xuiLoader.getXUIClass(nameOfClassToLoad, baos);         // check for the class that has the init method.         if(remoteClass.getName().equals(className + ".class")) {           modelClassEntry = remoteClass;           modelClass = c;         }       } else {         String imageNameLowerCase = remoteClass.getName().toLowerCase();         if(imageNameLowerCase.endsWith(".jpeg")           || imageNameLowerCase.endsWith(".jpg")           || imageNameLowerCase.endsWith(".gif")           || imageNameLowerCase.endsWith(".png")) {           // add resources (images)           XUIResources.getInstance().addResource(remoteClass, jarFile);         }       }     }     // now instantiate the model.     try {       // create a new instance of this class       Object o = modelClass.newInstance();       // get the method called 'init'. This is part of the API       // requirement       Method m = modelClass.getMethod("init", new Class[] {XUI.class});       // at last, call the method up.       m.invoke(o, new Object[] {xui});     } catch(InstantiationException ie) {       ie.printStackTrace();     } catch(IllegalAccessException iae) {       iae.printStackTrace();     } catch(NoSuchMethodException nsm) {       nsm.printStackTrace();     } catch(InvocationTargetException ite) {       System.out.println(ite.getTargetException());       ite.printStackTrace();     }   } else {     throw new XUIBindingException(        "This platform/API requires Java libraries.");   }}

检查这个框架的机制后,现在让我们测试一下这个框架并展示一个应用程序示例。

框架应用示例

项目框架(见 下载)包含几个示例,其中 Web 浏览器示例的内容非常详尽。

Web 浏览器 XML UI 文档

这个示例是一个相对真实的示例,您可能会打算将它放进一个声明性 XML UI 文档中。查看 清单 8,这个主 Window 指定了 x 和 y 坐标以及一个 id 值。所有元素必须拥有惟一的ID 值,以便业务逻辑能够引用这些元素。

Window 元素包含以下几个子元素:

Panel:提供主要布局

OpenFileDialog:打开新的Web 页面

SaveFileDialog:保存当前查看的Web 页面

CustomDialog:显示一个 yes 或 no 退出对话框

CustomDialog:显示 Web 书签

MenuBar:显示在 Window 的顶部,并提供菜单项功能

Resource:引用驱动这个 UI 的Java 模型代码

所含组件(比如 Button)的所有坐标用于表示在网格内的位置。所含组件的全部大小是指每个组件在网格内的宽度和高度的单元数量。元素的定义是高度声明性的,因为它们定义的是属性,而不是关于如何使用和创建那些属性的逻辑。本文的其他几个相关知识点包括:

MenuItem 可能有快捷键,比如 Ctrl-X 用于退出应用程序。

Window 拥有多个对话框,但默认情况下这些对话框不会显示,除非用户调用它们。

所有容器(比如 Panel)必须拥有布局,而且必须指定布局中的行数和列数。

清单 8. Web 浏览器 XML UI

                                         html    htm             html    htm                                                                          http://www.w3c.org      http://www.agentcities.org      http://www.apache.org      http://www.gnu.org                                                                                                                                                           

当然,如果没有用户交互,这个 UI 没有任何价值。下面就介绍如何进行用户交互。

Web 浏览器 Java 代码模型逻辑

在 清单 8 中,Resource 元素包含一个类名,该类充当进入应用程序模型的入口点。清单中给出的类名是 BrowserModel,因此,在 Java 端,已编译的类名必须与之匹配。类名包括名称空间,本例中为默认名称空间。

因此,任何类都可以充当进入应用程序的模型部分的入口点,只要它的名称与 Resource 元素的class 属性值相同。要让用户交互在运行时能够正确连接,实现类必须遵循以下几个规则:

使一个方法带有以下签名:public void init(XUI document)。

实现适当的事件处理接口以监听事件(比如用于 XUIButton 实现的ActionModel)。

使用 XML 元素的id 值引用 GUI 组件(这可以通过使用 XUI 中的几个方法实现)。

将自身作为监听器添加到适当的组件。这个框架中的所有事件生成组件(比如 XUIButton 类实现)都实现 XUIEventSource,因此都生成 UI 事件。

在 清单 9 中,BrowserModel 类在 init 方法中执行自己的初始化。初始化过程包括通过 id 值引用组件,创建包含 Web URL 书签的菜单项,通过 addEventListener 方法将自身作为一个监听器添加到多个组件。BrowserModel 可以将自身添加为一个监听器,因为它是一个 XUIModel(ActionModel 是 XUIModel 的子类型)。还有一点值得一提:XUIComponentFactory 类提供多种创建 XUI 组件的方法。

清单 9. 部分代码清单:初始化

...import org.purnamaproject.xui.binding.ActionModel;...public class BrowserModel implements ActionModel, TextModel, WindowModel,   ListActionModel {   ...   private XUI xui;   ...   public void init(XUI document) {     xui = document;     ...     bookmarksList = (XUIList)xui.getXUIComponent("list_0");     homeButton = (XUIButton)xui.getXUIComponent("button_1");     ...     List bookmarks = bookmarksList.getItems();     for(int i = 0; i < bookmarks.size(); i++) {       String url = (String)bookmarks.get(i);       XUIMenuItem aMenuItem = XUIComponentFactory.makeMenuItem(url);       bookmarksMenu.addMenuItem(aMenuItem);       linkModel.addSource(aMenuItem);       aMenuItem.addEventListener(linkModel);     }     ...     homeButton.addEventListener(this);     ...   }...}

清单 10 显示了针对各种组件的事件处理代码,例如:

openMenuItem 将导致一个 fileDialog 出现(一个模式对话框将打开一个本地存储的Web 网页)。

homeButton 和 popuphomeMenuItem(在窗口中通过单击右键访问)都会调用 doHome 方法,该方法将浏览器导向 HypertextPane 元素(参见 清单 8)的uri 属性值。

fileDialog 将加载一个新文件,然后递增 index 变量,应用程序的queue 使用该变量跟踪此前访问过的Web 页面。

清单 10. 部分代码清单:事件处理

public void action(XUIComponent component)   {     if(component == openMenuItem) {       fileDialog.setVisible(true);     } else if(component == homeButton || component == popuphomeMenuItem) {       doHome();     } else if(component == prevButton || component == popupprevMenuItem) {       doPrevious();     } else if(component == nextButton || component == popupnextMenuItem) {       doNext();     } else if(component == fileDialog) {       if(fileDialog.getSelectedFile() !=null)         hyperTextPane.setURL(fileDialog.getSelectedFileAsURL());       index++;       if(index != queue.size()) {         nextButton.setEnabled(false);         popupnextMenuItem.setEnabled(false);         for(int i = index; i < queue.size(); i++) {           queue.remove(i);         }       }       queue.add(hyperTextPane.getURL());       prevButton.setEnabled(true);       popupprevMenuItem.setEnabled(true);     } else if(component == saveDialog) {       try {         FileOutputStream fos = new FileOutputStream(saveDialog.getSelectedFile());         hyperTextPane.getDocument().writeTo(fos);       } catch (FileNotFoundException fnfe) {         fnfe.printStackTrace();       } catch (IOException ioe) {         ioe.printStackTrace();       }     } else if(component == popupsaveasMenuItem || component == saveMenuItem) {       saveDialog.setVisible(true);     } else if(component == popupbookmarkMenuItem || component == bookmarkMenuItem) {       doBookmark(hyperTextPane.getURL());     } else if(component == notDontExit) {       exitDialog.setVisible(false);       browserWindow.setVisible(true);     } else if(component == yesExit) {       System.exit(0);     } else if(component == exitMenuItem) {       exitDialog.setVisible(true);     } else if(component == manageBookmarksMenuItem) {       bookmarksDialog.setVisible(true);     }   }

图 2 中最后的应用程序展示了一个基本的Web 浏览器,它允许显示本地页面、基于 Web 的页面和此前访问过的Web 页面,并提供书签管理功能。

图 2. Web 浏览器的屏幕截图

本文的下载 部分包含其他几个示例应用程序。

问题和挑战

尽管这个解决方案很精彩,但这种方法是相当理想化的:框架中的安全问题被忽略了。回想一下这个 API 是如何随意地从任何 URI 加载 JAR 文件的。回顾一下 清单 8 中的Resource 元素。其类型实际上是 anyURI,这意味着本地文件、网络上的文件和 Internet 上的文件。一个应用程序应该信任来自任何地方的业务逻辑吗?显然,您需要考虑某种安全模型以限制不可信资源的加载。解决这个问题的一种方法是通过引用一个查找表来限制 URI。另一种更干净的方法是使用数字证书。

最后要注意一点,在这个声明性 XML UI 格式中可以加载其他 XML 格式。由于需要使用名称空间,这个 XML 模式支持这个功能。作为一个示例,您可以在 XML 文档中嵌入一个单独的XML 格式来表示可伸缩的矢量图形。

结束语

本文介绍了声明性 XML UI 语言的定义和外观,一个配套 Java 框架和一个示例应用程序 — 一个 Web 浏览器。最后,本文提出了潜在的安全问题和其他应该关注的问题。

创建声明性 XML UI 并不是什么新技术,它是一个日渐成熟和应用广泛的软件开发领域。这种技术的好处之一是有助于促进软件重用和模块化。

本文配套源码

当你感到悲哀痛苦时,最好是去学些什么东西。

创建一种声明性XMLUI语言-用Java语言构建一个UI和配套框架

相关文章:

你感兴趣的文章:

标签云: