创建持久(或者保留的)图形的能力还不是“抽象窗口工具箱(Abstract Window Toolkit (AWT))”的标准部分。相反,绘制的每个图形在每次显示其包含的容器时由应用程序代码重新绘制。本文中,我将向您介绍,如何通过向 Java 平台添加保留的图形对象(RGO)来解决 AWT 这个局限性。我的开发的方法 ― RGO API ― 需要的代码最少,并且可以在需要的时候允许您添加并实现多种对象类型。
RGO 提供了基于创建可绘制对象(类
Drawable
的实例)的强大和便利的绘图性能,这些对象收集在一个有序集(类
DrawableSequencer
的实例)中,然后每当需要呈现一个给定的对象时,它就重新绘制这个集合。可绘制对象和对象集都有描绘服务并且可以持久地储存它们自己。第三个相关类称为
DrawingContext ,它抽取类
java.awt.Graphics 中定义的 Java
对象表示服务。(请注意,出于本文的目的,我们将使用
Graphics 类,而不是更强大更复杂的
Graphics2D 。将
Graphics2D
用于更丰富的图形是一个简单的扩展问题。)
通过为每个保留的对象形式创建类的类型来定义 RGO。这些类都是类
Drawable 的子类,它们定义了可以保留的图形对象的类型。直线、椭圆、矩形、文本和子图形是其中一些类型示例。我们在本文中定义的相应样本对象是:
-
DrawableLine
-
DrawableOval
-
DrawableBox
-
DrawableText
-
DrawableSprite
许多其它图形对象类型,比如位图、三角形和常规的多边形可以根据需要添加。
图 1 是一个说明 drawables 包中各类之间关系的 UML 模型。请注意
Drawables 、
DrawableSequencer 和
DrawingContext 之间的关系。
图 1. drawables 包中各类的关系
在下面的图 2 中,我向
DrawableSequencer 实例添加了
Drawable 子类的三个实例。一组
DrawableSequencerObject
实例记录了可绘制对象的显示优先级和对它的一个引用(实线箭头所示)。
DrawableSequencerObject 实例记录在
DrawableSequencer 实例内部的集合(实际上是
java.util.Vector )中。
在任何
DrawableSequencer.draw(DrawingContext)
方法调用期间,使用
DrawableSequencerEnumerator
实例来枚举(通过虚线箭头)
DrawableSequencerObject
对象。
图 2. 向 DrawableSequencer 添加 Drawable 的三个实例
DrawingContext
实例实际上将在应用程序客户机窗口中描绘对象。客户机窗口是由
java.awt 和
javax.swing
类库中的类以及应用程序维护的。枚举器调用
Drawable.draw(DrawingContext) 方法。那个方法转而调用
Drawable.drawWorker(DrawingContext) 方法,通过调用
DrawingContext.draw...()
方法来处理请求,其中的省略号(...)表示图形类型。然后,
DrawingContext
实例将绘图请求转发到与
DrawingContext 关联的
java.awt.Graphics 实例。
图 2 中显示的描绘对象步骤如下所示:
- 使用在其它地方创建的
java.awt.Graphics对象创建DrawingContext实例。(这通常在应用程序的paint(Graphics)方法中完成,其中Graphics对象作为来自 Java 运行时支持的参数传入。)
- 调用传递
DrawingContext实例的DrawableSequencer.draw()方法。
-
DrawableSequencer.draw()创建DrawableSequencerEnumerator实例来遍历DrawableSequencer的集合中的所有对象。
下一步,对于集合中的每个
DrawableSequencerObject :
- 为
Drawable实例调用Drawable.draw()方法,该实例与向它传递DrawingContext实例的DrawableSequencerObject实例相关联。
- 如果这个实例可见,那么
Drawable.draw()立即为那个实例调用Drawable.drawWorker()。
-
Drawable.drawWorker()通过调用形成对象的对象所需的DrawingContext.draw...例程来处理描绘请求。例如,DrawableBox对象将使用DrawingContext.drawBox()方法。Drawable.drawWorker()方法可能在调用DrawingContext.draw...()方法之前执行对象转换。
然后DrawingContext.draw...()通过调用一个或多个java.awt.Graphics()方法来处理描绘请求。
为绘制一个实心(相对于空心)矩形,DrawingContext.drawBox()方法将使用Graphics.setColor()方法和Graphics.fillRect()方法。然后,DrawingContext.draw...()方法可能在调用Graphics方法完成操作之前执行对象转换。
在以下各节中,我将描述用来定义 RGO 编程框架(也称为 API)的类和方法。本文中讨论的所有类都在缺省 Java 包中。源码可以从本文结尾处的链接访问(请参阅 参考资料)。
有两种不同的方法来持久地存储
Drawable
对象。第一种方法使用 Java 平台的
Serializable 接口和
ObjectStreams ,该方法需要的代码量最少。这种方法中,每个
Drawable 对象和所有可从该方法获得的对象都必须实现
Serializable
接口。第二种方法(称为“自存储”,或者显式地为持久字段的存储编码)需要更多编码,但产生的持久文件要小得多(通常不到那些使用
Serializable
生成的文件大小的一半)。在保存复杂图形时,这个大小差别是很重要的。例如,比较存储包含
25 个测试
DrawableText 对象的图形所需的空间 ― 使用
Serializable 接口创建的文件大小为 2,636
字节,而使用“自存储”方法创建的文件大小为 1,284 字节。
在“自存储”方法中,持久保存的 drawables 继承了
PersistentDrawable 类。每个带新数据字段的
PersistentDrawable 子类必须定义
storeOn()
和
loadFrom() 方法。
我将在下面的相关类描述中进一步解释持久对象存储的这两种方法。请参阅侧栏“ 持久对象结构”来查看“自存储” RGO 的布局。
在示例 drawables 包中找到的类是以表 1 中显示的层次结构组织起来的。
| 类名 | 属性 | 实现方法 |
Drawable
| abstract, public |
draw 、
storeOn 、
loadFrom
|
DrawableBox
| public |
drawWorker 、
storeOn 、
loadFrom
|
DrawableLine
| public |
drawWorker 、
storeOn 、
loadFrom
|
DrawableOval
| public |
drawWorker 、
storeOn 、
loadFrom
|
DrawableRectForm
| abstract, public |
storeOn 、
loadFrom
|
DrawableBox
| public |
drawWorker
|
DrawableOval
| public |
drawWorker
|
DrawableSprite
| public |
drawWorker 、
storeOn 、
loadFrom
|
DrawableText
| public |
drawWorker 、
storeOn 、
loadFrom
|
我已经在
Drawable 或者
DrawableRectForm 超类中放置了尽可能多的公共行为。
下列方法和构造器对于组成 drawables 包的类是公共的。
clone() 方法
标准 Java 类库的一个弱点是很少使用
java.lang.Cloneable
接口。很明显 Java 语言的设计者考虑到了克隆,因为
clone() 方法已在
java.lang.Object
类中定义。但他们还是决定在没有实现
Cloneable
接口的类中禁止该方法。让大多数类(如果不是所有的类)实现标准的克隆方法后,类(特别是象
Point 和
Rectangle
这样的简单数据结构类)产生一个一致的办法来复制这些对象。没有这个办法,您有时必须通过使用复制构造器,或者通过带有待复制对象字段的引用(作为参数)的普通构造器,或者通过目标类的其它一些工厂(factory)方法来创建对象的副本。这样的选择取决于目标类。它迫使复制代码要知道要进行复制的目标类型。通用克隆能力则无须这么做。
clone()
方法提供给了本文(在它实际可以使用的地方)中定义的所有类。
toString() 方法
每个类实现
toString() 方法。这样允许类的实例用在
String
实例所需的任何地方(比如用于打印实例)。在类不是
final(也就是说,类可以生成子类)的情况下,
toString
行为在
toString() 方法(它生成类名称和括起的括号)和
bodyToString()
方法(它生成对象的内容)之间被分割。(这类似于通常用在
java.awt 和
javax.swing 包中的
toString() 方法和
paramString()
方法。)
这个方法允许所有的子类使用公共
toString() 方法,而允许一个子类一个子类地进行特定格式编排。
构造器类型 drawables 包中的每个类可以支持几种构造器类型:
-
缺省构造器:这是一个不带参数的构造器。缺省构造器用于类
Drawable的所有非抽象子类来支持持久对象的重新装入。如果在参数没有提供信息的情况下无法使对象处于可用状态,特殊的类就可能不提供缺省构造器。
-
普通构造器:这个构造器提供在实例创建时指定的所有参数。
-
备用构造器:这个构造器提供在实例创建时指定的参数子集(通常是常用的那些)。请注意,一些类不能提供备用构造器。
-
复制构造器:这个构造器通过复制相同类的另一个实例来初始化新创建的对象。
clone()方法使用它。这样,它就可以被提供clone()方法的任意类所使用。
访问方法
访问方法提供了对重要的私有(private)或者受保护的(protected)实例变量的公开(public)访问。要获取变量值,可以使用
get<VariableName>()
访问方法。要设置变量值,可以使用
set<VariableName>(...)
访问方法。通常,设置访问方法复制它的参数。这样就避免了类对任意传递对象的后继更改。一个例外是在
DrawableSequencer 中保存
Drawable
对象。访问方法经常声明为 final,以允许编译器优化它们的性能。
类
Drawable
是可绘制对象的一个抽象模型,用于提供这些对象的公共定义和行为。每个可绘制对象的类型由
Drawable 的非抽象子类定义。这些子类定义了受支持的
RGO。我准备了一个可绘制对象类型的具有代表性的样本,这些对象可能使用
java.awt.Graphics 服务。通过建立
Drawable
或者
DrawableRectForm
的子类可以添加其它保留对象类型。
所有
Drawable 实例共享一些公共特征:
-
可见性:每个
Drawable实例可以标记为 visible(可见)或 invisible(不可见)。 不绘制不可见对象。实例的可见性可以在任何时候更改。
-
缩放:每个
Drawable实例都有一个缩放因子(即,缩放比:M:N)。缺省值为 1:1 的比例。当 M 值比 N 值大时放大对象。相应地,M 值小于 N 值时缩小对象。这个比例通过类Scale实现。请注意,使用整数比例而不是浮点进行缩放,是因为整数比例需要较少的运行时计算(即,浮点和/或双精度与整数格式之间的转换)。
-
颜色:每个
Drawable实例都有一个java.awt.Color值。通常以这种颜色绘制实例。子类可能支持多颜色对象。在这种情况下,颜色值可以被忽略或者作缺省值或者底色。
请参阅下面的清单 1,查看类
Drawable
的定义。
Drawable
是抽象的,因为它本身没有绘图功能。它是可克隆的,因为这样很容易在不知道特殊对象子类名称的情况下复制
Drawable 对象。
draw() 和
DrawWorker() 方法实际上导致显示
Drawable
对象。
draw() 方法用来处理可见与不可见对象的表示。方法
drawWorker() 是一个抽象方法,必须在
Drawable
的所有非抽象子类中定义。它负责描绘对象。通常,它使用
DrawingContext 类的服务方法来完成。
当使用自存储持久性时,
loadFrom() 和
storeOn() 方法为
PersistentDrawable
对象提供持久性支持。每个添加实例变量的
PersistentDrawable
子类必须覆盖这些方法。由于文章篇幅有限,我不在所有的子类定义中显示这些方法,但所有这些方法都与下面的类
DrawableSprite 显示和描述的方法相似。请参阅侧栏“
持久对象的结构”查看“自存储”RGO 的布局。
清单 1. 类 Drawable 的定义
public abstract class Drawable implements Cloneable
{
protected Color _color; // object color
protected boolean _visible; // object is shown
protected Scale _scaler; // scale by this
protected Drawable(Color color, boolean visible, Scale scale) {
_color = new Color(color.getRGB());
_visible = visible;
_scaler = (Scale)scale.clone();
}
// draw myself
public final void draw(DrawingContext context) {
if(_visible) drawWorker(context);
}
abstract protected void drawWorker(DrawingContext context);
// save myself persistently
public void storeOn(DataOutputStream dos) throws IOException {
dos.writeByte(_color.getRed());
dos.writeByte(_color.getGreen());
dos.writeByte(_color.getBlue());
dos.writeInt(_scaler.multBy); dos.writeInt(_scaler.divBy);
dos.writeBoolean(_visible);
}
// reload myself from persistent storage
public void loadFrom(DataInputStream dis) throws IOException {
_color = new Color(s.readByte(), is.readByte(), dis.readByte());
_scaler = new Scale(is.readInt(), dis.readInt());
_visible = dis.readBoolean();
}
}
|
本节中总结的
Drawable
的可描绘子类将要提供如何编码可绘制对象的示例。它们不一定是绘图应用程序需要的所有类型对象的代表。可以在包含的源文件中找到下面描述的所有子类。
类
DrawableText
定义一行水平文本。
DrawableText 对象是
java.awt.Graphics.drawString
函数的直接表示。可以设置文本的开始位置、前景色和字体大小。实际所用的字体大小是为活动
Java 字体定义的最接近字体大小。如果所期望的字体太小(2
点或者更小),那么将无法绘制文本。
DrawableText 从
Drawable
继承而来。它定义
drawWorker(...)
方法。这个方法,象所有其它
Drawable 子类
drawWorker() 方法一样,只是调用类
DrawingContext 参数的服务方法。
清单 2. 类 DrawableText
public class DrawableText extends Drawable {
protected Point _start; // coordinates
protected String _text;
protected int _fontSize; // display size
// draw myself
public void drawWorker(DrawingContext context) {
context.drawText(_scaler.scale(_start), _text, _scaler.scale(_fontSize), _color);
}
}
|
类
DrawableLine
定义了一条直线。
DrawableLine 对象是
java.awt.Graphics.drawLine
函数的直接表示。可以设置直线的开始和结束位置以及颜色。如上所述,
DrawableLine 的
drawWorker() 方法调用类
DrawingContext 参数的服务方法。这适用于
Drawable 的所有子类的
drawWorker()
方法。清单 3 是类
DrawableLine 的定义。
清单 3. 类 DrawableLine
public class DrawableLine extends Drawable
{
protected Point _start, _end; // coordinates
// draw myself
public void drawWorker(DrawingContext context) {
context.drawLine(_scaler.scale(_start), _scaler.scale(_end), _color);
}
}
|
类
DrawableSprite 定义了一个 36 条直线的集合,这 36
条直线从一个公共中心点向外延伸,相邻两线之间的夹角为 10
度。每条直线有随意选择的不同颜色。因此忽略了继承的颜色值。可以设置中心点和直线的长度。
DrawableSprite 对象是由一些
java.awt.Graphics.drawLine 图元函数构成的。它显示
Drawable 子类不受
java.awt.Graphics
服务直接表示的限制。清单 4 是类
DrawableSprite
的定义。
清单 4. 类 DrawableSprite
public class DrawableSprite extends Drawable
{
protected Point _center; // coordinates
protected int _length; // line length
// draw myself
public void drawWorker(DrawingContext context) {
context.drawSprite(_scaler.scale(_center), _scaler.scale(_length));
}
}
|
DrawableSprite 的另一个实现是使用
DrawableLine 实例的集合。在这种情况下,不需要
DrawingContext.drawSprite() 方法。清单 5
显示了修改过的类
DrawableSprite
定义。这里,直线包含在实例中而不是由
DisplayContext
服务方法绘制。因为在对象中有多条直线,每条直线都必须由函数性方法处理。
清单 5. 类 DrawableSprite的数组实现
public class DrawableSprite extends Drawable
{
private int numlines = 36;
protected DrawableLine[] _lines = new DrawableLine[numlines];
public DrawableSprite(Point center, int length, boolean visible, Scale scale) {
super(Color.white, visible, scale);
double step;
int i;
PositiveRandom rgb = new PositiveRandom(256);
// make radial lines every 2Pi/numlines radians;
// each line is a different color
for(i = 0, step = 0 * Math.PI;
i < numlines;
i++, step += Math.PI / numlines) {
double sin = Math.sin(step), cos = Math.cos(step);
DrawableLine line = new DrawableLine(
Point(center.x + (int)((length / 10) * cos),
center.y - (int)((length / 10) * sin)),
Point(center.x + (int)( length * cos),
center.y - (int)( length * sin)),
new Color(rgb.next(), rgb.next(), rgb.next()), true, _scaler);
_lines[i] = line;
}
}
public void setScale(Scale scale) {
super.setScale(scale);
// scale each line
for(int i = 0; i < numlines; i++) {
_lines[i].setScale(scale);
}
}
// draw myself
public void drawWorker(DrawingContext context) {
// draw each line
for(i = 0; i < numlines; i++) {
_lines[i].draw(context);
}
}
// save myself
public void storeOn(DataOutputStream dos) throws IOException {
super.storeOn(dos);
// save each line
dos.writeInt(numlines);
for(int i = 0; i < numlines; i++) {
_lines[i].storeOn(dos);
}
}
// reload myself
public void loadFrom(DataInputStream dis) throws IOException {
super.loadFrom(dis);
// load each line
numlines = dis.readInt(numlines);
_lines = new DrawableLine[numlines];
for(int i = 0, i < numlines; i++) {
_lines[i] = new DrawableLine();
_line[i].loadFrom(dis);
}
}
}
|
loadFrom(...) 和
storeTo(...) 方法将从
Drawable
继承来的方法扩展为分别装入和保存该类引入的附加字段。
另一种实现是使用
DrawableSequencer
而不是数组来保留这些直线。这将不需要使用
for
循环来迭代每条直线。这个解决方案将需要
DrawableSequencer 从
Drawable 继承。
类
DrawableRectForm
为可以由边界矩形框描述的可绘制对象定义了抽象模型。
示例有实心和空心圆、椭圆以及矩形。可以设置对象的边角位置、宽度、高度和颜色。
清单 6 显示了类
DrawableRectForm 的定义。由于
DrawableRectForm 是抽象的,所以不需要为它实现
drawWorker(...) 方法。
清单 6. 类 DrawableRectForm
public abstract class DrawableRectForm extends Drawable
{
protected Rectangle _area; // coordinates & size
}
|
类
DrawableBox 和类
DrawableOval
分别定义了实心矩形和圆形(或者椭圆形)对象。
DrawableBox
对象是
java.awt.Graphics.fillRect
函数的直接表示;
DrawableOval 对象是
java.awt.Graphics.fillOval
函数的直接表示。可以设置对象的边角位置、宽度、高度和颜色。
清单 7 显示了类
DrawableBox 和类
DrawableOval 的定义。由于这两个类都从
DrawableRectForm
继承而来并且没有添加新的实例变量,因此它们不需要定义
loadFrom(...) 和
storeTo(...) 方法。
清单 7. 类 DrawableBox 和类DrawableOval
public class DrawableBox extends DrawableRectForm
{
// draw myself
public void drawWorker(DrawingContext context) {
context.drawBox(_scaler.scale(_area), _color);
}
}
public class DrawableOval extends DrawableRectForm
{
// draw myself
public void drawWorker(DrawingContext context) {
context.drawOval(_scaler.scale(_area), _color);
}
}
|
其名称以
DrawableSequencer 开始的类负责创建并维护
Drawable 对象的有序集。这个集合可以编辑、可以在
DrawingContext
上描绘、可以存储到文件中,或者从文件装入。
每个对象根据绘制优先级排序。优先级较低(在数学意义上)的对象排列在优先级较高的对象后面。如果优先级较高的对象不透明,那么它将隐藏紧跟在它后面的优先级较低的对象。多个对象可能有相同的优先级。不指定相同优先级对象的相对绘图顺序。
DrawableSequencer 的编辑能力包括添加对象、除去对象或者除去所有的对象。通过除去对象,然后根据不同的优先级进行添加来重新排列对象。
DrawableSequencer 支持符合
java.util.Enumeration
的对象的创建。可以使用它们访问包含在序列中的对象。提供了两个枚举类型:
-
无限制:集合中的所有
Drawable对象将按照优先级顺序被查看。
-
限制范围:其优先级值在指定的值范围之间(包括边界值)的集合中的所有
Drawable对象按照优先级顺序被查看。如果高值小于低值,那么将看不到对象。
当使用自存储持久性时,
DrawableSequencer
提供服务来将它的对象持久地存储到文件中,并在稍后重新装入它们。重新装入一组已保存对象的集合意味着根据优先级将它们添加到现有的序列中。要用文件中的对象替换序列,必须首先从序列中除去所有对象。
当使用 Java 的
ObjectStream
持久性时,
DrawableSequencer 提供了静态
loadFrom() 和
storeIn() 方法,它们使用
ObjectStreams 来装入和存储
Drawables 。这个机制总是替代现有的可绘制对象的内容。
在“自存储”方法中,每个对象通过它实例上的方法装入或者存储其本身。在
ObjectStream 持久性方法中,每个对象由外部应用的逻辑来保存或装入。因此,我更喜欢“自存储”方法。
在下面各节中,我们将讨论与类
DrawableSequencer 有关的每个类的定义和用法。
类
DrawableSequencer 提供了上述支持。清单 8 是类
DrawableSequencer 的定义。
清单 8. 类 DrawableSequencer
public class DrawableSequencer implements Cloneable
{
private Vector _items; // ordered items in list
// add new drawable
public void addAt(Drawable drawable, int priority) {
int i;
// insert element at proper priority; higher priorities come last
for(i = 0; i < _items.size(); i++) {
if(priority <= dso.priority) // past all lower priorities elements
break;
}
_items.insertElementAt(new DrawableSequencerObject(priority, drawable), i);
}
// remove all drawables
public void removeAll() {
_items.removeAllElements();
}
// remove a drawable
public void remove(Drawable drawable) throws NoSuchElementException {
int i, size = _items.size();
// find element to remove
for(i = 0; i < size; i++) {
DrawableSequencerObject dso =
(DrawableSequencerObject)_items.elementAt(i);
if(drawable == dso.drawable) // must be the same (vs equal)
break;
}
if(i < size) // found one
_items.removeElementAt(i);
else
throw new NoSuchElementException(
"DrawableSequencer.remove() - drawable not found");
}
// get enumerator for all
public Enumeration elements() {
return elements(Integer.MIN_VALUE, Integer.MAX_VALUE);
}
// get enumerator for range
public Enumeration elements(int low, int high) {
return new DrawableSequencerEnumerator(_items, low, high);
}
// draw all matching drawables
protected void drawSelected(DrawingContext context, Enumeration e) {
// walk all drawables, and draw them
while(e.hasMoreElements()) {
Drawable d = (Drawable)e.nextElement();
d.draw(context); // draw current drawable
}
}
// draw all drawables
public void draw(DrawingContext context) {
drawSelected(context, elements());
}
// draw selected drawables
public void draw(DrawingContext context, int min, int max) {
drawSelected(context, elements(min, max));
}
}
|
loadFrom() 和
storeOn() 方法为包含在
DrawableSequencer 中的
Drawable
对象提供持久性支持。当需要每个文件的单个序列时,文件形式是很方便的。流形式使用一个现有的流。这样多个序列可以存储在一个流上。
清单 9 定义类
DrawableSequencer 的持久性服务。
清单 9. 类 DrawableSequencer的持久性服务
// store all on a stream
public void storeOn(DataOutputStream dos) throws IOException {
// walk all drawables, and store them
Enumeration e = _items.elements();
dos.writeInt(_items.size()); // write total count
while(e.hasMoreElements()) {
DrawableSequencerObject dso =
(DrawableSequencerObject)e.nextElement();
Drawable drawable = dso.drawable;
int priority = dso.priority;
dos.writeInt(priority); // write priority
dos.writeUTF(drawable.getClass().getName()); // then class name
drawable.storeOn(dos); // then class instance variables
dos.flush();
}
}
// store all on a stream
public void storeOn(File f) throws IOException {
FileOutputStream fos = new FileOutputStream(f);
BufferedOutputStream bos = new BufferedOutputStream(fos);
DataOutputStream dos = new DataOutputStream(bos);
storeOn(dos); // store myself
dos.close();
}
// load all from a stream
public void loadFrom(DataInputStream dis) throws IOException,
ClassNotFoundException, InstantiationException, IllegalAccessException {
int drawableCount; // number of objects in stream
// load all names
try {
// walk all stored drawables, and load them
drawableCount = dis.readInt();
for(int count = 0; count < drawableCount; count++) {
int priority = dis.readInt();
String className = dis.readUTF();
Class classObject = Class.forName(className);
Object object = classObject.newInstance();
if(!(object instanceof Drawable))
throw new ClassNotFoundException(className + " not a subclass of Drawable");
Drawable drawable = (Drawable)object;
drawable.loadFrom(dis);
addAt(drawable, priority);
}
}
catch(EOFException ex) { // map exception
throw new ClassNotFoundException(ex.getMessage());
}
}
// load all drawables from a file
public void loadFrom(File f) throws IOException,
ClassNotFoundException, InstantiationException, IllegalAccessException {
FileInputStream fis = new FileInputStream(f); // get a data stream to a file
BufferedInputStream bis = new BufferedInputStream(fis);
DataInputStream dis = new DataInputStream(bis);
loadFrom(dis); // load myself
dis.close();
}
|
类
DrawableSequencerObject 表示存储在
DrawableSequencer 中的信息。 它记录
Drawable
对象和它的优先级。这个类不是公用的,因为只能由
DrawableSequencer 使用它。清单 10 是类
DrawableSequencerObject 的定义。
清单 10. 类DrawableSequencerObject
final class DrawableSequencerObject implements Cloneable
{
public int priority; // relative position selector
public Drawable drawable; // actual item to draw
}
|
类
DrawableSequencerEnumerator 提供了服务来迭代
DrawableSequencer 中的对象。本质上,它是 C
结构,并且记录鼠标的当前位置和选择范围的高低值。这个类不是公共的,因为它是由
DrawableSequencer 的方法创建的,并且符合公共接口
java.util.Enumeration 。
由于
DrawableSequencer 使用
java.util.Vector 来存放
Drawable
对象,所以枚举器只增加索引值来在序列中前进。不在期望范围内的值被跳过。索引总是在待访问序列的下一个对象上设置,或者越过最后一个对象设置。
清单 11 是类
DrawableSequencerEnumerator 的定义。
清单 11. 类DrawableSequencerEnumerator
class DrawableSequencerEnumerator implements Enumeration
{
private int _index; // relative position selector
private Vector _vector; // enumerate over this
private int _low, _high; // range selectors
public DrawableSequencerEnumerator(Vector vector, int low, int high) {
_vector = vector;
_low = low; _high= high;
// advance while outside of range to set to first
for(_index = 0; _index < _vector.size(); _index++) {
int priority =
((DrawableSequencerObject)_vector.elementAt(_index)).priority;
if(priority >= _low && priority <= _high) // in range
break;
}
}
// test for done
public boolean hasMoreElements() {
return _index < _vector.size();
}
// get current and advance
public Object nextElement() {
Object result =
((DrawableSequencerObject)_vector.elementAt(_index)).drawable;
// advance while inside of range to set to next
for(_index++; _index < _vector.size(); _index++) {
int priority =
((DrawableSequencerObject)_vector.elementAt(_index)).priority;
if(priority >= _low && priority <= _high) // in range
break;
}
return result;
}
}
|
类
DrawingContext 提供对
java.awt.Graphics
绘图服务的访问,还提供下列附加服务:
-
缩放:每个
DrawingContext提供逻辑窗口区域和逻辑框架区域。这些区域目前只限于一个正方形,但它可以简单地扩展为任意矩形。应用程序可以将框架区域与显示的框架大小相关联。DrawingContext实例将自动缩放对象,使窗口范围显示在一个框架大小的区域内。
-
平移:每个
DrawingContext提供窗口原点。原点弥补了java.awt.Graphics的 x 和 y 方向与普通数学方向之间的差别。在 Java 语言中,x 从左到右递增,y 从上到下递增。在数学中,x 从左到右递增,y 从下到上递增。而且,移动原点产生扫视显示的对象的效果。
清单 12 是类
DrawingContext 的定义。
清单 12. 类 DrawingContext
public class DrawingContext
{
protected Graphics _graphics; // associated graphics context
protected Point _origin; // origin in space
protected Scale _scaler; // scale by this
public DrawingContext(Graphics graphics, Point origin, Scale scale) {
_graphics = graphics.create(); // local copy that can change
_origin = new Point(origin.x, origin.y);
_scaler = (Scale)scale.clone();
}
// do rectangle adjustments
protected Rectangle map(Rectangle r) {
Point xy = map(r.x, r.y), wh = _scaler.scale(r.width, r.height);
return new Rectangle(xy.x, xy.y, wh.x, wh.y);
}
// do translate and scale
protected Point map(int x, int y) {
Point mapped;
mapped = _scaler.scale(x, y);
mapped.x = _origin.x + mapped.x; // x increases towards screen right
mapped.y = _origin.y - mapped.y; // y increases towards screen bottom
return new Point(mapped.x, mapped.y);
}
public void drawLine(Point start, Point end, Color color) {
_graphics.setColor(color);
Point s = map(start), e = map(end);
_graphics.drawLine(s.x, s.y, e.x, e.y);
}
public void drawBox(Rectangle area, Color color) {
// similar to drawLine above but uses _graphics.fillRect
}
public void drawOval(Rectangle area, Color color) {
// similar to drawLine above but uses _graphics.fillOval
}
public void drawSprite(Point center, int length) {
double Pi = Math.PI;
PositiveRandom rgb = new PositiveRandom(256);
Point c = map(center);
int l = _scaler.scale(length);
// make radial lines every 2Pi/36 radians
for(double step = 0 * Pi; step < 2 * Pi; step += Pi / 36) {
_graphics.setColor(new Color(rgb.next(), rgb.next(), rgb.next()));
double sin = Math.sin(step), cos = Math.cos(step);
_graphics.drawLine(c.x + (int)((l / 10) * cos), c.y - (int)((l / 10) * sin),
c.x + (int)( l * cos), c.y - (int)( l * sin));
}
}
public void drawText(Point start, String text, int fontSize, Color color) {
int newFontSize = _scaler.scale(fontSize);
if(newFontSize > 2) { // font can be seen
_graphics.setColor(color);
Font of = _graphics.getFont(); // scale the font
Font nf = new Font(of.getName(), of.getStyle(), newFontSize);
_graphics.setFont(nf);
Point p = map(start);
_graphics.drawString(text, p.x, p.y);
}
}
:
// other drawing services added here
:
}
|
原点和缩放访问方法(没有在清单中显示)允许动态更改通过
DrawingContext 显示的
DrawableSequencer
。每种绘图服务(
draw(...) )调用一个或多个
java.awt.Graphics
服务从而在实际上描绘对象。特别的,
drawSprite(...)
多次调用
Graphics.drawLine() 。
drawText(...)
服务必须进行特殊处理来弥补 Java 语言文本字体有限的缩放能力。
drawables 包提供并使用两个简单服务类,类
Scale 和类
PositiveRandom 。
类
Scale 提供服务来缩放各种数据类型。清单 13
显示了类
Scale 的接口。
清单 13. 类 Scale
public final class Scale implements Cloneable
{
public int multBy;
public int divBy;
public Scale(int m, int d);
public Scale();
public Scale(Scale other);
public String toString();
public Object clone();
public int scale(int v); // scale a value
public Point scale(int x, int y); // scale x, y pair
public Point scale(Point other); // scale a point
public Rectangle scale(Rectangle other); // scale a rectangle
public Dimension scale(Dimension other); // scale an extent
}
|
类
PositiveRandom 产生限制在
0:limit-1
(包含边界)范围的随机数字。清单 14 显示了类
PositiveRandom 的接口。
清单 14. 类 PositiveRandom
public class PositiveRandom
{
public PositiveRandom(int limit);
public PositiveRandom();
public int next(); // get a random number (0:limit-1)
}
|
显示在
清单 8 中的
DrawableSequencer
仅仅是一个绘图引擎示例。可以构建其它序列器,它们可能基于
java.util.Map 接口。例如,如果不需要文件的持久性和其它
DrawableSequencer
服务(诸如缩放和平移),那么可以用简单的 Java
数组来代替序列器,如清单 15 所示。
清单 15. 简单的绘图应用程序
class SomeApplet extends Applet
{
private static final int MAX_DRAWABLES = 100; // arbitrary
private Drawable[] drawables = new Drawable[MAX_DRAWABLES];
private int nextDrawable = 0;
// code to create a Drawable subclass
public void createDrawable(...) throws ... {
if(nextDrawable < length) {
Drawable d;
:
// code to make the drawable
:
drawables[nextDrawable++] = d; // record drawable
}
else
throw ...; // recover from full condition
}
// paint the collection
public void paint(Graphics g) {
DrawingContext dc = new DrawingContext(g);
// walk list of drawables
for(int i = 0; i < length; i++) {
if(drawables[i] != null)
drawables[i].draw(dc);
}
}
}
|
在一个完全不同的实现中,RGO 可以定义为 Java 接口而不是 Java
类。这样允许 RGO 充当在应用程序中使用的其它类型。然后,通常每个 RGO
类型都会有一个缺省实现(例如,
DrawableLine 接口可以有
DefaultDrawableLine 或者
DrawableLineImpl
类)。
也可能设计备用类层次结构。例如,考虑如表 2 中所示的层次结构。
| 类名 | 属性 |
Drawable
| abstract, public |
DrawableComment
| public |
PersistentDrawable
| abstract, public |
DrawablePoint
| public |
ScaleableDrawable
| abstract, public |
DrawableSequencer
| public |
DrawableSprite
| public |
ColoredDrawable
| abstract, public |
DrawableBox
| public |
DrawableLine
| public |
DrawableOval
| public |
DrawableRectForm
| abstract, public |
DrawableBox
| public |
DrawableOval
| public |
DrawableText
| public |
这种组织允许创建非持久性的可绘制对象(诸如
DrawableComment )并且消除
DrawableSprite
中不使用的颜色值。现在有可能使用不可缩放的对象(诸如
DrawablePoint )。它还允许创建、绘制和持久保存
Drawable
对象的树结构。取决于所有包含对象的缩放需要,
DrawableSequencer
可以从
ScaleableDrawable (如下所示)或者从
PersistentDrawable 继承。
尽管本文中包含的示例没有演示,但还是能很容易地想象使用上面的层次结构如何开发复杂的对象编辑器。这样的编辑器在任何时候都可以从
DrawableSequencer 添加和删除对象。无需从序列器中除去可绘制对象就可编辑它们本身。例如,可以使对象不可见或者更改它的缩放比。
备用设计可能性的讨论当然是很简要的,主要是用来激发您的想象。重要的是掌握我在本文中描述的编程模块的灵活性。可以构造
DrawableSequencer
集合来创建比这里定义的那些可绘制对象复杂得多的对象。
Drawable
对象还可以和多个
DrawableSequencer 以及诸如
java.util.Vector
的其它集合关联。如果序列器保存到文件中并被重新装入,那么每个序列器将获取以前共享对象的独立副本。
我已经开发了一个简单的驱动程序来演示 drawable 包的能力。该演示有三个实现,如下面的表 3 所描述,每个实现所满足的功能性要求略有不同。
| 实现 | AWT GUI 对 Swing GUI* | 自存储对 ObjectStream 持久性 |
| DrawableTester | AWT | 自存储 |
| DrawableTester2 | Swing | 自存储 |
| DrawableTesterOS | Swing |
ObjectStream
|
* 基于 AWT 和基于 Swing 的 GUI 之间的一个显著差别是 AWT 的
Panel 调用方法
update() 作为
redraw()
方法的结果,这样应用程序就不需要绘制背景。Swing 的
JPanel,因为使用轻量级组件,没有这样做,所以
paint()
方法必须首先绘制背景。
通常来说,
DrawableTester 演示提供接口来允许创建
Drawable 对象的多种类型,并将它们添加到单个
DrawableSequencer
中。然后它驱动序列器以便在它的客户机区域中显示对象。它提供了对对话框的访问来将当前的
Drawable
集合保存到用户可选的文件中,从用户可选的文件中重新装入它,然后改变已绘制对象的缩放比和原点。
下面几张图显示了保留的图形对象的实际外观和工作原理。同时鼓励您尝试一下包含在本文 参考资料一节中的源码清单。
图 3. 文本演示
图 4. 椭圆和方形演示,以及更改缩放比
请注意表 4 中对象的相对位置。许多对象被优先级较高的对象所遮盖。
图 5. 子图形演示
图 6. 缩放并移动对象后的演示
图 7. 样本演示界面
使用本文提供的示例以及所包含的源码,您应该发现在需要图形对象时,创建和保留它们相当简单。另外,可以以本文中的示例为起点,在这里描述的
API 上创建您自己的变体。因为类
Drawable 和类
DrawingContext
都可以生成子类,因此添加新的可绘图对象和
java.awt.Graphics 绘图服务接口非常容易。新的对象作为
Drawable 的新子类添加。新的绘图服务和数据转换作为
DrawingContext 的子类添加。
有效演示和源码
- 您可以参阅本文在 developerWorks 全球站点上的
英文原文.
-
DrawableTester 使用 AWT GUI
和自存储方法来保留图形文件。
-
DrawableTester2 使用 Swing
GUI 和自存储方法来保留图形文件。
-
DrawableTesterOS 使用 Swing
GUI 和
ObjectStream持久性方法来保留图形文件。
技术参考资料
- IBM alphaWorks 提供了
Java 的图形基础类(GFC),它是 Java
语言中图形的编程框架。
-
IBM 研究小组支持许多与图形和可视化编程有关的项目。
附加的相关内容
- RGO API 只是 Barry Feigenbaum 的发明之一。他的文章“
Take command of your client/server apps”(developerWorks,2001
年 6 月)详细叙述了 Barry
实现客户机/服务器对话应用程序的轻量级方法。
- John Carr 在他的文章“
JSci: An
open-source alternative for Java 2D
graphing”(developerWorks,2001 年 10 月)中提出了基于 Java 2D
图形备用开放源码。
- Bertrand Portier 的“
Java 2 gets a
new focus subsystem”(developerWorks,2001 年 10 月)讨论了
Merlin(Java 2 标准版,v1.4)为 AWT
焦点管理子系统带来的几个激动人心的更改。
- John Zukowski 的“
AWT roundup” (developerWorks,2001 年 11
月)进一步详细描述了对 AWT 类库最近的更改。
- 可以在 IBM developerWorks 的
Java
技术专区中找到许多有关 Java 编程各方面的文章。

Barry Feigenbaum 博士在开发操作系统和复杂应用程序方面有 20 多年的经验。目前他是 IBM Developer Relations IT 领域的系统架构师。他曾经是专门从事电子商务支持技术(诸如 Java 技术、HTML、servlet、JSP 技术以及 EJB 组件)的顾问。他是面向对象的分析、设计和开发方面的专家,并且非常精通客户机/服务器和 n 层体系结构的最新趋势。他持有多项专利。他还发表了许多书籍和文章,并代表 IBM 出席技术会议和贸易展示。他是美国奥斯汀的德克萨斯大学的副教授。可以通过 feigenba@us.ibm.com 与 Barry 联系。