2008年9月9日星期二

A Swing Architecture Overview(三)

可拔插的感官架构(Pluggable look-and-feel architecture)
Swing的可拔插感官架构允许我们可以使用单一的组件API,而不用指定特定的感官。Swing的工具包里面提供了一套缺省的感官,API是“开放”的,这样的设计既可以让开发人员继承已有的感官来创建新的感官也可以从头开始创建一套新的感官。虽然可拔插感官API架构具有可扩展性,但它被有意设计在基本组件API的下层,这样使得开发人员开发swing GUIs的时候不需要明白它的所有细节。(如果你想知道的话,继续往下读。。。)

我们不期望(或建议)大多数的开发人员都创建新的感官实现,我们认识到可拔插感官特性为希望创建具有一致性的系列应用提供了有力的支持。可拔插感官还为创建具有为残障人士提供可达性的GUIs提供完美的支持。

综上所述,可拔插感官设计简单的来说就是组件的表现(外观)和事件处理(感觉)部分的实现被当前安装的可以在运行时随时被更换的感官的独立的UI对象来代理。

可拔插感官的API(The pluggable look-and-feel API)
可拔插感官API包括:
  • Swing 组件类的一些小钩子。
  • 一些感官管理的顶层API。
  • 在独立的包中真正实现感官的较复杂的API。
组件钩子(The component hooks)
每个具有感官具体行为的swing组件在swing.plaf包中都有一个抽象类表示它的UI代理。命名规则是组件的名称除去前缀“J”,加上后缀“UI”。比如, JButton的plaf类UI代理定义为ButtonUI。

UI代理在组件的构造器里面创建,可以像JavaBeans属性绑定一样访问。比如, JScrollBar提供了下面的方法来访问它的UI代理:
public ScrollBarUI getUI()

public void setUI(ScrollBarUI ui)
创建和像“UI”属性一样设置一个组件的UI代理的过程取决于安装的组件的感官。

每个组件还提供了一个方法来为缺省的感官创建和设置UI代理(这个方法在安装的时候会被构造器调用)
public void updateUI()
感官的实现为每个抽象plaf UI类提供了具体的子类。比如, windows感官定义了WindowsButtonUI和WindowsScrollBarUI等。当一个组件安装它的UI代理的时候,它必须想办法动态的为当前感官找到相应的具体类名。这个操作具体是通过使用key 自动定义在组件的getUIClassID ()方法里的hash 表。
public String getUIClassID() {
return "ScrollBarUI";
}
所以,windows感官中的hash表提供了一个值映射“ScrollBarUI”到
“com.sun.java.swing.plaf.windows.WindowsScrollBarUI”

感官管理(Look-and-feel management)
Swing定义了一个LookAndFeel抽象类来描述以感官实现为中心的所有信息,比如它的名称,它的描述,是否为本地感官等,在一张hash 表里面(就是所谓的“缺省值表”)来存储各种各样的感官属性缺省值,如颜色和字体等。

每个感官实现都定义了一个LookAndFeel子类(例如swing.plaf.motif.MotifLookAndFeel)来为swing对感官的管理提供必要的信息。

组件和程序通过UIManager API来访问感官信息(很少,几乎没有直接和LookAndFeel实例打交道的)。UIManager负责跟踪哪些LookAndFeel类被提供了,哪些被安装了,哪个是当前缺省使用的。UIManager还管理对当前的感官的“DefaultTable”的访问。

缺省感官(The 'default' look and feel)
UIManager还提供方法来getting和setting当前的感官:
public static LookAndFeel getLookAndFeel()

public static void setLookAndFeel(LookAndFeel newLookAndFeel)

public static void setLookAndFeel(String className)
缺省情况下,Swing初始化了一套跨平台的java感官(以前被称为“Metal”),如果swing程序希望显式的设置缺省感官,可以使用UIManager.setLookAndFeel()方法。例如,下面的代码样例将会设置缺省感官为CDE/Motif:感官。
UIManager.setLookAndFeel("com.sun.java.swing.plaf.motif.MotifLookAndFeel");
有时候一个应用可能不希望指定特定的感官,而是希望用这种方式来配置无论它运行在哪个平台上面都动态匹配该平台的感官(比如当它运行在Windows NT平台上,它会使用Windows感官,而在Solaris 平台上面使用CDE/Motif 感官)。或者说,应用软件可能希望使用跨平台的java 感官。

针对所有的这些情况,UIManager提供了下面的静态方法来自动获取合适的LookAndFeel类名。
public static String getSystemLookAndFeelClassName()

public static String getCrossPlatformLookAndFeelClassName()
所以,为了保证程序总是使用系统平台的感官,代码可能是这样的:
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
动态改变缺省感官(Dynamically Changing the Default Look-and-Feel)
当swing应用程序动态设置感官(像上面所描述的那样),这样做的最好地方是在所有swing组件实例化之前。这是因为UIManager.setLookAndFeel()方法通过加载和初始化特定的LookAndFeel实例为当前缺省感官,但是它不会自动使得任何已经存在的组件改变它们的感官。

记住组件初始化它们的UI代理构造的时间,如果它们构造之后当前缺省感官发生了变化,它们将不会自动更新它们的UIs,它们必须通过编码实现动态的转换来对组件更新。(注意:swing提供了SwingUtilities.updateComponentTreeUI()方法来支持这个处理)。

组件的感官可以在任何时候通过使用它的updateUI()方法来更新为当前的缺省感官,它使用UIManager的如下的静态方法来获得合适的UI代理:
public static ComponentUI getUI(JComponent c)
比如,ScrollBar的updateUI()方法的实现如下:
public void updateUI() {
setUI((ScrollBarUI)UIManager.getUI(this));
}
如果一个程序在它实例化之后需要改变它的GUI层次,代码可能如下:
// GUI already instantiated, where myframe
// is top-level 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));
}
管理感官数据(Managing look-and-feel data)
UIManager定义了一个静态类,叫做UIManager.LookAndFeelInfo,用来保存LookAndFeel高级别名称(如“Metal”)和特定的类名(如“com.sun.java.swing.plaf.MetalLookAndFeel ”)。它内部使用这些类来管理它所知道的对象。这个信息能通过下面的静态方法访问:
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)
这些方法可以用来自动获知哪些感官实现被提供,这在创建用户接口的时候非常有用,在用户动态选择一套感官的时候可以得知哪些是允许被选择的。

感官包结构(The look-and-feel packages)
swing.plaf提供的UI 代理类(ButtonUI, ScrollBarUI等)定义了用于和UI 代理实例交互的组件的精确API。(注意:原来这个地方用的是接口,但是后来被抽象类所代替,因为我们觉得那些API接口对于具体转形来说不够成熟)。这些plaf APIs是所有感官实现的根。

每个感官实现提供了这些plat类的具体子类实现。所有这些在特定的感官实现中定义的类都放在swing.plaf包下面的一个单独的包里面(比如, swing.plaf.motif, swing.plaf.metal等等)。一个感官包包括下面内容:

  • LookAndFeel子类(例如, MetalLookAndFeel)。
  • 所有的感官代理类(例如, MetalButtonUI,MetalTreeUI 等)
  • 所有的感官工具类( MetalGraphicsUtils,MetalIconFactory 等)。
  • 与感官相关的资源,如图标文件等。
在实现各种各样的swing感官的时候,我们很快发现它们之间有很多共同的地方。我们把这些共同的代码放到一个基本的感官实现(叫做“basic”)中,它继承了plaf抽象类。所有的具体感官实现(motif, windows等)都从它继承。basic感官包支持创建桌面级的感官,比如Windows或CDE/Motif。basic感官包只是一个如何创建可拔插感官的样例,架构足够的灵活,也可以支持其它方案。

本文的余下部分将会展示感官包在一般级别下如何工作的, basic包的细节留在将来的一篇文档中描述。

警告:包下面的所有的APIs在swing1.0.X版本中都没有被冻结,我们现在清除了JDK1.2beta4所带的swing APIs,从那时起它们被冻结。所以如果你使用1.0.1 API来开发你自己的感官实现,这将会对你造成影响。

感官子类(The LookAndFeel Subclass)
LookAndFeel类定义下面的抽象方法,所有的子类都必须实现它:
public String getName();

public String getID();

public String getDescription();

public boolean isNativeLookAndFeel();

public boolean isSupportedLookAndFeel();
getName(),getID(),和getDescription()方法提供了感官的一般信息:

如果是当前平台的本地感官, isNativeLookAndFeel()方法返回true。例如,如果系统当前运行在Solaris平台,MotifLookAndFeel返回true,否则返回false。

isSupportedLookAndFeel()方法返回该感官是否授权在当前平台上运行。比如, WindowsLookAndFeel只当它运行在Windows 95,Windows 98或Windows NT机器上的时候才返回true。

LookAndFeel类还提供了初始化和反初始化方法:
public void initialize()

public void uninitialize()
在LookAndFeel通过使用UIManager.setLookAndFeel()方法设置为缺省时initialize()方法被UIManager调用。当LookAndFeel作为缺省感官被替换时uninitialize()方法会被UIManager调用。

缺省值表(The Defaults Table)

最后,LookAndFeel类提供了一个方法返回缺省值表的感官实现:

缺省值表通过UIDefaults类来表现,UIDefaults类是java.util.Hashtable 的一个直接扩展,它增加了访问感官具体类型信息方法。这个表必须包含所有的UIClassID-to-classname映射信息和每个UI 代理所有的与表现相关的属性的缺省值(比如颜色,字体,边框和图标)。比如下面是一个假设名为“mine”感官包中的可能的getDefaults()代码片断:

public UIDefaults getDefaults() {
UIDefaults table = new UIDefaults();
Object[] uiDefaults = {
"ButtonUI", "mine.MyButtonUI",
"CheckBoxUI", "mine.MyCheckBoxUI",
"MenuBarUI", "mine.MyMenuBarUI",
...
"Button.background",
new ColorUIResource(Color.gray),
"Button.foreground",
new ColorUIResource(Color.black),
"Button.font",
new FontUIResource("Dialog", Font.PLAIN, 12),
"CheckBox.background",
new ColorUIResource(Color.lightGray),
"CheckBox.font",
new FontUIResource("Dialog", Font.BOLD, 12),
...
}
table.putDefaults(uiDefaults);
return table;
}
当缺省感官通过UIManager.setLookAndFeel()设置的时候,UIManager在新的LookAndFeel实例中调用getDefaults(),并保存它返回的哈希表。在后来调用UIManager的查找方法时将会应用到这个表,比如,在使得“mine”为缺省感官之后:

UIManager.get("ButtonUI") => "mine.MyButtonUI"

UI类访问它们的缺省信息的方式都一样,比如,我们的例子类ButtonUI会初始化JButton的“background”属性如下:
button.setBackground(UIManager.getColor("Button.background");
缺省值通过这种方式组织起来以便开发人员重置它。关于Swing缺省值机制的更多细节会在将来的文档中描述。

UI设置属性与应用设置属性之间的区别(Distinguishing between UI-set and app-set properties)

Swing允许应用程序在组件上单独设置属性值(如颜色和字体)。所以必须保证这些值不会把感官给组件设置的缺省值属性弄的一团糟。

这在UI代理第一次安装在组件上的时候(在构造的时候)没有任何问题,因为所有的属性都会被反初始化和被感官合法的设置。问题出现在当应用程序在组件构造之后设置单独的属性,并且随后设置了一个新的感官(即感官动态转换)。这意味着感官必须能够区分哪些是应用程序设置的属性值,哪些是感官设置的属性值。

这个问题是通过感官使用plaf.UIResource接口对所有的这些值做标记来解决的。plaf包提供了一系列“标记了的”类来表现这些值,ColorUIResource,FontUIResource和BorderUIResource。前面的代码例子中展示了这些类为我们假设的MyButtonUI类的缺省值做标记的用法。


来源:
http://www.blogjava.net/azure/archive/2007/05/05/115441.html
http://java.sun.com/products/jfc/tsc/articles/architecture/ui_install/index.html

没有评论:

我的简介