【IT168 技术文章】对于大多数人来说,应用程序的look-and-feel只不过是一种偏好和欣赏。但是在某些情况下,有必要定制应用程序的look-and-feel,使其使用特定的字体、颜色模式或图标。例如,有些弱视用户常常要求look-and-feel在文本与背景之间具有高对比度,同时还要有大的字体和图标。"IBM Java Accessibility Checklist" 规定,一个应用程序中的所有用户界面对象都必须支持高对比度设置。
Java 平台早期的版本对颜色和字体的偏好设置的支持不是很好。从J2SE 1.4开始,尤其是随着J2SE 1.4.2中新的Windows XP 和 GTK look-and-feel设计的引入,用户现在可以配置他们想要的look-and-feel设计。Windows look-and-feel试图使用Windows平台底层的颜色和字体模式,而GTK look and feel则使得Linux用户能够通过定义一些脚本来定制look-and-feel。
乍一看来,“本地的” look-and-feel 设计的改进使得Metal look and feel失去了作用。然而在有些场合中,Metal look-and-feel(或者其定制版本)仍然比较有用。例如:
如果您需要的是能够在任何平台下运行,并且即使是被一个未签名的applet使用时也是可定制的这样一种look-and-feel。
如果您需要更多其他look-and-feel设计不能提供的定制能力。
如果您使用的是Java平台的一个早期版本,而这种版本最近的改进不可用。
本文将解释如何修改Metal look-and-feel,使其使用特定的字体和颜色模式。您还将学习如何修改用于绘制窗口小部件(例如复选框、单选按钮、树以及文件对话框)的图标。
首先我将解释如何重载用于look-and-feel的颜色和字体。接着我们将看看如何修改标准窗口小部件所使用的图标。最后,我们将看一个新look-and-feel的实例,在这个实例中您可以在一个文本文件中定义对颜色、字体和图标大小的设置。以后您就可以使用我们的例子来创建高对比度的look-and-feel。
javax.swing.plaf.metal.MetalLookAndFeel 类是 Metal look and feel的主类。这个类的Java API 文档(参见 参考资料)表明,这个类中定义的大部分方法都是getter方法,用以返回画Swing框架中提供的不同窗口小部件时所用的颜色和字体。然而,这些getter方法返回值的任务并不是直接在这个类中实现的,而是委托给了另一个类,这个类叫做一个 主题(theme)。主题惟一的作用就是提供要使用的颜色和字体的值。
Metal look and feel 使用一种可插式主题体系结构。这种look and feel 本身 ( javax.swing.plaf.metal.MetalLookAndFeel ) 定义了窗口小部件的外观以及它们对用户交互的反应,而主题则定义在绘制窗口小部件时应该使用的颜色和字体。您可以通过创建一个新的继承 javax.swing.plaf.metal.MetalTheme 的类来定义一种新的主题。
图 1 和 图 2 显示了来自SwingSet演示程序的两个屏幕截图,这个演示程序是随JSDK一起提供的。这两个图演示了不同的主题是如何改变一个应用程序的外观的,而且,这里的应用程序使用的是同一个look-and-feel。
图 1. 默认的主题

图 2. 高对比度主题

因此,定制Metal look and feel 很容易,只需:
创建一个新的扩展 javax.swing.plaf.metal.MetalTheme 的类,或者创建 javax.swing.plaf.metal.MetalTheme 的默认实现 javax.swing.plaf.metal.DefaultMetalTheme (参见 参考资料部分中给出的Java API文档的参考提示)。
重载这个主题类的 getXXXFont() 或者 getXXXColor() 方法,使这两个方法能返回在新主题中需要用到的字体或颜色。
通过调用 MetalLookAndFeel.setCurrentTheme(theme) 静态方法设置Metal look and feel中的新主题。
javax.swing.plaf.metal.MetalTheme 的Java API文档(参见 参考资料)表明,您可以重载大约50个不同的方法,在一个细粒度的程度上定义必须使用的颜色和字体。不幸的是,大部分的这些方法都没有相应的文档,这使得测试每个方法以检查它对用户界面的影响这样的一项任务相当烦人。
幸运的是,您通常不需要重载所有这些方法便可以得到一个可接受的结果――如果 vax.swing.plaf.metal.MetalTheme 提供了众多的方法,那么它的默认实现 javax.swing.plaf.metal.DefaultMetalTheme 也会以一致的方式实现大部分的这些方法,这样一来这些方法就会返回以下方法中某个方法的结果:
getBlack()
getWhite()
getPrimary1()
getPrimary2()
getPrimary3()
getSecondary1()
getSecondary2()
getSecondary3()
getControlTextFont()
getMenuTextFont()
getSubTextFont()
getSystemTextFont()
getUserTextFont()
getWindowTitleFont()
因而,继承 javax.swing.plaf.metal.DefaultMetalTheme 和重载这8种颜色及6种字体通常就足以获得一个一致的新主题。
不用硬编码这些方法(Java平台提供的主题中就是硬编码的),相反,一个好主意是实现一个能返回在外部资源文件中指定的颜色和字体的通用的主题。通过这种方式,我们可以轻易地定义一个新主题――我们只需要使用任何文本编辑器编辑一些值。
在某些情况下,可能还需要修改look and feel所使用的一些图标和图形资源。例如,您可能想修改复选框和单选按钮所使用的图标以便绘出它们的状态,或者使用其他的图标来绘制文件夹和树组件的节点。
例如,如果您希望某个look and feel带有大号字体,那么就需要修改图标。复选框和单选按钮所使用的图标的尺寸应该与它们的字体相适应。如果弱视用户不能辨认某个选框是否被选中,那么即使是带有大字体的复选框对他(她)来说也是毫无用处的,因为其图标的尺寸没有改变。
图3 显示了一个高对比度、大字体的但是图标的尺寸没有改变的主题的例子。您可以看到字体大小和图标尺寸之间的不协调。
图 3. 一个高对比度、大字体但是没有调整图标尺寸的主题
不幸的是,在一个主题中图标是不能修改的。因此,如果我们想重载Metal look and feel中使用的图标,我们就必须使用其他的技术。
Metal look and feel将一系列要使用的图形化资源(颜色、字体和图像)存放在一个 javax.swing.UIDefaults 对象中,这个对象基本上就是一种hash表。不同窗口小部件所需的每个图像都被该look and feel以特定的键(key)存放在这个表中,以便于检索。这同时也意味着如果您知道某一特定图像是存放在哪个键之下的,那么您就可以通过将另一个图像存放在同一个键下来替换那个图像。
让我们看看Metal look and feel如何初始化包含了图形化资源的这个表。
当look and feel被创建时,它首先实例化一个空的 UIDefaults 对象,这个对象可以通过以下方法来填充内容:
initClassDefaults(UIDefaults)
initComponentDefaults(UIDefaults)
initSystemColorDefaults(UIDefaults)
这些方法中每一个方法都是由该look and feel本身连续地调用的。这些方法连续地将图形化资源(不管是颜色、字体还是图像)存入 UIDefaults 表中惟一的键之下。
要了解更多细节,参见 javax.swing.plaf.MetalLookAndFeel 类的API 文档(参见 参考资料)。图像的初始化是在 initComponentDefaults(UIDefaults )中进行的。通过重载这个方法,您可以替换在look and feel中用到的任何标准图像。
不幸的是,存放图标时所参考的键没有相应的文档,但是 javax.swing.plaf.MetalLookAndFeel 类的源代码可以提供这些信息。表 1显示了用于引用Metal look and feel中图标的现有键的列表:
表 1. 用于引用Metal look and feel中图标的键CheckBox.iconInternalFrame . closeIconRadioButton.iconInternalFrame.maximizeIconInternalFrame.iconInternalFrame.iconifyIconInternalFrame.iconifyIconInternalFrame.minimizeIconFileView.directoryIconMenu.checkIconFileView.fileIconMenu.arrowIconFileView.computerIconMenuItem.checkIconFileView.hardDriveIconMenuItem.arrowIconFileView.floppyDriveIconCheckBoxMenuItem.checkIconFileChooser.detailsViewIconCheckBoxMenuItem.arrowIconFileChooser.homeFolderIconRadioButtonMenuItem.checkIconFileChooser.listViewIconRadioButtonMenuItem.arrowIconFileChooser.newFolderIconTree.openIconFileChooser.upFolderIconTree.closedIconSlider.horizontalThumbIconTree.leafIconSlider.verticalThumbIconTree.expandedIconInternalFrame.iconTree.collapsedIconInternalFrame.paletteCloseIcon
与随Java平台一起交付的其他 look-and-feel设计不同,Metal look and feel并不使用GIF文件来绘制它的图标。相反,它是通过一个名为 avax.swing.plaf.metal.MetalIconFactory 的工厂类来动态地创建图标的。对于表 1 中列出的每一个键,它都提供一个相应的 getXXXIcon() 方法,用以绘制 矢量图标。
利用一个工厂创建的矢量图标有一个好处,那就是它们可以使用当前主题所定义的颜色,而不是预定义的颜色。因为这种图标是矢量的,通常可以在不影响质量的情况下改变其大小。不幸的是,除了4个图标外,在 MetalIconFactory 中没有哪个方法会接收一个可以定义图标大小的参数。因此,定义图标大小的惟一的方法就是构建一个全新的图标工厂。
幸运的是,对这个问题并非无计可施。您可以创建一个实现 javax.swing.Icon 接口的类,然后将一个现有的图标以及缩放比例作为参数传给这个类的构造函数,再用 java.awt.Graphics2D API绘出该图标的一个新版本,在这个新版本中,其大小已发生了变化,其格式是位图图像格式(参见清单1)。当然,这意味着当一个图标被放大时,该图标的质量会有所损失。不过,这种方法实现起来比构建一个全新的图标工厂要容易得多。而且,对于像复选框和单选按钮这样的一些简单的图标,经放大后其质量依然是可以接受的。
清单 1. 一个图标放大器
2 protected class MagnifiedIcon implements Icon {
3 private Icon icon ;
4 private double factor ;
5
6 public MagnifiedIcon(Icon icon, double factor) {
7 this.icon = icon ;
8 this.factor = factor ;
9 }
10 public int getIconWidth() {
11 return (int)(icon.getIconWidth()*factor) ;}
12 public int getIconHeight() {
13 return (int)(icon.getIconHeight()*factor) ;}
14
15 public void paintIcon(Component c, Graphics g, int x, int y) {
16 Graphics2D g2d = (Graphics2D)g.create() ;
17 g2d.translate(x,y);
18 g2d.scale(factor, factor);
19 icon.paintIcon(c,g2d,0,0);
20 g2d.dispose();
21 }
22 }
23
清单 2 显示了基于Metal look and feel以及为提供可定制的、高对比度的look and feel而创建的颜色模式的一种通用look and feel的源代码。
第9行到第16行是装载定义了该look and feel的一些属性的外部资源文件。 这几行代码建立了一个定制的主题,该主题的颜色和字体是以外部文件定义的。
第18行到第20行重写了 MetalLookAndFeel 的一些标准方法,以便将该look and feel的名称、描述和ID也放在外部资源文件中定义。
第22行到第53行修改了该look and feel的UIDefaults,以便通过在外部资源文件中定义的一个缩放比例来放大标准图标。真正的放大工作是由 MagnifiedIcon 这个内部类来做的(第95行到第113行)。
第56行到第92行是这个定制的主题的实现。这些代码首先从资源文件中读出要使用的字体的名称和大小(第64行到第70行)。然后重写 DefaultMetalTheme 的getter方法,以便使用在外部资源文件中定义的颜色和字体。
清单3显示了用于Black on White Look and Feel, Large Fonts的外部资源文件。这个look and feel所使用的字体大小被设置为30(第5行),图标被放大到初始大小的250%(第3行),基本颜色则被设置为黑色和白色(第6行到第15行)。
清单 3. HighContrastLAF.properties:白背景,黑文本
2 2. description = Black on white, large fonts
3 3. iconMagnificationFactor = 2.5
4 4. fontName = Dialog
5 5. fontSize = 30
6 6. backgroundColor = FFFFFF
7 7. foregroundColor = 000000
8 8. primaryColor1 = 000000
9 9. primaryColor2 = FFFFFF
10 10. primaryColor3 = FFFFFF
11 11. secondaryColor1 = 000000
12 12. secondaryColor2 = C0C0C0
13 13. secondaryColor3 = FFFFFF
14 14. selectionForeground = FFFFFF
15 15. selectionBackground = 000000
16
图 4 显示了这个look and feel的屏幕截图。
图 4.白背景,黑文本

通过与之相同的方法,您可以创建一个带有白文本、黑背景、普通大小字体的高对比度的look and feel,您只需提供如清单4所示的另一个资源文件。
清单 4. HighContrastLAF.properties:黑背景,白文本
图 5 显示了清单4的结果。
图 5. 黑背景,白文本
在您的Java应用程序中使用新的look and feel
通过在 main 方法中第一个窗口小部件创建之前调用下面一行代码,您便可以在任何Swing应用程序中使用新创建的look and feel:
2
理想情况下, 您的产品应该能够提供一个对话框或者偏好选择页面,以便用户能够选择他们中意的look and feel。但是,如果您不想在这方面花那么大的力气,那么一种更轻松的解决方案是使用系统属性来允许用户在启动该程序的命令行中指定look and feel,如清单5所示。
清单 5. 从系统属性初始化LAF
2 if (plaf!=null) try {
3 UIManager.setLookAndFeel(plaf);
4 } catch (Exception e) { ("Error loading PLAF "+plaf+":"+e);}
5
当用户启动应用程序时,他们可以通过在命令行中使用以下语法来选择一个特定的look and feel:
2 CLASSPATH MAINCLASS ARGUMENTS
3
结束语
在本文中,我们展示了定制跨平台的Metal look and feel是件多么容易的事情。正如我们所讨论的,这件事可以通过继承 MetalLookAndFeel 和 DefaultMetalThemes 类来完成。我们还展示了如何轻松地通过使用矢量图标来对图标进行放大。您可以在您的项目中直接使用本文提供的源代码,或者适当地作些修改,以便为弱视用户提供更多的定制空间。
下载本文的 完整的源代码。