本质上说,可插拔LAF的设计意味着实现组件展现(Look)和事件处理(Feel)的部分是被代理到独立的UI对象中的,这些UI对象是由当前的LAF提供的,它们可以被动态地修改。
这些可插拔LAF的API包括:
- Swing组件(component)类中的钩子
- LAF管理的顶层API
- 独立的包中实际实现LAF的接口
组件钩子
拥有有LAF特征的Swing组件在javax.swing.plaf包都有一个抽象类来代表它的UI
Delegate,这些UI类的命名规则是该组件类去掉前缀J,加上后缀UI。比如JButton的UI
Delegate类的名字是ButtonUI。
UI
Delegate在组件的构造函数中创建,并以组件的限定JavaBean属性的形式访问,比如JScrollBar提供了下面的方法访问它的UI
Delegate:
public ScrollBarUI
getUI()
public void setUI(ScrollBarUI
ui)
组件创建并设置UI
Delegate的过程实际上是“安装”组件LAF的过程。每个组件同时提供方法创建和设置“缺省”LAF的UI
Delegate,组件的构造函数在安装UI时使用该方法。
public void
updateUI()
LAF实现为每个抽象UI类提供了具体子类,比如,Windows LAF定义了WindowsButtonUI,
WindowsScrollBar等。当组件安装它的UI
Delegate时,须有办法动态查找到当前缺省LAF的具体实现类名。该操作使用了一个hash表,它的主键是由组件的getUIClassID()方法获得,习惯是使用平台抽象类名,比如JScrollBar的getUIClassID()是这样定义的:
public
String getUIClassID() {
return
"ScrollBarUI";
}
相应在此hash表中,Windows
LAF将他ScrollBarUI映射成JScrollBar在Windows平台的UI实现类:
com.sun.java.swing.plaf.windows.WindowsScrollBarUI
LAF管理
Swing定义了一个抽象类LookAndFeel来表达所有LAF实现的核心信息,比如LAF的名字、描述、是否是本地化LAF,以及一个Hash表(也称作“Defaults
Table”)来存储各种各样LAF属性的缺省值,比如颜色和字体。每种LAF实现都定义一个LookAndFeel的子类,如swing.plaf.motif.MotifLookAndFeel,来为Swing提供管理LAF的必须信息。
UIManager是组件和程序访问LAF信息的API(尽量不要直接访问LookAndFeel实例)。
UIManager负责跟踪当前有哪些LookAndFeel类可用,哪些安装了,谁是缺省的。UIManager还管理对于当前LAF的Defaults
Table的访问。
“缺省”LAF
UIManager还提供了设置和获取当前缺省LookAndFeel的方法:
public static LookAndFeel
getLookAndFeel()
public static void
setLookAndFeel(LookAndFeel
newLookAndFeel)
public static void setLookAndFeel(String
className)
Swing初始化了一个跨平台Java外观(以前称作“Metal”)作为缺省的LAF。然而,当Swing程序想明确设置缺省LAF时,可使用UIManager.setLookAndFeel()方法,比如下面的代码将缺省的LAF设置成CDE/Motif:
UIManager.setLookAndFeel("com.sun.java.swing.plaf.motif.MotifLookAndFeel");
有时应用程序并不想指定特定的LAF,而想动态匹配当前平台类型的LAF,比如运行在Windows
NT上就使用Windows
LAF,运行在Solaris上就使用CDE/Motif,或者程序想设置成为跨平台的Java
LAF,UIManager为这些情况提供了下面的静态方法,动态地获取合适LookAndFeel的类名:
public static String
getSystemLookAndFeelClassName()
public static
String
getCrossPlatformLookAndFeelClassName()
为确保程序总是使用系统LAF,可使用下面的代码:
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
动态调整缺省的LAF
Swing程序使用上面方法动态地设置LAF时,理想位置是在任何Swing组件初始化以前,因为UIManager.setLookAndFeel()方法通过装载并初始化LookAndFeel实例来使它成为当前缺省的LAF,但是它不能自动使当前所有的组件改变它们的外观。记住组件在构造函数里初始化它们的UI
Delegate对象,如果当前缺省LAF在它们构造函数完毕之后发生变化,它们不会相应地自动更新,需程序遍历组件树更新组件来实现外观切换,Swing在SwingUtilities中提供了updateComponentTreeUI()方法来帮程序员完成此过程。
组件外观可在任何时候通过调用它的updateUI()方法来切换到当前缺省的LAF,该方法调用如下UIManager的静态方法来获得合适的UI
Delegate 对象:
public static ComponentUI
getUI(JComponent c)
比如JScrollBar的updateUI()实现代码如下:
public
void updateUI() {
setUI((ScrollBarUI)UIManager.getUI(this));
}
如果程序在初始化后需要改变它的GUI树的外观,可使用下面的代码:
//
GUI已经初始化了,假设myframe变量是最顶层的frame
try
{
UIManager.setLookAndFeel("com.sun.java.swing.plaf.motif.MotifLookAndFeel");
myframe.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
SwingUtilities.updateComponentTreeUI(myframe);
myframe.validate();
} catch
(UnsupportedLookAndFeelException e) {
} finally
{
myframe.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
管理LAF数据
UIManager定义了一个静态类UIManager.LookAndFeelInfo来存储LAF的名称(如“Metal”)及LAF类名如
com.sun.java.swing.plaf.MetalLookAndFeel,它在内部使用这些名字来管理LookAndFeel对象,这些信息能通过UIManager下面的静态方法访问获得:
public static LookAndFeelInfo[]
getInstalledLookAndFeels()
public static void
setInstalledLookAndFeels(LookAndFeelInfo[]
infos)
throws SecurityException
public static void
installLookAndFeel(LookAndFeelInfo
info)
public static void
installLookAndFeel(String name, String
className)
这些方法可在编程中决定哪个LAF实现可用,当创建允许用户动态切换皮肤的界面时很有用。
LAF包
javax.swing.plaf包中的UI
Delegate类(ButtonUI,ScrollBarUI等)定义了组件可以用来访问UI
Delegate对象的接口,这些类在早期Swing实现中使用的是接口,后来替换成了抽象类。这些平台API接口是所有LAF实现的基类。
每个LAF实现提供这些抽象类的具体类,LAF实现定义的这些类是分别放在javax.swing.plaf包的子包中,如javax.swing.plaf.motif、javax.swing.plaf.metal等。LAF包中包含下面的文件:
- LookAndFeel的子类,比如MetalLookAndFeel
- 所有LAF的UI
delegate类,比如MetalButtonUI、MetalTreeUI等。
- 所有LAF的工具类,比如MetalGraphicsUtils、MetalIconFactory等。
- 其他和LAF相关的资源,比如图像文件等。
在实现各种Swing LAF中,有许多共性的东西,这些共同的代码被重构成一个基本LAF实现,称作basic
LAF,各平台包括motif、windows等的LAF又继承basic LAF,basic
LAF包支持桌面级别的LAF,比如Windows和CDE/Motif。Basic
LAF包只是实现可插拔LAF的一个例子,Swing的LAF框架足够灵活,足够容纳其他的实现。许多第三方的LAF是基于basic
LAF,当然更多的是基于metal、motif或者windows这些更为具体的LAF实现的。