20.2 创建新的观感
除非公司要求我们自定义所有的内容来提供唯一的体验,通常并不需要从头创建一个完整的新观感。通常,开发者通过提供一些自定义的UI委托来对现在的观感进行小量的修改。然而如果我们确实希望创建一个新的观感类,我们只需要创建一个LookAndFeel类的子类。我们仍然必须提供UI委托,但是现在这些类可以由Swing组件隐藏,因为他们的使用将并不会直接为javax.swing组件类所知。
20.2.1 在非Windows机器上使用WindowsLookAndFeel
为了演示新的观感类的创建,让我们创建一个封装了Windows UI委托的平台需求的观感实现。通过简单的重写public boolean isSupportedLookAndFeel()方法来返回true,我们就可以为Windows观感类移除平台需求。
注意,Java许可证禁止发布移除了Windows观感类平台需求的程序。所以只要我们不发布他,就可以使用这个这里的示例。
列表20-9中的类定义显示了创建一个新的观感实现是多么的简单。
package swingstudy.ch20; import com.sun.java.swing.plaf.windows.WindowsLookAndFeel; public class MyWindows extends WindowsLookAndFeel { public String getID() { return "MyWindows"; } public String getName() { return "MyWindows Look and Feel"; } public String getDescription() { return "The MyWindows Look and Feel"; } public boolean isNativeLookAndFeel() { return false; } public boolean isSupportedLookAndFeel() { return true; } }
如果我们在一个非Windows机器上使用这个Swing类,我们可以使得观感成为Windows观感。只需要将我们的观感设置为MyWindows并且使得观感类文件可用。类文件只需要在我们的CLASSPATH中可用并且使用下面的命令行启动:
java -Dswing.defaultlaf=MyWindows ClassFile
为了使得Windows观感变化可以正常工作,我们需要在MyWindows目录结构的icons子目录中提供观感所用的图标文件。表20-9列出了与预定义的观感类型相对应的图标。MyWindows观感需要所有的Windows图像文件。
注意,尽管Ocean只是Metal的主题,他却提供了他自己的图像集合。
注意,至少JOptionPane消息类型的图像在所有的观感中都是需要的。通常他们名为Error.gif,Inform.gif,Question.gif与Warn.gif,尽管并不是绝对需要。
如果我们不希望跨过Widnows观感的“本地”需求,我们可以安装单独的UI委托,例如下面的代码将会为JButton组件使用Windows UI委托:
UIManager.put("ButtonUI","com.sun.java.swing.plaf.windows.WindowsButtonUI")
20.2.2 添加UI委托
创建一个具有自定义UI委托的新观感需要创建一个LookAndFeel类的子类。更可能的情况是,我们将会创建一个BasicLookAndFeel类或是另一个预定义观感类的子类,然后为其中的一些组件提供我们的委托。
如果我们继承BasicLookAndFeel类,则他会有一个public void initClassDefaults(UIDefaults table)方法,可以重写这个方法来安装我们自己的委托。只需要将委托放在观感的UIDefaults表中,而不是放在希望使用这个新委托的我们程序中。
列表20-10中的MetalLookAndFeel的扩展将前面定义的MyComboBoxUI委托作为ComboBoxUI委托添加到观感中。随着我们定义更多的自定义组件,我们可以使用相似的方法进行添加。
package swingstudy.ch20; import javax.swing.UIDefaults; import javax.swing.plaf.metal.MetalLookAndFeel; public class MyMetal extends MetalLookAndFeel { public String getID() { return "MyMetal"; } public String getName() { return "MyMetal Look and Feel"; } public String getDescription() { return "The MyMetal Look and Feel"; } public boolean isNativeLookAndFeel() { return false; } public boolean isSupportedLookAndFeel() { return true; } protected void initClassDefaults(UIDefaults table) { super.initClassDefaults(table); table.put("ComboBoxUI", "MyComboBoxUI"); } }
20.3 使用Metal主题
Metal观感类(javax.swing.plaf.metal.MetalLookAndFeel)提供了定义颜色,字体以及由UIManager管理的所有的UIDefaults默认设置的方法。通过允许用户修改主题,他们就可以通过开发者的最少工作来获得所喜欢的颜色或是字体尺寸。通过开发合适的主题,我们无需创建新的观感类就可以很容易的定制接口或是手动将新的设置插入到UIDefaults中。
20.3.1 MetalTheme类
表20-10列出了通过MetalTheme类可用的49个不同的属性。各种primary与secondary属性是抽象并且必须在子类中实现。在其他的属性中,以Font结尾的六个属性-controlTextFont,menuTextFont,subTextFont,systemTextFont,userTextFont与windowTextFont-也是抽象并且必须在子类中实现。其余的属性,在默认情况下,重用11个primary/secondary值中的一个用于他们的设置。
20.3.2 DefaultMetalTheme与OceanTheme类
与类名相反,DefaultMetalTheme类并不是默认的Metal主题;默认主题为OceanTheme。DefaultMetalTheme称其自身为Steel主题并且分别使用蓝色与灰色用于primary与secondary设置。OceanTheme,称之为Ocean,对于背景使用浅蓝色调色板。
要使用Steel主题而不是Ocean主题,我们需要将swing.metalTheme系统属性设置为steel,如下所示:
java –Dswing.metalTheme=steel ClassName
大多数人更喜欢Ocean的新外观,但是为了兼容仍然可以使用Steel。
如果我们创建我们自己的Metal主题,我们需要继承OceanTheme或是DefaultMetalTheme,然后通过设置MetalLookAndFeel类的表述currentTheme属性将自定义主题安装为我们的主题。
MetalTheme myTheme = new MyTheme(); MetalLookAndFeel.setCurrentTheme(myTheme);
由于MetalTheme的大多数定制都是与字体和颜色相关的,public void addCustomEntriesToTable(UIDefaults table)方法允许我们重写Metal观感的默认UIDefaults设置。所以,不仅主题自定义Swing组件的字体与颜色,而他们还可以自定义Swing组件中许多UIResource相关的属性。
下面的代码演示了如何为特定的主题设置两个滚动条设置。记住,当合适的时候要使用UIResource标记这些设置,并且不要忘记使用我们超类实现来初始化table参数。
public void addCustomEntriesToTable(UIDefaults table) { super.addCustomEntriesToTable(table); ColorUIResource thumbColor = new ColorUIResource(Color.MAGENTA); table.put("Scrollbar.thumb", thumbColor); table.put("ScrollBar.width", new Integer(25)); }
MetalWorks系统demo是由JDK安装随着自定义主题的例子而提供的。他所定义的主题由一个属性文件读取主题颜色设置。无需每次我们要修改我们程序主题的时候创建一个新的类文件,我们可以在运行时由文件读取。
name=Charcoal primary1=33,66,66 primary2=66,99,99 primary3=99,99,99 secondary1=0,0,0 secondary2=51,51,51 secondary3=102,102,102 black=255,255,255 white=0,0,0
图20-8显示了Metalworks演示程序中所用的Charcoal主题。图20-9显示了他所定义的Presentation主题。
20.4 使用Auxiliary观感
Swing提供了多个观感,可以通过MutliLookAndFeel或是通过swing.properties文件中swing.plaf.multiplexingplaf属性指定在任意时刻激活。当安装了多个观感类时,只有一个观感会可见并在屏幕上绘制。其余的版本被称之为auxiliary观感并且与可访问选项相关联,例如屏幕读取器。另一个辅助观感就是日志记录器,他会记录那些与日志文件交互的组件。
Auxiliary观感类是通过使用swing.properties文件中的swing.auxiliarylaf属性使用运行环境注册的。如果指定了多个类,则各项需要通过逗号进行分隔。除了使用属性文件,我们可以通过调用UIManager的public static void addAuxiliaryLookAndFeel(LookAndFeel lookAndFeel)方法在程序中安装观感。一旦安装,多元观感就会为所有已安装的观感类自动创建并管理UI委托。
要确定安装了哪一个辅助观感类,我们可以通过UIManager的public static LookAndFeel[] getAuxiliaryLookAndFeels()方法进行查询。这会返回实际LookAndFeel对象的数组,与通过getInstalledLookAndFeels()方法返回的UIManager.LookAndFeelInfo数组不同。
20.5 SynthLookAndFeel类
Synth观感是一个完全丰满的观感,并不是Metal,Windows或Motif的主题扩展。尽管此观感并不使用UIResource表,该类由一个空的绘图开始并且由一个XML文件中读取完整的定义。
20.5.1 配置Synth
Synth观感的配置类似如下的样子:
SynthLookAndFeel synth = new SynthLookAndFeel(); Class aClass = SynthSample.class; InputStream is = aClass.getResourceAsStream("config.xml"); synth.load(is, aClass); UIManager.setLookAndFeel(synth);
那么配置文件config.xml中的内容到底是什么呢?在我们的配置文件中,我们指定了我们希望在我们的程序中所用的特定组件如何显示。这通常称之为skining我们的程序,或是创建一个自定义的皮肤。通过简单的修改XML文件,我们程序的整个外观就会发生变化;并不需要编程实现。
DTD文件可以由 http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/plaf/synth/doc-files/synth.dtd获取。文件格式在http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/plaf/synth/doc-files/synthFileFormat.html进行完全描述。解析器并不验证,在有工具帮助自动化过程之前,我们需要小心处理XML文件的创建。
在Synth中有许多可用的配置选项,但是基本的XML概念用来定义style并将其bind到组件。<style>标签可以允许我们自定义诸如字体,颜色,insets以及背景或是边框图片的属性。
为了进行演示,列表20-11定义了一个名为button的自定义。通常,文本会以粗体24点Monospaced字体显示。当鼠标移动到关联的组件上时,字体变为48点斜体SansSerif字体并且背景颜色变为蓝色。当组件被按下时,字体会再次发生变化,这次变为36点粗斜体Serif字体,并且背景颜色变为红色。配置文件的最后部分是将名为button的风格关联到JButton控件,通过<bind>标签。
<?xml version="1.0" encoding="UTF-8"?> <synth> <style id="button"> <font name="Monospaced" size="24" style="BOLD"/> <state value="MOUSE_OVER"> <font name="SansSerif" size="48" style="ITALIC"/> <opaque value="TRUE"/> <color value="BLUE" type="BACKGROUND"/> </state> <state value="PRESSED"> <font name="Serif" size="36" style="BOLD AND ITALIC"/> <opaque value="TRUE"/> <color value="RED" type="BACKGROUND"/> </state> </style> <bind style="button" type="region" key="Button"/> </synth>
图20-10显示了屏幕上组件的样子。
列表20-12包含了生成图20-10的完整示例代码。
package swingstudy.ch20; import java.awt.BorderLayout; import java.awt.EventQueue; import java.io.InputStream; import java.text.ParseException; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.UIManager; import javax.swing.plaf.synth.SynthLookAndFeel; public class SynthSample { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Runnable runner = new Runnable() { public void run() { SynthLookAndFeel synth = new SynthLookAndFeel(); try { Class aClass = SynthSample.class; InputStream is = aClass.getResourceAsStream("config.xml"); if(is == null) { System.err.println("Unable to find theme configuration InputStream"); System.exit(-1); } synth.load(is, aClass); } catch(ParseException e) { System.err.println("Unable to load theme configuration"); System.exit(-2); } try { UIManager.setLookAndFeel(synth); } catch(javax.swing.UnsupportedLookAndFeelException e) { System.err.println("Unable to change look and feel"); System.exit(-3); } JFrame frame = new JFrame("Synth Sample"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JButton button = new JButton("Hello, Synth"); frame.add(button, BorderLayout.CENTER); frame.setSize(300, 200); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
20.5.2 默认Synth属性
配置文件不仅仅是风格与绑定。事实上,如果我们确实希望设置一个UIResource属性,我们仍然可以,但是只可以用一个有限的子集。位于http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/plaf/synth/doc-files/componentProperties.html 的文件描述了完整的集合。这个集合要大大的小于我们将会在本书附录中看到了列表。这些设置是通过<property>标签或是<defaultProperty>标签进行配置的。默认属性是UIDefaults表中用于观感的默认属性;而属性仅是特定控件的设置。类似于下面的标签将会位于用于配置的特定<style>中:
<property key="ScrollPane.viewportBorderInsets" type="insets" value="5 5 5 5"/>
或是
<object class="javax.swing.plaf.ColorUIResource" id="color"> <int>255</int> <int>0</int> <int>0</int> </object> <defaultsProperty key="Table.focusCellForeground" type="idref" value="color"/>
20.5.3 使用Synth图像
间接的,使用SynthPainter类,我们可以提供用于组件周围边框所用的图像。然而,图像必须是特殊创建的,因而绘图器不仅是绘制图像。That one image must be used for components of all sizes, of that type, such as for all text fields。绘图器通过将组件分为多个区域在不同尺寸的控件中共享图像。四个角通常按其本来尺寸进行绘制。中间的区域会进行水平或垂直扩展。
图20-11显示了一个可用的边框图像。注意四个角的曲线,但是中间区域并没有曲线。曲线会在边框扩展时保持不变。
要在我们的配置文件中使用图20-11中的图像,我们需要使用<imagePainter>标签并且指定一个相应的method属性。这直接映射到SynthPainter类的paintXXX(SynthContext context, Graphics g, int x, int y, int w, int h, int orientation)方法。因为将会使用这个图像用于绘制文本域边框,method属性应该设置为textFieldBorder。我们同时需要指定图像的path(其文件名以及相对于SynthLookAndFeel.load()方法第二个参数指定的位置的路径)。insets与颜色是我们需要指定的其他项。下面是该使用的完整style定义。
<style id="textfield"> <opaque value="true"/> <state> <color value="#C2E2CF" type="BACKGROUND"/> <color value="#000000" type="TEXT_FOREGROUND"/> </state> <imagePainter method="textFieldBorder" path="text.png" sourceInsets="3 3 3 3" paintCenter="false"/> <insets top="3" left="3" bottom="3" right="3"/> </style> <bind style="textfield" type="region" key="TextField"/>
向列表20-2中的程序添加JTextField会生成类似于图20-12所示的屏幕结果。当我们调整屏幕尺寸时,文本域的边框会变化。从技术上来说,类似的定义同样可以用于JTextArea,因为在这里所用的图像并没有高度限制。
20.6 小结
在本章中,我们探讨了Swing组件的可插拨的观感体系结构。因为Swing组件的所有方面都是使用Java语言编写的,如果我们不喜欢组件的某一方面,我们就可以进行简单的修改。而修改正是本章向我们展示的内容。
首先,我们了解了如何查询预安装的LookAndFeel类以及如何修改我们当前的观感。接下来,我们了解了如何通过UIManager修改其UIDefaults来自定义当前观感。我们了解了这些默认设置如保实现了UIResource接口,从而当观感类变化时设置发生变化。另外,我们了解了为了更好的资源使用这些资源如何实现UIDefaults.LazyValue与UIDefaults.ActiveValue。而且我们了解了客户端属性如何由API视图隐藏,但是仍然可以用于自定义一个组件的观感。
为了自定义各种组件的观感,我们探讨了创建新的UI委托以及新的观感类,其中的一些是非可见的或是辅助的。我们同时了解了Metal观感如何通过其主题的使用包含特定的行为。最后,我们探讨了自定义Synth观感。
在第21章中,我们将会了解Swing undo框架,他可以用来设计撤销与重做操作。