3.3.3 实用示例
在本节中,我们将要介绍一个实用的示例程序,用来说明在实际环境中XML的用法。请回忆一下卷Ⅰ第12章,GridBagLayout是Swing构件中最有用的布局管理器。然而,人们都很畏惧它,这不仅是因为它的复杂性,还因为其编码冗长乏味。把布局描述放到一个文本文件中来替代大量重复代码将会带来很大便利。在本节中,你将看到怎样用XML来描述网格组(grid bag)布局和怎样解析布局文件。
网格组是由行和列构成的,它和HTML表格非常相似。与HTML表格相似的是,我们把它描述成一个行的序列,每个行都包含若干单元格:
有些单元格可以跨多行多列。在网格组布局中,这是通过将gridwidth和gridheight设置为大于1的值来实现的。我们将使用相同的名字作为属性名:
同样,我们将属性应用于网格组的其他约束:f?ill、anchor、gridx、gridy、weightx、weighty、ipadx和ipady。(我们不处理insets约束,因为它的值不是简单类型,但是要支持它也是很简单的。)例如:
对大多数属性,我们都提供了与为GridBagConstraints的无参构造器所提供的默认值相同的默认值:
gridx和gridy的值受到了特殊处理,因为如果手工设定会很冗长且易于出错。因此,提供它们的值是一项可选操作:
如果没有提供这些值,程序会通过如下的启发式方法来确定它们:在第0列,gridx的默认值是0;否则,它是前面的gridx加上前面的gridwidth;gridy的默认值总是与行数相同。这样,在大多数跨越多行的情况下,你都不必指定gridx和gridy的值。但是,如果一个构件跨越多列,那么每当要跨过这个构件时,就必须指定gridx。
注意:网格组专家可能会奇怪,我们为什么不使用RELATIVE和REMAINDER机制让网格组布局自动确定gridx和gridy的位置呢?我们试过这种方法,但是怎么也不能产生图3-4中那个字体对话框示例的布局。阅读了GridBagLayout的源代码后,我们发现,很明显,它的算法没有完成恢复绝对位置所必需的繁重任务。
这个程序对属性进行解析,并且设置了网格组的约束条件。例如,要读取网格宽度,程序只需包含下面这行语句:
程序不必担心属性的缺失,因为当文档中没有指定任何其他的值时,解析器会自动提供其默认值。
如果要测试是否指定了gridx或gridy属性,我们可以调用getAttribute方法来检查它是否返回空串:
我们发现允许单元格包含任意对象会显得很方便,这使我们能够指定如边界那样的非构件类型。我们只要求这些对象属于这样的类:它具有一个默认构造器,而对每个属性都提供了相应的获取器(getter)/ 设置器(setter)对。(例如被称为JavaBean的类。)
bean是由一个类名和0或多个属性定义的:
属性包含一个名字和一个值。
把字符串用标签围起来似乎有点麻烦。为什么不只用#PCDATA表示字符串而只留下用于其他类型的标签呢?因为那样我们就需要使用混合式内容,并且会把value元素的规则弱化为:
这样的规则允许由任意文本和标签构成的混合内容。
程序可以使用BeanInfo类来设置属性,而BeanInfo可以枚举bean的属性描述符。我们用匹配名字的方式来查找属性,然后调用它的setter方法来设置其值。
当我们的程序读入一个用户界面描述时,它有足够的信息来构建和布局用户界面构件。但是,当然,这个界面是死的,因为它没有事件监听器。如果要添加事件监听器,我们必须先定位构件。因为这个缘故,我们为每个bean提供了ID类型的可选属性:
例如,下面是一个带有ID的组合框:
注意:在这个示例中,我们只使用了XML来描述构件布局,而把在Java代码中添加事件处理器的工作留给了程序员。你可以更进一步,将该代码添加到XML描述中去。最有前途的方式是用JavaScript这样的脚本语言来编码这种代码。如果你想添加这样的增强功能,请参考第8章描述的Nashorn JavaScript解释器。
程序清单3-2的程序显示了如何使用GridBagPane类来完成设定网格组布局时所有的无聊工作,这个布局是在程序清单3-4中定义的。图3-4显示了运行结果。该程序只初始化了组合框(这项工作对于GridBagPane支持的bean属性设定机制来说过于复杂了)和添加事件监听器;程序清单3-3中的GridBagPane类用于解析XML文件,构造构件并放置它们;程序清单3-5显示的是DTD文件。
如果选择了包含字符串-Schema的文件,那么该程序除了DTD,还可以处理Schema。
程序清单3-6就包含了这样的Schema。
这个例子是XML的典型用法。XML格式十分健壮,足以表达复杂的关系。在此基础上,通过接管有效性检查和提供默认值等例行工作,XML解析器添加了新的价值。
程序清单3-2 read/GridBagTest.java
程序清单3-3 read/GridBagPane.java