5.6 可插拔式外观样式

Swing相对于AWT的一个重要优势是Swing允许开发人员定制其组件的外观样式,而AWT只能使用底层操作系统提供的组件外观样式。Swing的这个特性为开发人员在美化程序外观样式时提供了足够的灵活性。Swing所提供的这个特性被称为可插拔式外观样式(pluggable look-and-feel, PLAF)。Swing也默认提供了一些外观样式供开发人员选择,这其中包括默认使用的“metal”样式和Java 7中新增的“nimbus”样式。除了这些在不同平台上相同的外观样式之外,还包括不同平台上特有的外观样式。程序外观样式的管理由javax.swing.UIManager类来完成。通过UIManager类可以为程序切换不同的外观样式。

代码清单5-13给出了一个切换外观样式的示例。在示例中,通过UIManager类的getInstalledLookAndFeels方法可以获取当前Java平台上所有可用的外观样式的信息。每种信息由UIManager.LookAndFeelInfo类的对象来表示。通过UIManager类的setLookAndFeel方法可以设置所要使用的外观样式。设置时的参数既可以是一个已有的javax.swing.LookAndFeel类的对象,也可以是外观样式实现类的名称。这里使用的是类的名称。在完成设置之后,需要通过SwingUtilities类的updateComponentTreeUI方法来通知界面上的所有组件更新自己的外观样式。如果不使用这个方法,可能会出现界面上组件的外观样式不一致的情况。在示例中,每当通过下拉列表选择一个外观样式之后,整个程序的外观样式会随之发生变化。

代码清单5-13 切换Swing界面的外观样式的示例


public void selectPlaf(){

final JFrame frame=new JFrame();

UIManager.LookAndFeelInfo[]lafs=UIManager.getInstalledLookAndFeels();

JComboBox combo=new JComboBox(lafs);

combo.addItemListener(new ItemListener(){

public void itemStateChanged(ItemEvent e){

if(ItemEvent.SELECTED==e.getStateChange()){

UIManager.LookAndFeelInfo info=(UIManager.LookAndFeelInfo)e.getItem();

try{

UIManager.setLookAndFeel(info.getClassName());

SwingUtilities.updateComponentTreeUI(frame);

}catch(Exception ex){

}

}

}

});

frame.add(combo, BorderLayout.NORTH);

frame.add(new JButton("按钮"),BorderLayout.SOUTH);

frame.setSize(400,300);

frame.setVisible(true);

}


如果希望基于Swing开发的程序的界面像AWT一样在不同的操作系统平台上使用当前平台的默认外观样式,可以通过UIManager类的getSystemLookAndFeelClassName方法来得到当前平台的默认外观样式的名称,再通过setLookAndFeel方法进行设置即可。

Swing为了实现可插拔的外观样式,采用了一种用户界面代理(UI delegate)的设计思路。当一个Swing用户界面组件在进行绘制时,实际的绘制工作是代理给另外的一个对象来完成的。通过这种职责分离方式,使得同一个组件的外观样式可以根据用户界面代理的不同而发生变化。Swing中所有的用户界面代理类都继承自javax.swing.plaf.ComponentUI类。每个组件都有其对应的用户界面代理类来负责绘制其外观,比如与javax.swing.JButton类对应的代理类是javax.swing.plaf.ButtonUI类。有两种做法可以把一个组件对象和一个用户界面代理对象关联起来:一种是通过组件对象的setUI方法,另外一种是通过用户界面代理对象的installUI方法。前面介绍JLayer和LayerUI类时就提到了这两种做法。从这里可以看出,每个样式外观其实是一组用户界面代理类的集合。这些用户界面代理类分别为Swing中的不同组件提供了对应的外观样式。

如果使用setUI方法来设置组件对应的代理对象,就要求对每个组件的对象实例进行设置。如果需要对某一类型组件的所有对象实例都应用某种外观样式,更好的选择是使用自定义的外观样式实现。实现自定义的外观样式很简单,只需要继承自LookAndFeel类并实现相应的方法,然后再通过UIManager类来进行设置即可。如果只是修改少数组件的外观,那么更好的选择是继承已有的javax.swing.plaf.basic.BasicLookAndFeel类。BasicLookAndFeel类可以作为实现自定义的外观样式的基础。比如,希望把界面上所有的标签都改成黑底白字的显示方式,可以继承自javax.swing.plaf.basic.BasicLabelUI类,并提供自己的绘制逻辑,如代码清单5-14所示。

代码清单5-14 自定义标签的外观样式的示例


public class MyLabelUI extends BasicLabelUI{

public static ComponentUI createUI(JComponent c){

return new MyLabelUI();

}

public void paint(Graphics g, JComponent c){

g.setColor(Color.BLACK);

g.fillRect(0,0,c.getWidth(),c.getHeight());

g.setColor(Color.WHITE);

JLabel label=(JLabel)c;

g.drawString(label.getText(),0,label.getHeight()/2);

}

}


通过覆写用户界面代理类的paint方法可以控制组件的外观样式。从paint方法的JComponent类型的参数可以获取与用户界面代理对象相关联的组件,从而获得所需的信息。代码清单5-14获取了标签的文本。需要注意的是,自定义的界面代理类都需要覆写createUI这个静态方法以创建出正确的界面代理对象。下一步是创建程序自己的LookAndFeel类,如代码清单5-15所示。程序自己的MyLookAndFeel类选择继承BasicLookAndFeel类以减少实现的工作量。受限于篇幅,代码清单5-15中省略了MyLookAndFeel类的一些方法,这些方法都只是用来提供自定义外观样式的元数据,作用并不大。这里只列出了比较重要的initClassDefaults方法。在这个方法中,通过修改参数中的javax.swing.UIDefaults类的对象把标签组件对应的用户界面代理的Java类名设成了MyLabelUI类。经过这样的设置,在创建标签组件时,MyLabelUI类的对象会作为默认的界面代理对象,从而成功地应用自定义的外观样式。如果还需要修改其他类型组件的外观样式,只要按照相同的方式进行设置即可。

代码清单5-15 启用自定义外观样式的示例


public class MyLookAndFeel extends BasicLookAndFeel{

public void initClassDefaults(UIDefaults table){

super.initClassDefaults(table);

table.putDefaults(new Object[]{"LabelUI","com.java7book.chapter5.plaf.mylaf.MyLabelUI"});

}

}


在程序中,只需要在最开始的时候通过UIManager类设置使用MyLookAndFeel类作为外观样式即可。