类 DrawingContext 提供对 java.awt.Graphics 绘图服务的访问,还提供下列附加服务:
缩放:每个 DrawingContext 提供逻辑窗口区域和逻辑框架区域。这些区域目前只限于一个正方形,但它可以简单地扩展为任意矩形。应用程序可以将框架区域与显示的框架大小相关联。 DrawingContext 实例将自动缩放对象,使窗口范围显示在一个框架大小的区域内。
平移:每个 DrawingContext 提供窗口原点。原点弥补了 java.awt.Graphics 的 x 和 y 方向与普通数学方向之间的差别。在 Java 语言中,x 从左到右递增,y 从上到下递增。在数学中,x 从左到右递增,y 从下到上递增。而且,移动原点产生扫视显示的对象的效果。
清单 12 是类 DrawingContext 的定义。
清单 12. 类 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 = 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 = Math.sin(step), cos = 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, 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 }
63
原点和缩放访问方法(没有在清单中显示)允许动态更改通过 DrawingContext 显示的 DrawableSequencer 。每种绘图服务( draw(...) )调用一个或多个 java.awt.Graphics 服务从而在实际上描绘对象。特别的, drawSprite(...) 多次调用 Graphics.drawLine() 。 drawText(...) 服务必须进行特殊处理来弥补 Java 语言文本字体有限的缩放能力。
服务类
drawables 包提供并使用两个简单服务类,类 Scale 和类 PositiveRandom 。
类 Scale 提供服务来缩放各种数据类型。清单 13 显示了类 Scale 的接口。
清单 13. 类 Scale
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 String toString();
9 public 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 }
16
类 PositiveRandom 产生限制在 0:limit-1 (包含边界)范围的随机数字。清单 14 显示了类 PositiveRandom 的接口。
清单 14. 类 PositiveRandom
2 {
3 public PositiveRandom(int limit);
4 public PositiveRandom();
5 public int next(); // get a random number (0:limit-1)
6 }
7
备用方法
显示在 清单 8 中的 DrawableSequencer 仅仅是一个绘图引擎示例。可以构建其它序列器,它们可能基于 java.util.Map 接口。例如,如果不需要文件的持久性和其它 DrawableSequencer 服务(诸如缩放和平移),那么可以用简单的 Java 数组来代替序列器,如清单 15 所示。
清单 15. 简单的绘图应用程序
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 }
28
在一个完全不同的实现中,RGO 可以定义为 Java 接口而不是 Java 类。这样允许 RGO 充当在应用程序中使用的其它类型。然后,通常每个 RGO 类型都会有一个缺省实现(例如, DrawableLine 接口可以有 DefaultDrawableLine 或者 DrawableLineImpl 类)。
也可能设计备用类层次结构。例如,考虑如表 2 中所示的层次结构。
表 2. 备用类层次结构类名 属性 Drawableabstract, public DrawableCommentpublic PersistentDrawableabstract, public DrawablePointpublic ScaleableDrawableabstract, public DrawableSequencerpublic DrawableSpritepublic ColoredDrawableabstract, public DrawableBoxpublic DrawableLinepublic DrawableOvalpublic DrawableRectFormabstract, public DrawableBoxpublic DrawableOvalpublic DrawableTextpublic
这种组织允许创建非持久性的可绘制对象(诸如 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 的子类添加。