安卓ui xml_创建声明性XML UI语言

本文探讨了声明式XML UI在GUI开发中的优势,以一个Web浏览器为例展示了如何使用XML构建用户界面,并讨论了相关框架和Java API的实现。通过这种方式,可以将UI代码与业务逻辑解耦,提高代码可读性和维护性,但同时也提出了安全和挑战的问题。
摘要由CSDN通过智能技术生成

GUI开发可能是一项艰巨的任务。 GUI框架的文档记录并不总是很好,所需的代码量可能会快速增长,从而减慢了开发流程。 最重要的是,支持这些GUI框架的拖放工具和IDE通常可以引导GUI软件开发人员创建难以管理且难以阅读的代码。 这会进一步模糊业务逻辑和描述GUI的代码之间的界线,从而使软件维护更加困难。

这是说明性UI语言变得很方便的地方。 UI语言描述的是“什么”,而不是“如何”。 例如,HTML描述的是显示的内容,而不是用于呈现内容的呈现功能。 通过不使用声明性语言指定“方式”,控制流也被省去了。 尽管这种损失听起来像是一种限制,但由于消除了诸如修改全局状态(例如变量)或调用其他函数或方法之类的副作用,它成为一种优势。 选择声明性语言还具有将UI代码与应用程序代码分离的好处。 这种解耦可以带来未来的好处,例如在项目角色和团队角色之间明确区分,甚至可以减少将业务逻辑与多个视图或视图技术集成的成本。

如今,使用了许多声明性XML UI的示例。 使用GNOME桌面环境的Linux®和UNIX®操作系统具有Glade。 Microsoft®Windows®用户具有可扩展应用程序标记语言(XAML),该语言支持多种功能,包括在XML中插入代码。 Adobe®Flex®Framework的MXML格式描述了Adobe Shockwave(SWF)播放器的GUI,还包括代码插入。 请参阅相关主题的更多信息的链接。

Java技术中的基本声明性UI框架的一组要求可能是:

  • 验证:使用XML模式
  • DOM:一个自定义DOM,用于处理细节,例如使GUI组件状态和XML节点状态保持同步
  • 持久性: GUI的编组和解组
  • 图像数据:存储为Base64数据
  • Swing组件:表示用于GUI开发的更常用的Swing组件

考虑到这些要求,是时候创建声明性XML了。

声明式XML

清单1中的XML格式的第一次尝试展示了一个简单的窗口,一个面板和一个按钮。 清单1中的属性代表了基本必需的属性,例如坐标,尺寸和引用单个内存中组件的唯一标识符。

清单1.声明性XML概念
<?xml version="1.0" encoding="UTF-8"?>
<xui:XUI>
  <xui:Window id="window_0" name="Hello World" width="300" height="300" x="426"
    y="282" visible="true">
    <xui:GridLayout height="1" width="1"></xui:GridLayout>
    <xui:Panel id="panel_0" x="0" y="0" name="Hello Panel"
      width="1" height="1">
      <xui:GridLayout height="1" width="1"></xui:GridLayout>
        <xui:Button x="0" y="0" width="1" height="1" id="button_0"
          label="Press Me" enabled="true" selected="true" orientation="horizontal"/>
    </xui:Panel>
  </xui:Window>
  <xui:Resource type="java" class="ButtonModel" uri="model.jar"/>
</xui:XUI>

声明性的XML UI将XML元素映射到Java Swing框架 ,该框架提供了最大的可移植性,因为Swing保证在所有当前Java运行时环境中都可用。 许多Swing组件将具有XML格式内的代表性XML元素。

该框架使用XML模式。 XML模式允许在模式实例内强制执行指定的顺序,基数和数据类型。 这个很重要; 框架将期望以指定的顺序指定类型的一组XML元素。 清单2演示了XML模式实例中层次结构的初始元素和属性。

清单2.声明性XML UI模式:初始元素
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema elementFormDefault="qualified"
  targetNamespace="http://xml.bcit.ca/PurnamaProject/2003/xui"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:xui="http://xml.bcit.ca/PurnamaProject/2003/xui">

  <xs:element name="XUI">
    <xs:complexType>
      <xs:sequence>
        <xs:element minOccurs="0" maxOccurs="128" ref="xui:Window"/>
        <xs:element minOccurs="0" maxOccurs="1" ref="xui:Resource"/>
      </xs:sequence>
      <xs:attribute name="id" type="xs:anyURI" use="required"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="Resource">
    <xs:complexType>
      <xs:sequence>
      </xs:sequence>
      <xs:attribute name="uri" type="xs:anyURI" use="required"/>
      <xs:attribute name="class" type="xs:token" use="required"/>
      <xs:attribute name="type" use="required">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="java"/>
            <xs:enumeration value="groovy"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
    </xs:complexType>
  </xs:element>

  <xs:element name="Window">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="xui:GridLayout"/>
        <xs:choice minOccurs="0" maxOccurs="unbounded">
          <xs:element ref="xui:BasicDialog"/>
          <xs:element ref="xui:OpenFileDialog"/>
          <xs:element ref="xui:SaveFileDialog"/>
          <xs:element ref="xui:CustomDialog"/>
          <xs:element ref="xui:Panel"/>
          <xs:element ref="xui:SplitPanel"/>
          <xs:element ref="xui:TabbedPanel"/>
        </xs:choice>
        <xs:element minOccurs="0" maxOccurs="1" ref="xui:MenuBar"/>
      </xs:sequence>
      <xs:attribute name="id" type="xs:ID" use="required"/>
      <xs:attribute name="x" type="xs:short" use="required"/>
      <xs:attribute name="y" type="xs:short" use="required"/>
      <xs:attribute name="width" type="xs:unsignedShort" use="required"/>
      <xs:attribute name="height" type="xs:unsignedShort" use="required"/>
      <xs:attribute name="name" type="xs:string" use="required"/>
      <xs:attribute name="visible" type="xs:boolean" use="required"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="GridLayout">
    <xs:complexType>
      <xs:attribute name="width" type="xs:unsignedShort" use="required"/>
      <xs:attribute name="height" type="xs:unsignedShort" use="required"/>
    </xs:complexType>
  </xs:element>

</xs:schema>

详细查看架构。 首先,XML声明必须位于所有内容之前,甚至必须位于XML建议中指出的空格和注释之前。 接下来, schema元素包含其他元素:

  • elementFormDefault="qualified"声明所有元素必须具有名称空间-前缀或默认名称空间。
  • targetNamespace="http://xml.bcit.ca/PurnamaProject/2003/xui"指定目标名称空间URI。
  • 模式实例使用W3C XML模式建议及其中的所有元素( xmlns:xs="http://www.w3.org/2001/XMLSchema" )。
  • xmlns:xui="http://xml.bcit.ca/PurnamaProject/2003/xui"标识另一个名称空间及其附带的前缀。

在XSD中使用名称空间很重要:消除名称空间冲突。 当来自两种或多种XML格式的两个或多个元素具有相同的名称时,就会发生名称空间冲突 。 这种冲突会引起对对其相应标签集感兴趣的任何应用程序的困惑。 通过使用名称空间和随附的名称空间前缀,可以完全避免此问题。

接下来,根级别数据类型元素XUI指出:

  • 它允许使用0到128个Window元素的一个序列,最后允许一个Resource元素。 这两个都是稍后在架构实例中找到的引用元素。
  • 它具有id属性,该属性是必需的,并且必须为anyURI类型。

XUI元素(可能)包含许多Window元素,尽管基于minOccurs属性中值为0的XUI元素可能没有Window元素。 至于Resource元素:

  • 由于其xs:sequence元素为空,因此它的内容模型为空。
  • 它具有三个必需的属性。
  • 最后一个属性,即type属性,从XSD的定义类型( token )创建派生的简单类型,其中限制方面是enumeration ,允许枚举的javagroovy文字文本值。

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

Window元素:

  • 包含一个GridLayout序列,在BasicDialogOpenFileDialogSaveFileDialogCustomDialogPanelSplitPaneTabbedPane元素之间无限选择,最后包含零个或一个MenuBar
  • 具有七个属性(全部为必需属性),它们使用XML Schema Recommendation中的各种定义的数据类型(请注意xs前缀)。

Window可以包含许多不同的顶层和中间层容器。 Window元素引用GridLayout元素。 GridLayout元素指定组件可以占据的单元格网格的尺寸。 GridLayout提供了与Java环境中的java.awt.GridBagLayout相似的布局功能,只是没有所有的复杂性。

不用再看了,很明显XML Schema实质上是表达性的。 清单3显示了更多元素。

清单3.声明性XML UI模式:更多元素
...
<xs:element name="CustomDialog">
  <xs:complexType>
    <xs:sequence>
      <xs:element ref="xui:GridLayout"/>
      <xs:element minOccurs="0" maxOccurs="unbounded" ref="xui:Panel"/>
    </xs:sequence>
    <xs:attribute name="modal" type="xs:boolean" use="required"/>
    <xs:attribute name="idref" type="xs:IDREF" use="optional"/>
    <xs:attribute name="name" type="xs:string" use="required"/>
    <xs:attribute name="id" type="xs:ID" use="required"/>
    <xs:attribute name="x" type="xs:short" use="required"/>
    <xs:attribute name="y" type="xs:short" use="required"/>
    <xs:attribute name="width" type="xs:unsignedShort" use="required"/>
    <xs:attribute name="height" type="xs:unsignedShort" use="required"/>
    <xs:attribute name="visible" type="xs:boolean" use="required"/>
  </xs:complexType>
</xs:element>

<xs:element name="Panel">
  <xs:complexType>
    <xs:sequence>
      <xs:element maxOccurs="1" minOccurs="1" ref="xui:GridLayout"/>
      <xs:choice minOccurs="0" maxOccurs="unbounded">
        <xs:element minOccurs="0" maxOccurs="unbounded" ref="xui:Button"/>
        <xs:element minOccurs="0" maxOccurs="unbounded" ref="xui:Calendar"/>
        <xs:element minOccurs="0" maxOccurs="unbounded" ref="xui:CheckBox"/>
        <xs:element minOccurs="0" maxOccurs="unbounded" ref="xui:ComboBox"/>
        <xs:element minOccurs="0" maxOccurs="unbounded" ref="xui:HypertextPane"/>
        <xs:element minOccurs="0" maxOccurs="unbounded" ref="xui:Image"/>
        <xs:element minOccurs="0" maxOccurs="unbounded" ref="xui:Label"/>
        <xs:element minOccurs="0" maxOccurs="unbounded" ref="xui:List"/>
        <xs:element minOccurs="0" maxOccurs="unbounded" ref="xui:PasswordField"/>
        <xs:element minOccurs="0" maxOccurs="unbounded" ref="xui:ProgressBar"/>
        <xs:element minOccurs="0" maxOccurs="unbounded" ref="xui:RadioButton"/>
        <xs:element minOccurs="0" maxOccurs="unbounded" ref="xui:SliderBar"/>
        <xs:element minOccurs="0" maxOccurs="unbounded" ref="xui:Table"/>
        <xs:element minOccurs="0" maxOccurs="unbounded" ref="xui:TextArea"/>
        <xs:element minOccurs="0" maxOccurs="unbounded" ref="xui:TextField"/>
        <xs:element minOccurs="0" maxOccurs="unbounded" ref="xui:Tree"/>
      </xs:choice>
    </xs:sequence>
    <xs:attribute name="x" type="xs:unsignedShort" use="required"/>
    <xs:attribute name="y" type="xs:unsignedShort" use="required"/>
    <xs:attribute name="width" type="xs:unsignedShort" use="required"/>
    <xs:attribute name="height" type="xs:unsignedShort" use="required"/>
    <xs:attribute name="name" type="xs:string" use="required"/>
    <xs:attribute name="id" type="xs:ID" use="required"/>
    <xs:attribute name="idref" type="xs:IDREF" use="optional"/>
  </xs:complexType>
</xs:element>

<xs:element name="RadioButton">
  <xs:complexType>
    <xs:sequence>
      <xs:element maxOccurs="3" minOccurs="0" ref="xui:Image"/>
    </xs:sequence>
    <xs:attribute name="label" type="xs:string" use="required"/>
    <xs:attribute name="x" type="xs:unsignedShort" use="required"/>
    <xs:attribute name="y" type="xs:unsignedShort" use="required"/>
    <xs:attribute name="width" type="xs:unsignedShort" use="required"/>
    <xs:attribute name="height" type="xs:unsignedShort" use="required"/>
    <xs:attribute name="enabled" type="xs:boolean" use="required"/>
    <xs:attribute name="selected" type="xs:boolean" use="required"/>
    <xs:attribute name="id" type="xs:ID" use="required"/>
    <xs:attribute name="orientation" use="required">
      <xs:simpleType>
        <xs:restriction base="xs:token">
          <xs:enumeration value="horizontal"/>
          <xs:enumeration value="vertical"/>
        </xs:restriction>
      </xs:simpleType>
    </xs:attribute>
  </xs:complexType>
</xs:element>
...

请注意,没有存储易失性状态信息,仅存储可能有助于GUI组件重建的状态信息。 一个示例是CustomDialog元素的状态信息:

  • 对话框中允许的Panel元素数
  • 对话框是否为模式对话框(模式对话框会捕获焦点,直到用户关闭对话框为止。)
  • 桌面中的坐标(以像素为单位的x和y )
  • 尺寸(以像素为单位的宽度和高度)
  • 窗口的可见性

Panel是一个中间容器,并允许包含大量的原子成分。 返回清单3Panel仅有一个GridLayout并且可以选择不将任何原子组件放置在Panel内部或根据需要添加任何原子组件。 Panel本身具有x和y坐标。 但是, Panel使用桌面上的像素(如CustomDialog那样),而是使用x和y坐标来引用父容器的GridLayout 。 就像俄罗斯的娃娃一样,这种嵌套的构图非常类似于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编组到文件中)。 这些步骤是:

图1说明了以下步骤

  1. Builder从检索BuilderFactory
  2. 在允许检索XUI文档之前, Builder首先确保XML文档已经过验证和解析。 如果解析或验证失败,则将发生XUIParseException ,并且框架将中止文档加载。
  3. Builder创建DOM,对象在其中反映已读入的XML元素。
  4. XUI对象内部调用的Realizer对象被实例化,并准备好执行下一步。
  5. 实现是框架基于先前创建的XML节点的层次结构(框架引擎的真正核心)创建GUI组件层次结构的地方。
  6. 利用Java环境中的反射功能,模型逻辑(驱动UI的应用程序部分)将绑定到刚刚实现的GUI组件。
图1.框架流程和XUI API用来构建GUI的步骤的详细视图
XUI API用来构建GUI的框架流程和详细步骤视图

这个六步调用流程易于使用,但包含大量消息和内部的对象实例化,接下来值得探讨。 该框架的核心是步骤56

GUI组件和XML节点组成

图1中步骤5创建了一个组件模型。 这允许将XML节点(现在是内存中的对象)与GUI组件配对。 此配对需要以下事件的非常严格的同步:

  • 对于框架读取的每个XUINode (代表任何XML元素的内存对象), XUIComponent必须创建一个XUIComponent来包含XUINode
  • 对于在内存中创建的每个XUIComponent ,必须创建一个GUI对等项,例如javax.swing.JFrame
  • 每次XUIComponent实例(或其子类型之一,例如XUIButton )时(例如,更改尺寸时), XUIComponent都会确保XUINode和GUI对等体同时被同等地更新。

通过满足上述要求,该框架允许程序员阅读(取消编组)XML文档,修改DOM,并将更改保存回XML文档(编组)。 程序员甚至可以以编程方式创建新的DOM并将其封送。

DOM节点编组

为了使XUINode能够将自己封送为XML,提供了toString方法的自定义实现(在清单5中 )。 根节点可以包含许多子节点。 每个子节点可以包含自己的子节点集,依此类推。 通过调用根级节点的toString方法,框架可以轻松封送整个XML文档。 添加了名称空间,并使每个元素了解其在层次结构中的level (通过level变量)。 这样,在调用toString方法时,它提供了缩进,以便于手动读取这些文档。

清单5. XUINode toString方法实现
@Override
public 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("<");
    // get namespaces for this node
    Enumeration keys = nameSpaces.keys();

    String names = "";
    while(keys.hasMoreElements()) {
        String uri = (String)keys.nextElement();
        String prefix = (String)nameSpaces.get(uri);
        /* if its the xsi namespace (XML Schema Instance),
         * ignore it, this isn't part of that namespace but it is
         * needed for the XML Schema validator to work. */
        if(!(prefix.equals("xsi"))) {
            sb.append(prefix + ":");
            namespacePrefix = prefix;
        }
        names += (" " + "xmlns:" + prefix + "=\"" + uri + "\"");
    }
    if(beginOfNamespace) {
        sb.append(name + names);
    } else {
        sb.append(name);
    }

    // do attributes if there are any
    if(attributes.getLength() > 0) {
        int length = attributes.getLength();
        for(int i = 0; i < length; i++) {
            String attributeValue = attributes.getValue(i);
            String attributeQName = attributes.getQName(i);
            sb.append(" " + attributeQName + "=\"" + attributeValue + "\"");
        }
    }
    sb.append(">");
    sb.append(cdata);
    int size = childNodes.size();
    for(int i = 0; i < size; i++) {
        XUINode e = (XUINode)childNodes.get(i);
        sb.append(e.toString());
    }
    if(size > 0) {
        sb.append("\n");
        for(int s = 0; s < (level); s++)
            sb.append("  ");
    }
    if(namespacePrefix.length() > 0) {
        sb.append("</" + namespacePrefix + ":" + name + ">");
    } else {
        sb.append("</" + name + ">");
    }

    return sb.toString();
}

添加到容器组件

另一部分值得探讨是容器类型XUIWindow ,这是一个间接子类型的XUIComponentXUIWindow实现表示一个javax.swing.JFrame组件,因此必须允许将子组件添加到布局中。 清单6演示了实现。 第一步是确保只能将某些类型的组件添加到XUIWindow 。 如果是这样,则XUIComponent的DOM节点表示形式XUINode ,以便访问该组件的属性。 请注意,这要求所有XUIComponent的构造函数初始化这些值。

进一步检查以确保该组件是中间容器(例如XUIPanel ),并且该中间容器适合XUIWindow的行和列网格。 最后,该组件可以被添加到XUIWindow确保组件已启用,在布局网格内的正确位置设置,并且XUIWindowXUINode (的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的实例。

BindingFactorydoBinding方法(在清单7中 )必须做几件事才能将模型代码绑定到构造的GUI:

  • 抓取URL,无论是本地URL,Internet还是相对URL。
  • 通过JarURLConnectionJarURLConnection JAR,并使用自定义且独立的类加载器加载类。
  • 从加载的XML文档中查找与Resource元素的class属性名称匹配的class 。 该类是模型的入口点。
  • 使用Java反射框架实例化入口点类的实例,并调用其init方法。 init方法在概念上与典型Java类的main方法相似,因为它们都是入口点。
  • 如果JAR文件包含图像,则也将它们加载到内存中
清单7. BindingFactorydoBinding方法
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
  • SaveFileDialog用于保存当前查看的网页
  • 一个显示是或否退出对话框的CustomDialog
  • 显示Web书签的CustomDialog
  • 显示在Window顶部并提供菜单项功能的MenuBar
  • 用于引用将驱动此UI的Java模型代码的Resource

所包含的组件(例如Button )的所有坐标都引用网格内的位置。 所包含组件的所有尺寸是指每个组件在网格中有多少个单元格宽和高。 组件的定义是高度声明性的,因为它们定义属性,而不是有关如何使用或创建这些属性的逻辑。 本文档中的其他一些兴趣点是:

  • MenuItem可以具有快捷键,例如Ctrl-X可以退出应用程序。
  • Window具有对话框,但是默认情况下,这些对话框在用户调用它们之前是不可见的。
  • 所有容器(例如Panel )必须具有布局,并且必须指定该布局中的行数和列数。
清单8. Web浏览器XML UI
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated by The Purnama Project XUI API version 0.5 -->
<xui:XUI xmlns:xui="http://xml.bcit.ca/PurnamaProject/2003/xui"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xml.bcit.ca/PurnamaProject/2003/xui ../../xui.xsd"
  id="http://xml.bcit.ca/PurnamaProject/examples/XUIWebBrowser">
  <xui:Window id="window_0" name="XUI Web Browser" x="200" y="20" width="800"
    height="600" visible="true">
    <xui:GridLayout width="1" height="1"></xui:GridLayout>
    <xui:Panel x="0" y="0" width="1" height="1" id="panel_0" name="main panel"
      idref="window_0">
      <xui:GridLayout width="8" height="8"></xui:GridLayout>
      <xui:HypertextPane x="1" y="0" width="8" height="7" id="hyper_0"
        uri="http://www.w3c.org"></xui:HypertextPane>
      <xui:Button  x="0" y="0" width="1" height="1" id="button_0" label="Back"
        enabled="true" orientation="horizontal"></xui:Button>
      <xui:Button  x="0" y="3" width="1" height="1" id="button_1" label="Home"
        enabled="true" orientation="horizontal"></xui:Button>
      <xui:Button  x="0" y="7" width="1" height="1" id="button_2"
        label="Forward" enabled="true" orientation="horizontal"></xui:Button>
    </xui:Panel>

    <!-- For opening files. Only want to see html files -->
    <xui:OpenFileDialog x="10" y="10" width="400" height="300"
      id="filedialog_0" idref="window_0" visible="false">
      <xui:Filter>html</xui:Filter>
      <xui:Filter>htm</xui:Filter>
    </xui:OpenFileDialog>

    <!-- For saving files. Only want to save html files -->
    <xui:SaveFileDialog x="10" y="10" width="400" height="300"
      id="savedialog_0" idref="window_0" visible="false">
      <xui:Filter>html</xui:Filter>
      <xui:Filter>htm</xui:Filter>
    </xui:SaveFileDialog>

    <!-- Ask the user if they really want to quit -->
    <xui:CustomDialog x="200" y="200" width="320" height="160"
      id="customdialog_1" idref="window_0" name="Exit Purnama Browser"
        modal="true" visible="false">
      <xui:GridLayout width="1" height="1"></xui:GridLayout>
      <xui:Panel x="0" y="0" width="1" height="1" id="panel_2"
        name="Quit Panel" idref="customdialog_0">
        <xui:GridLayout width="5" height="4"></xui:GridLayout>
        <xui:Label id="label_0" x="1" y="1" width="3" height="1"
          justified="center" text="Do you really want to exit?"></xui:Label>
        <xui:Button  x="2" y="1" width="1" height="1" id="button_3"label="Yes"
          enabled="true" orientation="horizontal"></xui:Button>
        <xui:Button  x="2" y="3" width="1" height="1" id="button_4" label="No"
          enabled="true" orientation="horizontal"></xui:Button>
      </xui:Panel>
    </xui:CustomDialog>

    <!-- For displaying the bookmarks -->
    <xui:CustomDialog x="100" y="100" width="300" height="300"
      id="customdialog_0" idref="window_0" name="Bookmarks" modal="false"
        visible="false">
      <xui:GridLayout width="1" height="1"></xui:GridLayout>
      <xui:Panel x="0" y="0" width="1" height="1" id="panel_1"
        name="bookmarks panel" idref="customdialog_0">
        <xui:GridLayout width="1" height="1"></xui:GridLayout>
        <xui:List x="0" y="0" width="1" height="1" id="list_0" enabled="true"
          itemSelected="0" scrolling="vertical">
          <xui:ListItem>http://www.w3c.org</xui:ListItem>
          <xui:ListItem>http://www.agentcities.org</xui:ListItem>
          <xui:ListItem>http://www.apache.org</xui:ListItem>
          <xui:ListItem>http://www.gnu.org</xui:ListItem>
        </xui:List>
      </xui:Panel>
    </xui:CustomDialog>

    <!-- The menu bar with pop-up menu items too -->
    <xui:MenuBar id="menuBar_0" idref="window_0">
      <xui:Menu id="menu_0" idref="menuBar_0" enabled="true"
        isPopupMenu="false" isSubMenu="false" label="File">
        <xui:MenuItem id="mi_1" idref="menu_0" enabled="true" label="Open URL"
          checked="false">
          <xui:Shortcut keyCode="F" keyModifier1="ALT"></xui:Shortcut>
        </xui:MenuItem>
        <xui:MenuItem id="mi_0" idref="menu_0" enabled="true" label="Save"
          checked="false">
          <xui:Shortcut keyCode="F" keyModifier1="ALT"></xui:Shortcut>
        </xui:MenuItem>
        <xui:MenuItem id="mi_2" idref="menu_0" enabled="true" label="Exit"
          checked="false">
          <xui:Shortcut keyCode="X" keyModifier1="CTRL"></xui:Shortcut>
        </xui:MenuItem>
      </xui:Menu>
      <xui:Menu id="menu_1" idref="menuBar_0" enabled="true"
        isPopupMenu="false" isSubMenu="false" label="Bookmarks">
        <xui:MenuItem id="mi_3" idref="menu_1" enabled="true"
          label="Add Bookmark" checked="false">
          <xui:Shortcut keyCode="D" keyModifier1="CTRL"></xui:Shortcut>
        </xui:MenuItem>
        <xui:MenuItem id="mi_4" idref="menu_0" enabled="true"
          label="Manage Bookmarks" checked="false">
          <xui:Shortcut keyCode="M" keyModifier1="CTRL"></xui:Shortcut>
        </xui:MenuItem>
      </xui:Menu>
      <xui:Menu id="menu_2" idref="hyper_0" enabled="true" isPopupMenu="true"
        isSubMenu="false" label="">
        <xui:MenuItem id="mi_5" idref="menu_2" enabled="true"
          label="Save As ..." checked="false"></xui:MenuItem>
        <xui:MenuItem id="mi_6" idref="menu_2" enabled="true" label="Previous"
          checked="false"></xui:MenuItem>
        <xui:MenuItem id="mi_7" idref="menu_2" enabled="true" label="Next"
          checked="false"></xui:MenuItem>
        <xui:MenuItem id="mi_8" idref="menu_2" enabled="true" label="Home"
          checked="false"></xui:MenuItem>
        <xui:MenuItem id="mi_9" idref="menu_2" enabled="true" label="Bookmark"
          checked="false"></xui:MenuItem>
      </xui:Menu>
    </xui:MenuBar>
  </xui:Window>

  <!-- The library (model) code that drives the user interface -->
  <xui:Resource type="java" class="BrowserModel" uri="BrowserModel.jar"/>
</xui:XUI>

当然,接下来没有用户交互,这都没有任何价值。

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本身可以添加为监听器,因为它是一个XUIModelActionModel是一个子类型的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页面)。
  • homeButtonpopuphomeMenuItem (在窗口中单击鼠标右键以访问它们)都调用doHome方法,该方法将浏览器定向到HypertextPane元素的uri属性值(来自清单8 )。
  • fileDialog将加载一个新文件,然后递增应用程序queue使用的index变量,以跟踪以前访问的网页。
清单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浏览器的屏幕截图
书签管理器以及显示本地页面,基于Web的页面和以前访问的页面的Web浏览器的屏幕快照。

您可以在本文的下载中找到其他几个示例应用程序。

难题与挑战

尽管该解决方案令人兴奋,但该方法相当理想:该框架内的安全问题已被忽略。 回忆一下API如何从任何URI无害地加载JAR文件。 回想一下清单8中所示的Resource元素。 类型实际上是anyURI 。 这意味着本地文件,网络上的文件,Internet上的文件。 任何地方。 应用程序应该从任何地方信任业务逻辑吗? 显然,您想考虑某种安全模型来限制不可信资源的加载。 解决此问题的一种方法是限制URI,是引用查找表。 另一种(更清洁的)解决方案是使用数字证书。

最后,考虑在此声明性XML UI格式中加载其他XML格式。 由于需要使用名称空间,因此XML Schema支持此功能。 例如,您可以嵌入单独的XML格式来表示XML文档中的可缩放矢量图形。

结论

本文介绍了一种声明性XML UI语言以及它的外观。 它引入了随附的Java框架以及示例应用程序-Web浏览器。 最后,它带来了潜在的安全问题和担忧。

创建声明性XML UI确实不是什么新鲜事。 但是,它是软件开发领域中日趋成熟并变得越来越普遍的领域。 加号之一是创建声明性XML UI有助于促进软件重用和模块化。


翻译自: https://www.ibm.com/developerworks/java/library/x-decxmlui/index.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值