JAVA学习网首页 把生活查询网加入收藏 把JAVA学习网设为首页 联系方式
    Hi,JAVA学习
JAVA基础 设计模式 数据库 JavaBeans J2EE JavaDelphi 用户界面 综合文章  
保留的图形对象(Retained Graphic Objects)API 简介
时间:22/04/2007
作者:网络
来源:网络
小提示点这里把文章加入您的收藏夹,方便下次查看
设置文章字体大小:[ ]
创建您自己的 Java 平台可重用图形对象 
Barry Feigenbaum(feigenba@us.ibm.com)
高级 IT 咨询专家,IBM
2001 年 11 月

Java 专家 Barry Feigenbaum 创建了保留的图形对象(Retained Graphic Objects (RGO))API 来启用 Java 平台的可重用图形对象的创建。本文中,Barry 描述了组成 RGO API 的类和方法,并在 Java 编程环境中演示了它们的使用。遵照本文中的上机实践示例,您将学会如何创建并保留诸如直线、椭圆、矩形、文本、子图形等对象类型。
创建持久(或者保留的)图形的能力还不是“抽象窗口工具箱(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 
许多其它图形对象类型,比如位图、三角形和常规的多边形可以根据需要添加。

使用 drawables
图 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
在以下各节中,我将描述用来定义 RGO 编程框架(也称为 API)的类和方法。本文中讨论的所有类都在缺省 Java 包中。源码可以从本文结尾处的链接访问(请参阅参考资料)。

有两种不同的方法来持久地存储 Drawable 对象。第一种方法使用 Java 平台的 Serializable 接口和 ObjectStreams,该方法需要的代码量最少。这种方法中,每个 Drawable 对象和所有可从该方法获得的对象都必须实现 Serializable 接口。第二种方法(称为“自存储”,或者显式地为持久字段的存储编码)需要更多编码,但产生的持久文件要小得多(通常不到那些使用 Serializable 生成的文件大小的一半)。在保存复杂图形时,这个大小差别是很重要的。例如,比较存储包含 25 个测试 DrawableText 对象的图形所需的空间 — 使用 Serializable 接口创建的文件大小为 2,636 字节,而使用“自存储”方法创建的文件大小为 1,284 字节。

在“自存储”方法中,持久保存的 drawables 继承了 PersistentDrawable 类。每个带新数据字段的 PersistentDrawable 子类必须定义 storeOn() 和 loadFrom() 方法。

我将在下面的相关类描述中进一步解释持久对象存储的这两种方法。请参阅侧栏“持久对象结构”来查看“自存储” RGO 的布局。

drawables 包
在示例 drawables 包中找到的类是以表 1 中显示的层次结构组织起来的。

表 1. drawables 包的类层次结构

类名 属性 实现方法 
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() 方法,而允许一个子类一个子类地进行特定格式编排。

持久对象结构
DrawableSequencer 对象以简单线性结构持久地存储,如下所示: 
int 集合中对象的计数


每个 Drawable 对象以递增的优先级排序 
每个 Drawable 对象的构成如下所示:

int 优先级 
Object 对象 



每个 Object 对象的构成如下所示:

Drawable 子类名称,格式为 UTF-8。(请参阅下面的注释。)


对象的颜色值:三个 byte 颜色组件值,顺序为红、绿、蓝。


对象的缩放比(M:N):两个 int 值,顺序先 M,后 N。


对象的可见性:一个 byte 值,0 表示假,1 表示真。


其它依赖子类的值。数字存储的形式为 byte、short、int、或者 long 值。String 以 UTF-8 格式存储。结构化的对象(例如,java.awt.Point 的实例)通过存储它们的相关字段来存储。 



注:UTF-8 格式基于 Unicode 值,将 Unicode (16 位)字符编码为 1、2 或者 3 字节序列。在范围 0x0000:0x007F 中的字符(普通的 ASCII)为 1 字节。在范围 0x0080:0x07FF 中的字符为 2 字节。在范围 0x0800:0xFFFF 中的字符为 3 字节。每个 UTF-8 字符串以字节值的简短计数为前缀。
 
构造器类型
drawables 包中的每个类可以支持几种构造器类型:

缺省构造器:这是一个不带参数的构造器。缺省构造器用于类 Drawable 的所有非抽象子类来支持持久对象的重新装入。如果在参数没有提供信息的情况下无法使对象处于可用状态,特殊的类就可能不提供缺省构造器。


普通构造器:这个构造器提供在实例创建时指定的所有参数。


备用构造器:这个构造器提供在实例创建时指定的参数子集(通常是常用的那些)。请注意,一些类不能提供备用构造器。


复制构造器:这个构造器通过复制相同类的另一个实例来初始化新创建的对象。 clone() 方法使用它。这样,它就可以被提供 clone() 方法的任意类所使用。 



访问方法
访问方法提供了对重要的私有(private)或者受保护的(protected)实例变量的公开(public)访问。要获取变量值,可以使用 get<VariableName>() 访问方法。要设置变量值,可以使用 set<VariableName>(...) 访问方法。通常,设置访问方法复制它的参数。这样就避免了类对任意传递对象的后继更改。一个例外是在 DrawableSequencer 中保存 Drawable 对象。访问方法经常声明为 final,以允许编译器优化它们的性能。

类 Drawable
类 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
  1. {
  2.    protected Color   _color;    // object color
  3.    protected boolean _visible;  // object is shown
  4.    protected Scale   _scaler;   // scale by this
  5.    protected Drawable(Color color, boolean visible, Scale scale) {
  6.      _color = new Color(color.getRGB());
  7.      _visible = visible;
  8.      _scaler = (Scale)scale.clone();
  9.    }
  10.    // draw myself
  11.    public final void draw(DrawingContext context) {
  12.      if(_visible) drawWorker(context);
  13.    }
  14.    abstract protected void drawWorker(DrawingContext context);
  15.    // save myself persistently
  16.    public void storeOn(DataOutputStream dos) throws IOException {
  17.      dos.writeByte(_color.getRed());
  18.      dos.writeByte(_color.getGreen());
  19.      dos.writeByte(_color.getBlue());
  20.      dos.writeInt(_scaler.multBy); dos.writeInt(_scaler.divBy);
  21.      dos.writeBoolean(_visible);
  22.    }
  23.    // reload myself from persistent storage
  24.    public void loadFrom(DataInputStream dis) throws IOException {
  25.      _color = new Color(s.readByte(), is.readByte(), dis.readByte());
  26.      _scaler = new Scale(is.readInt(), dis.readInt());
  27.      _visible = dis.readBoolean();
  28.    }
  29. }


 

类 Drawable 的子类
本节中总结的 Drawable 的可描绘子类将要提供如何编码可绘制对象的示例。它们不一定是绘图应用程序需要的所有类型对象的代表。可以在包含的源文件中找到下面描述的所有子类。

类 DrawableText
类 DrawableText 定义一行水平文本。DrawableText 对象是 java.awt.Graphics.drawString 函数的直接表示。可以设置文本的开始位置、前景色和字体大小。实际所用的字体大小是为活动 Java 字体定义的最接近字体大小。如果所期望的字体太小(2 点或者更小),那么将无法绘制文本。

DrawableText 从 Drawable 继承而来。它定义 drawWorker(...) 方法。这个方法,象所有其它 Drawable 子类 drawWorker() 方法一样,只是调用类 DrawingContext 参数的服务方法。

清单 2. 类 
  1. DrawableText public class DrawableText extends Drawable {
  2.    protected Point  _start;     // coordinates
  3.    protected java/lang/String.java.html" target="_blank">String _text;
  4.    protected int    _fontSize;  // display size
  5.    // draw myself
  6.    public void drawWorker(DrawingContext context) {
  7.      context.drawText(_scaler.scale(_start), _text, _scaler.scale(_fontSize), _color);
  8.    }
  9. }


 

类 DrawableLine
类 DrawableLine 定义了一条直线。DrawableLine 对象是 java.awt.Graphics.drawLine 函数的直接表示。可以设置直线的开始和结束位置以及颜色。如上所述, DrawableLine 的 drawWorker() 方法调用类 DrawingContext 参数的服务方法。这适用于 Drawable 的所有子类的 drawWorker() 方法。清单 3 是类 DrawableLine 的定义。

清单 3. 类 
  1. DrawableLine public class DrawableLine extends Drawable
  2. {
  3.    protected Point _start, _end; // coordinates
  4.    // draw myself
  5.    public void drawWorker(DrawingContext context) {
  6.      context.drawLine(_scaler.scale(_start), _scaler.scale(_end), _color);
  7.    }
  8. }


 

类 DrawableSprite
类 DrawableSprite 定义了一个 36 条直线的集合,这 36 条直线从一个公共中心点向外延伸,相邻两线之间的夹角为 10 度。每条直线有随意选择的不同颜色。因此忽略了继承的颜色值。可以设置中心点和直线的长度。 DrawableSprite 对象是由一些 java.awt.Graphics.drawLine 图元函数构成的。它显示 Drawable 子类不受 java.awt.Graphics 服务直接表示的限制。清单 4 是类 DrawableSprite 的定义。

清单 4. 类 
  1. DrawableSprite public class DrawableSprite extends Drawable
  2. {
  3.    protected Point _center;   // coordinates
  4.    protected int   _length;   // line length
  5.    // draw myself
  6.    public void drawWorker(DrawingContext context) {
  7.      context.drawSprite(_scaler.scale(_center), _scaler.scale(_length));
  8.    }
  9. }


 

DrawableSprite 的另一个实现是使用 DrawableLine 实例的集合。在这种情况下,不需要 DrawingContext.drawSprite() 方法。清单 5 显示了修改过的类 DrawableSprite 定义。这里,直线包含在实例中而不是由 DisplayContext 服务方法绘制。因为在对象中有多条直线,每条直线都必须由函数性方法处理。

清单 5. 类 
  1. DrawableSprite 的数组实现 public class DrawableSprite extends Drawable
  2. {
  3.    private int numlines = 36;
  4.    protected DrawableLine[] _lines = new DrawableLine[numlines];
  5.    public DrawableSprite(Point center, int lengthboolean visible, Scale scale) {
  6.      super(Color.white, visible, scale);
  7.      double step;
  8.      int i;
  9.      PositiveRandom rgb = new PositiveRandom(256);
  10.      // make radial lines every 2Pi/numlines radians;
  11.      // each line is a different color
  12.      for(i = 0, step = 0 * java/lang/Math.java.html" target="_blank">Math.PI;
  13.          i < numlines;
  14.          i++, step += java/lang/Math.java.html" target="_blank">Math.PI / numlines) {
  15.        double sin = java/lang/Math.java.html" target="_blank">Math.sin(step), cos = java/lang/Math.java.html" target="_blank">Math.cos(step);
  16.        DrawableLine line = new DrawableLine(
  17.            Point(center.x + (int)((length / 10) * cos),
  18.                  center.y - (int)((length / 10) * sin)),
  19.            Point(center.x + (int)( length * cos),
  20.                  center.y - (int)( length * sin)),
  21.            new Color(rgb.next(), rgb.next(), rgb.next()), true, _scaler);
  22.        _lines[i] = line;
  23.      }
  24.    }
  25.    public void setScale(Scale scale) {
  26.      super.setScale(scale);
  27.      // scale each line
  28.      for(int i = 0; i < numlines; i++) {
  29.        _lines[i].setScale(scale);
  30.      }
  31.    }
  32.    // draw myself
  33.    public void drawWorker(DrawingContext context) {
  34.      // draw each line
  35.      for(i = 0; i < numlines; i++) {
  36.        _lines[i].draw(context);
  37.      }
  38.    }
  39.    // save myself
  40.    public void storeOn(DataOutputStream dos) throws IOException {
  41.      super.storeOn(dos);
  42.      // save each line
  43.      dos.writeInt(numlines);
  44.      for(int i = 0; i < numlines; i++) {
  45.        _lines[i].storeOn(dos);
  46.      }
  47.    }
  48.    // reload myself
  49.    public void loadFrom(DataInputStream dis) throws IOException {
  50.      super.loadFrom(dis);
  51.      // load each line
  52.      numlines = dis.readInt(numlines);
  53.      _lines = new DrawableLine[numlines];
  54.      for(int i = 0, i < numlines; i++) {
  55.        _lines[i] = new DrawableLine();
  56.        _line[i].loadFrom(dis);
  57.      }
  58.    }
  59. }


 

loadFrom(...) 和 storeTo(...) 方法将从 Drawable 继承来的方法扩展为分别装入和保存该类引入的附加字段。

另一种实现是使用 DrawableSequencer 而不是数组来保留这些直线。这将不需要使用 for 循环来迭代每条直线。这个解决方案将需要 DrawableSequencer 从 Drawable 继承。

类 DrawableRectForm
类 DrawableRectForm 为可以由边界矩形框描述的可绘制对象定义了抽象模型。示例有实心和空心圆、椭圆以及矩形。可以设置对象的边角位置、宽度、高度和颜色。

清单 6 显示了类 DrawableRectForm 的定义。由于 DrawableRectForm 是抽象的,所以不需要为它实现 drawWorker(...) 方法。

清单 6. 类 DrawableRectForm public abstract class DrawableRectForm extends 
  1. Drawable
  2. {
  3.    protected Rectangle _area; // coordinates & size
  4. }


 

类 DrawableBox 和类 DrawableOval
类 DrawableBox 和类 DrawableOval 分别定义了实心矩形和圆形(或者椭圆形)对象。DrawableBox 对象是 java.awt.Graphics.fillRect 函数的直接表示;DrawableOval 对象是 java.awt.Graphics.fillOval 函数的直接表示。可以设置对象的边角位置、宽度、高度和颜色。

清单 7 显示了类 DrawableBox 和类 DrawableOval 的定义。由于这两个类都从 DrawableRectForm 继承而来并且没有添加新的实例变量,因此它们不需要定义 loadFrom(...) 和 storeTo(...) 方法。

清单 7. 类 DrawableBox 和类 
  1. DrawableOval public class DrawableBox extends DrawableRectForm
  2. {
  3.    // draw myself
  4.    public void drawWorker(DrawingContext context) {
  5.      context.drawBox(_scaler.scale(_area), _color);
  6.    }
  7. }
  8. public class DrawableOval extends DrawableRectForm
  9. {
  10.    // draw myself
  11.    public void drawWorker(DrawingContext context) {
  12.      context.drawOval(_scaler.scale(_area), _color);
  13.    }
  14. }


 

DrawableSequencer 类
其名称以 DrawableSequencer 开始的类负责创建并维护 Drawable 对象的有序集。这个集合可以编辑、可以在 DrawingContext 上描绘、可以存储到文件中,或者从文件装入。

每个对象根据绘制优先级排序。优先级较低(在数学意义上)的对象排列在优先级较高的对象后面。如果优先级较高的对象不透明,那么它将隐藏紧跟在它后面的优先级较低的对象。多个对象可能有相同的优先级。不指定相同优先级对象的相对绘图顺序。

DrawableSequencer 的编辑能力包括添加对象、除去对象或者除去所有的对象。通过除去对象,然后根据不同的优先级进行添加来重新排列对象。

DrawableSequencer 支持符合 java.util.Enumeration 的对象的创建。可以使用它们访问包含在序列中的对象。提供了两个枚举类型:

无限制:集合中的所有 Drawable 对象将按照优先级顺序被查看。


限制范围:其优先级值在指定的值范围之间(包括边界值)的集合中的所有 Drawable 对象按照优先级顺序被查看。如果高值小于低值,那么将看不到对象。 
存储方法
当使用自存储持久性时,DrawableSequencer 提供服务来将它的对象持久地存储到文件中,并在稍后重新装入它们。重新装入一组已保存对象的集合意味着根据优先级将它们添加到现有的序列中。要用文件中的对象替换序列,必须首先从序列中除去所有对象。

当使用 Java 的 ObjectStream 持久性时,DrawableSequencer 提供了静态 loadFrom() 和 storeIn() 方法,它们使用 ObjectStreams 来装入和存储 Drawables。这个机制总是替代现有的可绘制对象的内容。

在“自存储”方法中,每个对象通过它实例上的方法装入或者存储其本身。在 ObjectStream 持久性方法中,每个对象由外部应用的逻辑来保存或装入。因此,我更喜欢“自存储”方法。

在下面各节中,我们将讨论与类 DrawableSequencer 有关的每个类的定义和用法。

类 DrawableSequencer
类 DrawableSequencer 提供了上述支持。清单 8 是类 DrawableSequencer 的定义。

清单 8. 类 
  1. DrawableSequencer public class DrawableSequencer implements java/lang/Cloneable.java.html" target="_blank">Cloneable
  2. {
  3.    private Vector _items; // ordered items in list
  4.    // add new drawable
  5.    public void addAt(Drawable drawable, int priority) {
  6.      int i;
  7.      // insert element at proper priority; higher priorities come last
  8.      for(i = 0; i < _items.size(); i++) {
  9.        if(priority <= dso.priority) // past all lower priorities elements
  10.          break;
  11.      }
  12.      _items.insertElementAt(new DrawableSequencerObject(priority, drawable), i);
  13.    }
  14.    // remove all drawables
  15.    public void removeAll() {
  16.      _items.removeAllElements();
  17.    }
  18.    // remove a drawable
  19.    public void remove(Drawable drawable) throws NoSuchElementException {
  20.      int i, size = _items.size();
  21.      // find element to remove
  22.      for(i = 0; i < size; i++) {
  23.        DrawableSequencerObject dso =
  24.          (DrawableSequencerObject)_items.elementAt(i);
  25.        if(drawable == dso.drawable) // must be the same (vs equal)
  26.           break;
  27.      }
  28.      if(i < size) // found one
  29.        _items.removeElementAt(i);
  30.      else
  31.        throw new NoSuchElementException(
  32.          "DrawableSequencer.remove() - drawable not found");
  33.    }
  34.    // get enumerator for all
  35.    public Enumeration elements() {
  36.      return elements(java/lang/Integer.java.html" target="_blank">Integer.MIN_VALUE, java/lang/Integer.java.html" target="_blank">Integer.MAX_VALUE);
  37.    }
  38.    // get enumerator for range
  39.    public Enumeration elements(int low, int high) {
  40.      return new DrawableSequencerEnumerator(_items, low, high);
  41.    }
  42.    // draw all matching drawables
  43.    protected void drawSelected(DrawingContext context, Enumeration e) {
  44.      // walk all drawables, and draw them
  45.      while(e.hasMoreElements()) {
  46.        Drawable d = (Drawable)e.nextElement();
  47.        d.draw(context); // draw current drawable
  48.      }
  49.    }
  50.    // draw all drawables
  51.    public void draw(DrawingContext context) {
  52.      drawSelected(context, elements());
  53.    }
  54.    // draw selected drawables
  55.    public void draw(DrawingContext context, int min, int max) {
  56.      drawSelected(context, elements(min, max));
  57.    }
  58. }
  59.  
  60. loadFrom() 和 storeOn() 方法为包含在 DrawableSequencer 中的 Drawable 对象提供持久性支持。当需要每个文件的单个序列时,文件形式是很方便的。流形式使用一个现有的流。这样多个序列可以存储在一个流上。
  61. 清单 9 定义类 DrawableSequencer 的持久性服务。
  62. 清单 9. 类 DrawableSequencer 的持久性服务 // store all on a stream
  63. public void storeOn(DataOutputStream dos) throws IOException {
  64.    // walk all drawables, and store them
  65.    Enumeration e = _items.elements();
  66.    dos.writeInt(_items.size());   // write total count
  67.    while(e.hasMoreElements()) {
  68.      DrawableSequencerObject dso =
  69.        (DrawableSequencerObject)e.nextElement();
  70.      Drawable drawable = dso.drawable;
  71.      int priority = dso.priority;
  72.      dos.writeInt(priority);     // write priority
  73.      dos.writeUTF(drawable.getClass().getName()); // then class name
  74.      drawable.storeOn(dos);      // then class instance variables
  75.      dos.flush();
  76.    }
  77. }
  78. // store all on a stream
  79. public void storeOn(File f) throws IOException {
  80.    FileOutputStream fos = new FileOutputStream(f);
  81.    BufferedOutputStream bos = new BufferedOutputStream(fos);
  82.    DataOutputStream dos = new DataOutputStream(bos);
  83.    storeOn(dos); // store myself
  84.    dos.close();
  85. }
  86. // load all from a stream
  87. public void loadFrom(DataInputStream dis) throws IOException,
  88.    java/lang/ClassNotFoundException.java.html" target="_blank">ClassNotFoundExceptionjava/lang/InstantiationException.java.html" target="_blank">InstantiationExceptionjava/lang/IllegalAccessException.java.html" target="_blank">IllegalAccessException {
  89.    int drawableCount; // number of objects in stream
  90.    // load all names
  91.    try {
  92.      // walk all stored drawables, and load them
  93.      drawableCount = dis.readInt();
  94.      for(int count = 0; count < drawableCount; count++) {
  95.        int priority = dis.readInt();
  96.        java/lang/String.java.html" target="_blank">String className = dis.readUTF();
  97.        java/lang/Class.java.html" target="_blank">Class classObject = java/lang/Class.java.html" target="_blank">Class.forName(className);
  98.        java/lang/Object.java.html" target="_blank">Object object = classObject.newInstance();
  99.        if(!(object instanceof Drawable))
  100.          throw new java/lang/ClassNotFoundException.java.html" target="_blank">ClassNotFoundException(className + " not a subclass of Drawable");
  101.        Drawable drawable = (Drawable)object;
  102.        drawable.loadFrom(dis);
  103.        addAt(drawable, priority);
  104.      }
  105.    }
  106.    catch(EOFException ex) {  // map exception
  107.      throw new java/lang/ClassNotFoundException.java.html" target="_blank">ClassNotFoundException(ex.getMessage());
  108.    }
  109. }
  110. // load all drawables from a file
  111. public void loadFrom(File f) throws IOException,
  112.    java/lang/ClassNotFoundException.java.html" target="_blank">ClassNotFoundExceptionjava/lang/InstantiationException.java.html" target="_blank">InstantiationExceptionjava/lang/IllegalAccessException.java.html" target="_blank">IllegalAccessException {
  113.    FileInputStream fis = new FileInputStream(f); // get a data stream to a file
  114.    BufferedInputStream bis = new BufferedInputStream(fis);
  115.    DataInputStream dis = new DataInputStream(bis);
  116.    loadFrom(dis); // load myself
  117.    dis.close();
  118. }


 

类 DrawableSequencerObject
类 DrawableSequencerObject 表示存储在 DrawableSequencer 中的信息。 它记录 Drawable 对象和它的优先级。这个类不是公用的,因为只能由 DrawableSequencer 使用它。清单 10 是类 DrawableSequencerObject 的定义。

清单 10. 类 
  1. DrawableSequencerObject final class DrawableSequencerObject implements java/lang/Cloneable.java.html" target="_blank">Cloneable
  2. {
  3.    public int priority;      // relative position selector
  4.    public Drawable drawable; // actual item to draw
  5. }
  6.  
  7. 类 DrawableSequencerEnumerator
  8. 类 DrawableSequencerEnumerator 提供了服务来迭代 DrawableSequencer 中的对象。本质上,它是 C 结构,并且记录鼠标的当前位置和选择范围的高低值。这个类不是公共的,因为它是由 DrawableSequencer 的方法创建的,并且符合公共接口 java.util.java/util/Enumeration.java.html" target="_blank">Enumeration 。
  9. 由于 DrawableSequencer 使用 java.util.java/util/Vector.java.html" target="_blank">Vector 来存放 Drawable 对象,所以枚举器只增加索引值来在序列中前进。不在期望范围内的值被跳过。索引总是在待访问序列的下一个对象上设置,或者越过最后一个对象设置。
  10. 清单 11 是类 DrawableSequencerEnumerator 的定义。
  11. 清单 11. 类 DrawableSequencerEnumerator class DrawableSequencerEnumerator implements Enumeration
  12. {
  13.    private int _index; // relative position selector
  14.    private Vector _vector; // enumerate over this
  15.    private int _low, _high; // range selectors
  16.    public DrawableSequencerEnumerator(Vector vector, int low, int high) {
  17.      _vector = vector;
  18.      _low = low; _high= high;
  19.      // advance while outside of range to set to first
  20.      for(_index = 0; _index < _vector.size(); _index++) {
  21.        int priority =
  22.          ((DrawableSequencerObject)_vector.elementAt(_index)).priority;
  23.        if(priority >= _low && priority <= _high) // in range
  24.          break;
  25.      }
  26.    }
  27.    // test for done
  28.    public boolean hasMoreElements() {
  29.      return _index < _vector.size();
  30.    }
  31.    // get current and advance
  32.    public java/lang/Object.java.html" target="_blank">Object nextElement() {
  33.      java/lang/Object.java.html" target="_blank">Object result =
  34.      ((DrawableSequencerObject)_vector.elementAt(_index)).drawable;
  35.      // advance while inside of range to set to next
  36.      for(_index++; _index < _vector.size(); _index++) {
  37.        int priority =
  38.          ((DrawableSequencerObject)_vector.elementAt(_index)).priority;
  39.        if(priority >= _low && priority <= _high) // in range
  40.          break;
  41.      }
  42.      return result;
  43.    }
  44. }


 

类 DrawingContext
类 DrawingContext 提供对 java.awt.Graphics 绘图服务的访问,还提供下列附加服务:

缩放:每个 DrawingContext 提供逻辑窗口区域和逻辑框架区域。这些区域目前只限于一个正方形,但它可以简单地扩展为任意矩形。应用程序可以将框架区域与显示的框架大小相关联。 DrawingContext 实例将自动缩放对象,使窗口范围显示在一个框架大小的区域内。


平移:每个 DrawingContext 提供窗口原点。原点弥补了 java.awt.Graphics 的 x 和 y 方向与普通数学方向之间的差别。在 Java 语言中,x 从左到右递增,y 从上到下递增。在数学中,x 从左到右递增,y 从下到上递增。而且,移动原点产生扫视显示的对象的效果。 
清单 12 是类 DrawingContext 的定义。

清单 12. 类 
  1. DrawingContext public class DrawingContext
  2. {
  3.    protected Graphics _graphics; // associated graphics context
  4.    protected Point _origin; // origin in space
  5.    protected Scale _scaler; // scale by this
  6.    public DrawingContext(Graphics graphics, Point origin, Scale scale) {
  7.      _graphics = graphics.create(); // local copy that can change
  8.      _origin = new Point(origin.x, origin.y);
  9.      _scaler = (Scale)scale.clone();
  10.    }
  11.    // do rectangle adjustments
  12.    protected Rectangle map(Rectangle r) {
  13.      Point xy = map(r.x, r.y), wh = _scaler.scale(r.width, r.height);
  14.      return new Rectangle(xy.x, xy.y, wh.x, wh.y);
  15.    }
  16.    // do translate and scale
  17.    protected Point map(int x, int y) {
  18.      Point mapped;
  19.      mapped = _scaler.scale(x, y);
  20.      mapped.x = _origin.x + mapped.x; // x increases towards screen right
  21.      mapped.y = _origin.y - mapped.y; // y increases towards screen bottom
  22.      return new Point(mapped.x, mapped.y);
  23.    }
  24.    public void drawLine(Point start, Point end, Color color) {
  25.      _graphics.setColor(color);
  26.      Point s = map(start), e = map(end);
  27.      _graphics.drawLine(s.x, s.y, e.x, e.y);
  28.    }
  29.    public void drawBox(Rectangle area, Color color) {
  30.      // similar to drawLine above but uses _graphics.fillRect
  31.    }
  32.    public void drawOval(Rectangle area, Color color) {
  33.      // similar to drawLine above but uses _graphics.fillOval
  34.    }
  35.    public void drawSprite(Point center, int length) {
  36.      double Pi = java/lang/Math.java.html" target="_blank">Math.PI;
  37.      PositiveRandom rgb = new PositiveRandom(256);
  38.      Point c = map(center);
  39.      int l = _scaler.scale(length);
  40.      // make radial lines every 2Pi/36 radians
  41.      for(double step = 0 * Pi; step < 2 * Pi; step += Pi / 36) {
  42.        _graphics.setColor(new Color(rgb.next(), rgb.next(), rgb.next()));
  43.        double sin = java/lang/Math.java.html" target="_blank">Math.sin(step), cos = java/lang/Math.java.html" target="_blank">Math.cos(step);
  44.         _graphics.drawLine(c.x + (int)((l / 10) * cos), c.y - (int)((l / 10) * sin),
  45.                            c.x + (int)( l * cos), c.y - (int)( l * sin));
  46.      }
  47.    }
  48.    public void drawText(Point start, java/lang/String.java.html" target="_blank">String text, int fontSize, Color color) {
  49.      int newFontSize = _scaler.scale(fontSize);
  50.      if(newFontSize > 2) { // font can be seen
  51.        _graphics.setColor(color);
  52.        Font of = _graphics.getFont(); // scale the font
  53.        Font nf = new Font(of.getName(), of.getStyle(), newFontSize);
  54.        _graphics.setFont(nf);
  55.        Point p = map(start);
  56.        _graphics.drawString(text, p.x, p.y);
  57.      }
  58.    }
  59.    :
  60.    // other drawing services added here
  61.    :
  62. }


 

原点和缩放访问方法(没有在清单中显示)允许动态更改通过 DrawingContext 显示的 DrawableSequencer 。每种绘图服务(draw(...))调用一个或多个 java.awt.Graphics 服务从而在实际上描绘对象。特别的,drawSprite(...) 多次调用 Graphics.drawLine()。drawText(...) 服务必须进行特殊处理来弥补 Java 语言文本字体有限的缩放能力。

服务类
drawables 包提供并使用两个简单服务类,类 Scale 和类 PositiveRandom。

类 Scale 提供服务来缩放各种数据类型。清单 13 显示了类 Scale 的接口。

清单 13. 类 
  1. Scale public final class Scale implements java/lang/Cloneable.java.html" target="_blank">Cloneable
  2. {
  3.    public int multBy;
  4.    public int divBy;
  5.    public Scale(int m, int d);
  6.    public Scale();
  7.    public Scale(Scale other);
  8.    public java/lang/String.java.html" target="_blank">String toString();
  9.    public java/lang/Object.java.html" target="_blank">Object clone();
  10.    public int scale(int v);                 // scale a value
  11.    public Point scale(int x, int y);        // scale x, y pair
  12.    public Point scale(Point other);         // scale a point
  13.    public Rectangle scale(Rectangle other); // scale a rectangle
  14.    public Dimension scale(Dimension other); // scale an extent
  15. }


 

类 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. 简单的绘图应用程序 
  1. class SomeApplet extends Applet
  2. {
  3.    private static final int MAX_DRAWABLES = 100; // arbitrary
  4.    private Drawable[] drawables = new Drawable[MAX_DRAWABLES];
  5.    private int nextDrawable = 0;
  6.    // code to create a Drawable subclass
  7.    public void createDrawable(...) throws ... {
  8.      if(nextDrawable < length) {
  9.        Drawable d;
  10.        :
  11.        // code to make the drawable
  12.        :
  13.        drawables[nextDrawable++] = d; // record drawable
  14.      }
  15.      else
  16.        throw ...; // recover from full condition
  17.    }
  18.    // paint the collection
  19.    public void paint(Graphics g) {
  20.      DrawingContext dc = new DrawingContext(g);
  21.      // walk list of drawables
  22.      for(int i = 0; i < length; i++) {
  23.        if(drawables[i] != null)
  24.          drawables[i].draw(dc);
  25.      }
  26.    }
  27. }


 

在一个完全不同的实现中,RGO 可以定义为 Java 接口而不是 Java 类。这样允许 RGO 充当在应用程序中使用的其它类型。然后,通常每个 RGO 类型都会有一个缺省实现(例如,DrawableLine 接口可以有 DefaultDrawableLine 或者 DrawableLineImpl 类)。

也可能设计备用类层次结构。例如,考虑如表 2 中所示的层次结构。

表 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 所描述,每个实现所满足的功能性要求略有不同。

表 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 的子类添加。

参考资料

有效演示和源码

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 联系。 
上一篇:设置菜单快捷键

下一篇:对Java客户端的展望

  • 日文系统不能安装Jbuilder的问题解决(InstallAnyWhere问题)
  • BeanShell快速入门---Java应用程序脚本引擎
  • Java布局管理器深入讨论
  • 几点有关Web Services的技术学习总结
  • API解读:Thread 选择自 treeroot 的 Blog
  • Eclipse学习4-在Eclipse中使用JUnit进行单元测试(下)
  • Java技术实现数据库应用系统慨述
  • 一个画柱状图形的例子,在页面中显示
  • ant简介
  • java写的StringList类
  • Java2MicroEdition简介(二)
  • InatallationCVSinJbuilder5forwin2000
  • 从报表的绘制方式来谈谈:SQL报表,Cell报表和中国式报表.
  • Java回归嵌入式无线通信
  • JUnit和ant结合
  • 一个判断session是否过期的小技巧
  • readAppfuse-4-Sitemesh使用
  • 看JAVA序列化的性能 -- 一个简单的例子
  • 返回】 【顶部】 【关闭
    Copyright © 2005-2010 www.594k.com All Rights Reserved.
    版权所有:JAVA学习网 备案序号:皖ICP备06004238号