返回
JAVA世界1997年第12期

描绘我们的世界:用Java1.1建立联网的白板

孙国泉 编译

  摘 要 图 形 化 编 程 随 着JDK1.1 的 发 布 已 经 大 大 地 简 化 了。 特 别 是 轻 巧 的 构 件 结 构 允 许 透 明 的 工 具 互 相 覆 盖, 代 表 制 度 的 事 件 结 构 提 供 了 一 种 更 为 简 单 的 事 件 模 型。

  本 文 将 讨 论 用 这 些 新 特 征 实 现 一 个 简 单 的 白 板。

  在 这 类 应 用 中 特 别 有 意 思 的 是 我 们 用 轻 巧 的 构 件 提 供 给 白 板 很 简 单 的 对GUI 事 件 和 显 示 的 访 问。

  正 文:

  白 板 是 一 种 简 单 的 画 图 用 的 实 用 程 序, 一 般 作 为 协 作 结 构 的 一 部 分 提 供, 它 允 许 分 散 的 用 户 共 享 一 个 公 共 的 绘 画 空 间。 本 文 我 们 将 看 一 看JDK1.1 是 如 何 简 化 这 类 应 用 的 实 现 的。

  JDK1.1 引 进 的 轻 巧 的 构 件 比 传 统 的JDK1.0 类 型 的 构 件 有 一 些 重 要 的 优 点。 例 如, 因 为 它 们 不 用 对 等 的 本 地GUI, 而 在 原 有 的AWT 顶 上 用 纯JAVA 的" 杰 作", 所 以 它 们 比 传 统 的 构 件 效 率 更 高。 然 而 这 种 应 用 更 重 要 的 意 义 是 这 些 构 件 都 是 透 明 的。 这 意 味 着 一 个 轻 巧 的 构 件 能 很 容 易 地 覆 盖 另 一 个 构 件 上 的 信 息。

  我 们 将 用 这 种 透 明 性 的 特 征 来 实 现 我 们 的 白 板; 当 用 户 选 择 了 一 个 特 定 的 绘 画 工 具 后, 一 个 轻 巧 构 件 将 会 被 覆 盖 在 该 白 板 上。 然 后 选 中 的 绘 画 工 具 就 能 用 这 个 构 件 来 注 释 白 板 显 示 并 收 集GUI 事 件。

白 板

  我 们 的 白 板 必 然 是 很 简 单 的; 对 于 可 理 解 性 的 强 制 要 求 使 它 必 须 是 相 当 有 限 的 区 域。 然 而, 我 阐 述 的 技 术 很 有 希 望 有 广 泛 的 应 用, 并 将 使 你 能 很 容 易 地 修 改 应 用 来 满 足 你 自 己 的 需 要。

java121-1.gif (10181 字节)
图1: 白 板




  白 板 由 下 面 这 些 类 组 成:

  
  * WBLauncher―― 一 种 简 单 的applet , 当 点 按 鼠 标 时 它 载 入 白 板。
  * WB―― 主 要 的 白 板 类, 它 是 一 个 单 独 的 框, 包 含 了 所 有 的 白 板 构件。
  * Tool―― 是 一 种 提 供 对 所 有 白 板 绘 画 工 具 访 问 的 接 口。
  * Element―― 是 一 种 描 述 了 白 板 制 图 元 素 的 接 口。
  * WBContainer―― 一 种 显 示 白 板 内 容 的 轻 巧 的 容 器。
  * Rect―― 一 种 允 许 不 同 颜 色 的 矩 形 放 在 白 板 上 的 绘 画 工 具。
  * Select―― 一 种 允 许 移 动 白 板 上 已 有 元 素 的 绘 画 工 具。
  * ObservableList―― 一 种 维 护 组 成 白 板 图 画 的 元 素 表 的 类。 它 与 向 量 很相 似, 不 同 的 是 无 论 何 时 当 表 有 变 化 时, 它 通 知" 监 听 者"。
  * UpdateListener―― 是 一 种 当ObservableList 被 修 改 时 想 要 得 到 通 知 的 类 都必 须 采 用 的 接 口。
  * UpdateEvent―― 是 一 种 通 知ObservableList 变 化 的 事 件; 它 通 过ObservableList 接 口 来 传 送。
  * LWContainer―― 一 般 的 轻 巧 容 器; 相 当 于Panel 类。
  * LWImageButton―― 一 般 的 轻 巧 的 图 象 按 纽。

  这 样 看 起 来 有 好 多 类, 但 事 实 上 白 板 的 核 心 是 相 当 小 的。WB 类 包 含 了 白 板 的 控 制 逻 辑; WBContainer 类 包 含 了 显 示 代 码; 工 具 和 元 素 接 口 描 述 了 不 同 的 工 具 和 元 素。

类 的 详 细 解 释

  为 简 单 起 见, 我 们 用 通 用 的 术 语 来 讨 论 类, 这 样 你 能 更 好 地 理 解 代 码。

WBLauncher 类

  WBLauncher 是 一 种 简 单 的applet, 它 提 供 了 一 个 简 单 的 白 板 引 导 接 口, 延 迟 白 板 类 的 下 载 直 到 用 户 要 求 装 入。

java121-2.gif (13577 字节)
图2: 白 板 发 射 器(Launcher)

  因 为 我 们 使 用 的 是JDK 1.1 AWT, 我 们 必 须 明 确 地 在GUI 事 件 中 声 明 以 便 能 接 受 它 们。 由 于 这 个 原 因, 我 们 调 用enableEvents() 方 法 来 接 收 鼠 标 事 件:

  enableEvents (AWTEvent.MOUSE_EVENT_MASK);

  这 些 事 件 必 然 是 通 过 新 的 隶 属 于 能 处 理 这 个 事 件 的processEvent() 方 法 的 方 法 引 导 的。

  protected void processMouseEvent (MouseEvent e) ...

  在 这 第 二 个 方 法 中, 我 们 用Class.forName().newInstance() 顺 序 来 建 立 白 板 实 例。 用 这 种 机 制, 只 有 当 用 户 真 正 地 点 按 了 装 入 选 择 后 白 板 类 才 被 下 载:

Class theClass = Class.forName ("org.merlin.step.nov.WB");

wb = (Frame) theClass.newInstance ();


  为 了 初 始 化 白 板, 我 们 传 递 一 个 虚 拟 的ActionEvent 给 它 的dispatchEvent() 方 法, 并 把 它 作 为 事 件 源; 然 后 白 板 就 会 利 用 我 们 的Applet 方 法。 尤 其 是 它 能 调 用getCodeBase() 来 决 定 其 来 源, 然 后 能 下 载 它 的 设 置 和 图 象 文 件:

  wb.dispatchEvent (new ActionEvent (this, AWTEvent.RESERVED_ID_MAX + 1, ""));

WB 类

  WB 类 是 一 种 设 置 和 控 制 白 板 的 框 架 子 类。 在JDK1.1 中, 没 必 要 再 为 了 实 现 一 个 独 立 的 应 用 而 成 为 框 架 的 子 类; 然 而, 在 某 些 情 况 下 这 种 方 法 是 非 常 方 便 的。

java121-3.gif (3472 字节)

图3: 白 板 布 局


  作 为 一 种 构 件 子 类, 我 们 必 须 再 次 调 用enableEvents() 事 件 以 便 接 收 事 件; 在 这 儿 是 窗 口 事 件:

  enableEvents (AWTEvent.WINDOW_EVENT_MASK);

  这 些 窗 口 事 件 将 传 递 给processWindowEvent() 方 法, 我 们 能 用 这 个 方 法 来 操 纵 用 户 关 闭 窗 口:



protected void processWindowEvent (WindowEvent e) {

super.processWindowEvent (e);

if (e.getID () == WindowEvent.WINDOW_CLOSING)

setVisible (false);



}

  为 了 捕 获WBLauncher 传 递 过 来 初 始 化 白 板 的ActionEvent, 我 们 必 须 重 载processEvent() 方 法:

protected void processEvent (AWTEvent e) {

if ((e instanceof ActionEvent) && (e.getSource () instanceof Applet))

init ((Applet) e.getSource ());

else

super.processEvent (e);



}

  当 白 板 被 初 始 化 后, 它 下 载 了 一 个 简 单 的 设 置 文 件。 为 了 分 析 这 个 文 本 文 件, 我 们 用JDK1.1 中 新 的Reader 类, 它 们 比InputStream 类 更 有 效, 正 确, 并 且 通 用 于 阅 读 文 本。 我 们 用"latin1" 字 符 编 码, 即ISO Latin 1。



URL u = new URL (parent.getCodeBase (), "config.txt");

InputStreamReader i = new InputStreamReader (u.openStream (), "latin1");

BufferedReader r = new BufferedReader (i);

String line;

while ((line = r.readLine ()) != null) ...








  剩 下 的 初 始 化 工 作 只 是 简 单 地 布 置 用 户 接 口, 包 括 框 架 左 边 的 一 行 工 具 图 标, 底 下 的 工 具 控 制 板, 以 及 其 余 区 域 内 的 白 板。

  点 按 任 意 一 个 工 具 图 标 都 传 递 一 个ActionEvent 事 件 给 我 们 的actionPerformed() 方 法。 然 后 我 们 初 始 化 选 中 的 工 具, 显 示 它 的 控 制 板, 并 把 其 显 示 构 件 覆 盖 在 白 板 上, 如 下 所 示:

  public void actionPerformed (ActionEvent e) ...

  在 工 具 类 这 部 分 中 将 更 加 详 细 地 讨 论 白 板 工 具。

工 具 接 口

  工 具 接 口 描 述 了 白 板 的 绘 画 工 具, 并 且 声 明 了 四 种 方 法:setDisplayList, getDisplay,getControls 和 dispose 。 下 面 详 细 讲 述 这 些 方 法。

  (1)public void setDisplayList (ObservableList contents);

  当 初 始 化 某 个 工 具 时, 用 这 个 方 法 调 用 目 前 的 白 板 显 示 列 表( 组 成 白 板 绘 画 的 元 素 表) 来 支 持 它。 该 工 具 能 通 过 操 纵 显 示 列 表 中 的 元 素 来 操 纵 白 板。

  (2)public Component getDisplay ();

  getDisplay 方 法 返 回 一 个 构 件, 它 覆 盖 在 真 正 的 白 板 上。 绘 画 工 具 可 以 用 这 个 显 示 外 观, 提 供 被 覆 盖 的 信 息( 例 如, 特 别 是 画 好 的 显 示 元 素), 并 且 收 集 用 户 接 口 事 件, 例 如 用 户 点 按 白 板 或 拉 出 一 个 四 边 形。

  (3)public Component getControls ();

  getControls 方 法 返 回 一 个 构 件 或 控 制 工 具 容 器。 构 件 或 容 器 显 示 在 白 板 的 底 部。

  (4)public void dispose ();

  当 选 择 别 的 白 板 工 具 时, 我 们 调 用dispose() 方 法。 后 面 将 详 细 解 释 两 个 工 具 例 子:Rect 和 Select。

元 素 接 口

  元 素 接 口 描 述 了 白 板 中 的 每 一 个 绘 画 元 素。 不 同 的 元 素 一 般 都 由 不 同 的 绘 画 工 具 创 建 并 被 加 入 到 白 板 中。 为 了 联 网, 所 有 的 绘 画 元 素 必 须 是 可 串 行 化 的; 因 此, 我 们 扩 展 可 串 行 化 接 口:

  public interface Element extends java.io.Serializable ...

  我 们 的 白 板 非 常 简 单, 它 在 这 个 接 口 只 提 供 了 三 个 方 法:getBounds, paint 和 getTranslated。

  (1)public Rectangle getBounds ();

  getBounds 方 法 返 回 一 个 描 述 元 素 的 限 定 框 的 四 边 形。

  (2)public void paint (Graphics g);

  当 该 元 素 必 须 把 它 自 己 画 到 特 定 的 制 图 区 g 时 要 调 用 paint 方 法。

  (3)public Element getTranslated (int dX, int dY);

  最 后, getTranslated 方 法 返 回 该 元 素 的 一 个 副 本, 根 据 移 动 白 板 元 素 的 特 定 的 偏 移 量 进 行 转 换。

WBContainer 类

  WBContainer 类 是 一 种 轻 巧 的 显 示 白 板 目 前 的 内 容 的 容 器。 当 一 个 绘 画 工 具 被 激 活 时, 其 透 明 的 显 示 元 素 就 覆 盖 在 这 个 容 器 上。

  在 它 创 建 之 后,WBContainer 作 了 登 记, 以 便 无 论 何 时 白 板 显 示 表 有 变 化 时 它 都 能 得 到 通 知:

  displayList.addUpdateListener (this);

  无 论 何 时 有 修 改 时, 我 们 只 简 单 地 调 用repaint():



public void updateOccurred (UpdateEvent e) {

repaint ();

}






  paint() 方 法 循 环 调 用 在 显 示 表 中 的 每 个 元 素 的paint() 方 法。 我 们 用 每 个 元 素 的 限 定 框 和 图 形 剪 切 四 边 形 来 使 重 画 尽 可 能 地 有 效。

Rect 类

  Rect 是 一 种 非 常 简 单 的 允 许 用 户 画 不 同 颜 色 的 矩 形 的 白 板 工 具。 它 实 际 上 由 两 个 类 组 成:Rect( 事 实 上 的 工 具) 和 RectElement。Rect 实 现 了 工 具 接 口, 透 明 的 轻 巧 的 覆 盖 构 件 以 及 控 制 面 板; RectElement 是 一 种 四 边 形 元 素。

  作 为 开 始, 我 们 先 讨 论 工 具 接 口 方 法:

  setDisplayList() 保 持 对 所 提 供 的 显 示 表 的 引 用。 当 用 户 画 了 一 个 四 边 形 后, 我 们 加 入 一 个RectElement 到 这 个 表 中。

  getDisplay() 返 回 变 化。Rect 类 本 身 是 一 个 透 明 的 覆 盖 构 件。

  getControls() 方 法 返 回 一 个 新 的 轻 巧 的 面 板, 它 包 括 确 定 四 边 形 颜 色 用 的 颜 色 选 择。

  dispose() 方 法 是 空 的; 与 这 个 工 具 毫 无 关 系。

  当Rect 工 具 被 激 活 时, 它 自 动 地 覆 盖 在 白 板 上。 为 了 接 收AWT 事 件, 它 执 行 调 用enableEvents() 和 重 载 processEvent() 方 法 的 标 准 过 程。 我 们 不 必 要 知 道 如 何 画 白 板 的 其 余 部 分; 这 个 任 务 由 我 们 叠 加 在 其 上 的WBContainer 来 自 动 完 成。

  然 后 编 写Rect 工 具 就 是 简 单 地 写 一 个AWT 构 件; 把 它 集 成 到 白 板 中 的 工 作 是 透 明 的。

  最 后, 一 旦 把 一 个RectElement 加 到 显 示 表 中 后, 我 们 就 完 成 了 绘 画。WBContainer 将 自 动 地 通 知 这 个 变 化 并 适 当 地 重 画 自 己。

Select 类

  Select 工 具 比Rect 工 具 更 简 单, 它 只 是 一 种 实 现 工 具 接 口 的 类。Select 类 用 通 常 的 方 法 处 理 事 件, 用 简 单 的 方 法 来 操 纵 显 示 表, 这 些 方 法 用 元 素 的getTranslated() 方 法 来 提 供 和 移 动 元 素。

  它 在 本 质 上 只 是 一 种 操 纵 一 系 列 元 素 的 单 独 的AWT 构 件。 它 与 所 集 成 的 白 板 没 有 特 别 的 关 系, 因 为 显 示 表 的 改 变 由WBContainer 自 动 反 映。

ObservableList 类

  ObservableList 类 是 一 种 可 以 观 察 的 向 量。 对 它 感 兴 趣 的 监 听 程 序 通 过addUpdateListener() 和 removeUpdateListener() 方 法 注 册。 无 论 何 时 对 表 作 了 修 改, 监 听 程 序 都 会 通 知 到。 这 个 类 暂 且 用 一 个 内 部 向 量 实 现。 以 后 我 们 将 考 虑 用 这 个 类 把 白 板 联 网 的 不 同 机 制。

UpdateListener 接 口

  UpdateListener 接 口 是 通 知 监 听 程 序ObservableList 发 生 了 变 化 的 机 制。

  public void updateOccurred (UpdateEvent e);

  当 表 有 变 化 时 我 们 调 用updateOccurred。

  

UpdateEvent 类

  当 ObservableList 被 改 变 时 UpdateEvent 就 通 知 给 监 听 程 序。 这 个 事 件 目 前 没 有 什 么 值 得 讨 论 的; 涉 及 更 多 的 实 施 时 会 显 示 出 所 发 生 的 更 详 细 的 变 化。

LWContainer 类

  LWContainer 是 一 种 简 单 的 非 常 小 的 轻 巧 容 器; 我 们 只 是 扩 展 了Container 类, 没 提 供 附 加 的 方 法。 如 果 Container 类 不 是 抽 象 的 话, 这 个 方 法 等 于 使 用Container 类 本 身 的 实 例。

  LWContainer 能 比 传 统 的Panel 类 更 有 效 地 规 划 版 面。 因 为 它 是 纯JAVA 的, 它 不 需 要 一 个 相 对 应 的 本 地 的 对 等GUI 构 件。

LWImageButton 类

  LWImageButton 类 是 一 种 简 单 轻 巧 的 图 象 按 纽: 当 鼠 标 移 动 到 图 象 按 纽 上 时, 我 们 使 它 变 成 高 亮 度 的; 当 按 下 鼠 标 时, 我 们 降 低 图 象 按 纽; 当 鼠 标 按 纽 释 放 时, 我 们 启 动ActionEvent 事 件。

编 译 代 码

  编 译 自 己 的 应 用 代 码 实 际 上 非 常 简 单。 只 要 完 成 以 下 几 步:

  1. 下 载 本 文 资 源 部 分 中 所 列 的 完 整 代 码, 打 开 文 件。
  2. 转 到step 目 录。
  3. 用 下 面 的 命 令 编 译 代 码:
  javac org\merlin\step\nov\*.java
  ( 如 果 你 使 用 的 是using Unix, 注 意 在 命 令 行 用 斜 杠 而 不 是 反 斜 杠。)
  4. 建 立 如 下 所 示 的 index.html 文 件:



< HTML >< BODY >

< APPLET CODE="org.merlin.step.nov.WBLauncher" WIDTH=96 Height=96 >

< /APPLET >

< /BODY >< /HTML >






  注 意: 因 为 这 个applet 使 用JDK1.1 AWT, 你 只 能 用 完 全 兼 容JDK1.1 的 浏 览 器 来 观 看; 最 简 单 的 办 法 就 是 用JDK1.1 的appletviewer 或 HotJava。

  5. 用appletviewer 看applet:

  appletviewer index.html

结 论

  看 起 来 本 文 涉 及 很 多 的 东 西; 但 主 题 很 简 单:

  用 轻 巧 的 构 件 和 容 器 来 提 高 效 率。
  用 透 明 的 轻 巧 构 件 来 覆 盖 信 息 并 允 许 方 便 的 事 件 操 作。
  用 代 表 事 件 模 式 来 广 播 事 件, 不 用 再 到 处 建 面 板 的 子 类。
  对 非AWT 任 务 遵 守JDK1.1 的 事 件 模 式。

  这 些 工 作 的 主 要 目 标 是 不 必 再 紧 密 地 耦 合 应 用 中 不 同 的 类-- 白 板 的 各 种 构 件 中 唯 一 共 同 的 对 象 就 是 显 示 表。 实 现 了 这 些 目 标 后, 我 们 能 从 根 本 上 改 变 下 一 级 的 白 板, 并 且 不 必 去 注 意 各 种 工 具。 此 外, 控 制 代 码(WB 类) 并 不 重 要; 它 所 做 的 只 是 在 不 同 的 工 具 间 进 行 切 换。

  下 面 我 举 例 说 明 这 种 设 计 的 好 处。 为 了 容 纳 白 板 的 多 个 视 图, 我 们 所 要 做 的 只 是 在 别 的 什 么 地 方 建 立 另 一 些 视 图, 并 把 透 明 连 到 同 一 个 表 上。 我 们 已 有 的 代 码 不 需 作 任 何 改 变。

  这 方 法 是 一 种 简 单 优 秀 的 面 向 对 象 的 设 计。 在 你 的 应 用 中 使 用 这 种 技 术 后, 对 已 有 的 代 码 稍 作 改 动 就 能 很 容 易 地 扩 展 和 增 强 你 的 应 用。

  下 个 月 我 们 讨 论 把 代 码 连 到 网 上 使 分 散 的 用 户 能 访 问 相 同 的 绘 画 空 间。

文 章 来 源:http://www.javaworld.com/javaworld/jw-11-1997/jw-11-step.html