2008年9月10日星期三

A Swing Architecture Overview(四)


UI 代理(The UI delegate)
所有的UI代理类的父类都是swing.plaf.ComponentUI。这个类包含了使可拔插感官工作的最主要的“机制”。它的方法主要处理UI的安装和卸载,代理组件的几何学处理和绘制。

很多UI代理子类还根据它们自己和组件的交互需要提供了额外的方法。不过,本文档的焦点主要是ComponentUI实现的一般机制。

UI的安装和卸载(UI installation and deinstallation)
马上,ComponentUI类为UI代理定义了这些安装和卸载的方法。
public void installUI(JComponent c)

public void uninstallUI(JComponent c)
看看JComponent.setUI()的实现(它总是被JComponent子类的setUI方法调用),我们可以清晰的看到UI代理是如何安装/卸载的:
protected void setUI(ComponentUI newUI) {
if (ui != null) {
ui.uninstallUI(this);
}
ComponentUI oldUI = ui;
ui = newUI;
if (ui != null) {
ui.installUI(this);
}
invalidate();
firePropertyChange("UI", oldUI, newUI);
}
UI安装插图(UI installation illustrated)
本文开头有一张巨大的像海报一样大小的图,描述的是安装UI代理的过程。他可以提供给你一些关于代理安装过程的有用信息。

UI代理的installUI()方法负责下面的事情:
  • 设置组件的缺省字体,颜色,边框和不透明属性。
  • 在组件上安装相应的布局管理。
  • 在组件上添加相应的子组件。
  • 在组件上注册所有需要的事件监听器。
  • 在组件上注册所有的感官具体键盘事件(助记符等)。
  • 注册相应的在重绘的时候会被通知的模型监听器。
  • 初始化所有相应的实例数据。
举例来说,一个ButtonUI的扩展类的installUI()方法可能如下:
protected MyMouseListener mouseListener;
protected MyChangeListener changeListener;
public void installUI(JComponent c) {
AbstractButton b = (AbstractButton)c;
// Install default colors & opacity
Color bg = c.getBackground();
if (bg == null || bg instanceof UIResource) {
c.setBackground(
UIManager.getColor("Button.background"));
}
Color fg = c.getForeground();
if (fg == null || fg instanceof UIResource) {
c.setForeground(
UIManager.getColor("Button.foreground"));
}
c.setOpaque(false);
// Install listeners
mouseListener = new MyMouseListener();
c.addMouseListener(mouseListener);
c.addMouseMotionListener(mouseListener);
changeListener = new MyChangeListener();
b.addChangeListener(changeListener);
}
组件属性初始化的约定(Conventions for initializing component properties)
Swing在安装的期间定义了很多组件属性初始化约定,包括如下:
  1. 所有用于设置颜色,字体和边框属性的值都应该从缺省值表中获取(在LookAndFeel subclass小节中有描述)。
  2. 颜色,字体和边框属性应该且只应该在应用还没有设置它们之前进行设置。
为了方便第一个约定, UIManager类提供了很多静态方法来提取具体类型的属性值(比如,静态方法UIManager.getColor(), UIManager.getFont()等)。

第二个约定通过在设置属性之前一直检查是否是空值或UIResource实例来实现。

ComponentUI的uninstall()方法必须小心恢复被installUI()方法做过的所有事情,这样组件就可以为下一个UI代理提供纯洁质朴的状态。uninstall()方法负责:
  • 如果边框属性已被installUI()方法设置了,将其清除。
  • 如果布局管理已被installUI()方法设置了,将其移除。
  • 移除所有installUI()方法添加的子组件
  • 移除所有installUI()方法添加的事件/模型监听器。
  • 移除所有installUI()方法添加的感官具体键盘事件。
  • 使所有初始化的实例数据无效(使GC可以回收)。
例如,uninstall()方法恢复我们在上面的安装例子中所做的事情可能如下:
public void uninstallUI(JComponent c) {
AbstractButton b = (AbstractButton)c;
// Uninstall listeners
c.removeMouseListener(mouseListener);
c.removeMouseMotionListener(mouseListener);
mouseListener = null;
b.removeChangeListener(changeListener);
changeListener = null;
}
几何学定义(Defining geometry)
在AWT中(swing中也一样),容器的布局管理器会根据它定义的算法来布局它的子组件;这被称为包含层次结构“确认”。代表性的来说,布局管理器会查询子组件的preferredSize属性(有时是minimumSize,或者maximumSize,取决于算法)来精确的判断它的子组件的大小和位置。

很明显,这些几何属性是某种感官通常需要为所给的组件定义的东西。所以为这个目的ComponentUI提供了下面的方法:

public Dimension
getPreferredSize(JComponent c)

public Dimension
getMinimumSize(JComponent c)

public Dimension
getMaximumSize(JComponent c)

public boolean
contains(JComponent c, int x, int y)
如果几何属性没有显式的被程序设置,JComponent相应的方法(它们通过确认被LayoutManager调用)会简单的代理UI对象的几何方法:

public Dimension getPreferredSize() {
if (preferredSize != null) {
return preferredSize;
}
Dimension size = null;
if(ui!=null)
{
size = ui.getPreferredSize(this);
}
return (size != null) ? size :super.getPreferredSize();
}
虽然所有的组件的边框都是矩形,但是通过在实现类中重置java.awt.Component的contains()方法模拟一个非矩形组件也是可行的。(这个方法用来给鼠标事件做点击测试)。但是,和swing的其它几何属性一样,UI代理定义了它自己的contains()方法版本,它也是被JComponent.contains()代理。

public boolean contains(JComponent c, int x, int y) {
return (ui!=null)?ui.contains(this, x, y) :super.contains(x, y);
}
所以UI代理可以通过定义特定的contains()实现提供非矩形的“感觉”(例如,如果我们想要我们的MyButtonUI类实现一个圆角的按钮)。

绘制(Painting)

最后,UI代理必须绘制相应的组件,所以ComponentUI有下面的方法:
public void paint(Graphics g, JComponent c)

public void update(Graphics g, JComponent c)
再重复一次,JComponent.paintComponent()负责代理绘制:
protected void paintComponent(Graphics g) {
if (ui != null) {
Graphics scratchGraphics =
SwingGraphics.createSwingGraphics(g.create());
try {
ui.update(scratchGraphics,this);
}
finally {
scratchGraphics.dispose();
}
}
}
和AWT一样,UI代理的update()方法清除背景(如果是透明的),然后调用paint()方法,它主要负责组件内容的渲染。

无状态的代理与有状态的代理(Stateless vs. stateful delegates)
ComponentUI所有的方法都是通过参数来获得JComponent对象,这个约定使得UI代理可以做无状态的实现(因为代理一直可以回过头来查询具体的组件实例来获得状态信息)。无状态UI代理实现使得组件所有的实例都可以使用同一个独立的UI代理,这可以有效的减少实例化的对象的数量。

这个方案对于很多简单的GUI组件来说工作的很好。但是对于更复杂的组件,我们发现它不能胜任,因为持续的状态判断带来的低效率比创建额外的对象要糟糕(特别是在给定的程序中复杂GUI组件的数量比较少的时候)。

ComponentUI类定义了一个静态方法来返回代理实例:
public static ComponentUI createUI(JComponent c)
这个方法的实现决定了代理是有状态的还是无状态的。因为组件通过调用UIManager.getUI()方法来创建UI代理对象,它通过内部调用代理类的createUI()方法来获得实例。

两种类型的代理swing感官都有使用。例如,swing的BasicButtonUI类就实现了一个无状态的代理:
// Shared UI object
protected static ButtonUI buttonUI;
public static ComponentUI createUI(JComponent c)
if(buttonUI == null) {
buttonUI = new BasicButtonUI();
}
return buttonUI;
}
然而swing的BasicTabbedPaneUI使用了有状态的方案:
public static ComponentUI createUI(JComponent c)
return new BasicTabbedPaneUI();
}
可拔插感官总结(Pluggable Look-and-Feel summary)
Swing的可拔插感官特性复杂(如果你搞懂了就不会)但非常强大。它是针对一小群有开发新感官实现需要的开发人员的编程而设计的。一般来说,应用开发人员只需要明白这个机制的能力,以便决定他们希望如何支持感官(比如是给程序提供固定的单一感官还是让用户来配置感官)。Swing的UIManager为应用提供了这个级别的API来管理感官。

如果你是这些需要(或想要)开发自定义感官的开发人员中的一个,不建议在没有理解这些基础之前就开始写代码,我们会提供更好的文档来帮助这个过程-从这篇文档起步,在很快就会来到的其它文档中继续前行。


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


没有评论:

我的简介