创 建 用 户 组 件

让 我 们 来 看 一 下 产 生 一 个 专 用 的 组 件, 重 用 它 们 和 在 内 部 事 件 处 理 中 保 持 你 的 应 用 级 别 代 码 的 简 洁 是 多 么 容 易!


摘 要

让 我 们 来 正 视 它: 抽 象 窗 口 类AWT 的 功 能 并 不 象 我 们 希 望 的 一 样。 的 确, 它 提 供 了 一 定 数 量 有 用 的 组 件, 但 是 通 常 被 限 制 在 一 定 的 范 围 之 内。 并 且 有 些 时 候 这 些 限 制 不 能 消 除, 如 果 你 需 要 功 能 更 复 杂 的 组 件, 而 你 又 不 知 道 怎 样 生 成 它 们, 那 么 这 篇 文 章 就 是 来 告 诉 你 该 怎 样 做。

在 这 个 月 的“step by step” 中, 我 们 将 把 使 用AWT 类 库 中 的 类 ,生 成 用 户 组 件 这 一 问 题 作 为 我 们 的 基 础, 这 种 方 法 允 许 我 们 不 需 要 从 最 底 层 做 起 就 可 以 生 成 一 个 高 性 能 的 组 件。 另 外 的 好 处 就 是 我 们 这 种 技 术 还 允 许 我 们 把 高 级 事 件 和 内 部 的, 低 级 的 事 件 分 离 开。 这 样 可 以 解 放 我 们 的 应 用 程 序 仅 去 抓 住 那 些 对 组 件 的 外 部 行 为 有 意 义 的 事 件。

使 用Java API 提 供 的 的 标 准 用 户 界 面 元 素, 许 多 应 用 程 序 就 可 以 很 好 的 完 成 很 多 工 作。 必 要 时, 程 序 员 可 以 把 这 些 元 素 组 合 成 更 高 级 复 杂 的 界 面。 然 而, 如 果 增 加 处 理 组 件 集 合 和 事 件 处 理 来 获 得 高 级 用 户 界 面 功 能( 如 象 一 个 字 体 选 择 器 这 样 的 单 一 概 念 的 用 户 事 件), 那 么 在 应 用 级 的 事 件 处 理 将 变 得 十 分 麻 烦。

让 我 们 看 看 上 面 例 子, 我 们 考 虑 一 个 需 要 字 体 选 择 器 的 应 用 程 序, 为 了 实 现 这 种 功 能, 我 们 需 要 三 个 分 离 的 组 件 来 分 别 取 得 字 体 的 名 称, 风 格 和 字 体 的 大 小。 应 用 程 序 的 事 件 处 理 代 码 必 须 具 备 这 三 个 组 件 的 筛 选 器。 这 就 是 整 个 内 部 事 件 的 跟 踪 处 理。 相 反 如 果 我 们 构 建 一 个 不 同 于 上 面 的 字 体 选 择 器, 那 么 这 种 固 有 内 部 事 件 处 理 就 被 隐 藏 了 起 来。 这 个 应 用 程 序 只 需 捕 捉 到 一 个 字 体 选 择 器 事 件 对 象, 事 件 的 目 标 将 是 示 例 字 体 选 择 器 对 象 本 身 - - 这 是 一 个 多 么 简 洁 的 方 法。

非 常 明 显 的, 用 户 组 件 的 一 个 重 要 牲 是 能 够 在 应 用 中 隐 藏 细 节 的 能 力, 但 是 可 重 复 使 用 也 是 导 致 构 造 一 个 用 户 组 件 的 原 因。 当 你 发 现 你 需 要 一 遍 又 一 遍 的 实 现 某 种 特 别 的 功 能, 你 就 会 想 要 为 这 一 工 作 构 造 一 个 用 户 组 件 了, 更 进 一 步 的 话, 你 可 以 把 重 复 使 用 的 专 门 界 面 元 素 结 合 到 用 户 组 件 包(custom component package) 中 去, 使 得 这 个 类 库 在 一 组 开 发 者 中 特 别 容 易 重 用。

现 在, 在 你 为 每 个 可 能 的 组 件 变 化 编 写 代 码 之 前, 我 必 须 提 到 保 留 一 个 关 于 用 户 组 件 的 警 告。 当 你 正 在 开 发applet 时 候, 你 的 用 户 组 件 类 库 必 须 被 下 载。 这 是 因 为 它 们 不 是 所 有Java 虚 拟 机 的 局 部 可 用 的 内 核API 的 一 部 分: 但 是 只 要 你 的applet 不 用 很 多 的 用 户 类, 你 不 用 担 心 这 类 问 题, 但 是 如 果 最 佳 的 下 载 时 间 是 紧 要 的, 那 么 你 必 须 十 分 确 定 这 种 功 能 提 高 的 必 要 性。

用 户 组 件 中 的 事 件 处 理
书 写 专 门 组 件 的 一 个 真 正 的 优 点 是 你 的 应 用 级 事 件 处 理 将 非 常 简 洁。 在Component 父 类 中 有 一 个postEvent 方 法, 你 可 以 用 来 向 父 容 器 中 传 送 事 件。 这 个Event 对 象 将 包 含 容 器 所 需 的 决 定 事 件 细 节 问 题 的 所 有 信 息。

Event 对 象 包 含 了 一 些 参 数(target, arg, date, x, and y) 这 些 参 数 代 表 了 诸 如 哪 个 组 件 生 成 了 事 件 和 动 作 的 时 间。 arg 参 数 实 际 上 是 事 件 本 身 的“masage”。 其 类 型 为 对 象, 它 意 味 着 可 以 代 表 任 何 类 型 的 信 息。 举 例 来 说, 对 于Checkbox 类, 使 用 这 个 域 来 存 储 与cheekbox 状 态 一 致 的boolean ( 布 尔 量), 通 过 它 允 许 通 知 应 用 程 序Checkbox 的 新 状 态。 在 字 体 选 择 器 中, 组 件 需 送 达 到 应 用 程 序 的 消 息 是 用 户 选 择 的 字 体 对 象。

现 在 普 遍 是 用 适 当 类 型 的arg 参 数 传 递 事 件。 然 后 应 用 程 序 可 以 查 看 哪 一 个 组 件 产 生 了 这 一 事 件, 将arg 参 数 构 造 成 适 当 的 对 象 类 型。( 如 在 字 体 选 择 器 中 就 是 一 个Font), 以 及 是 谁 发 出 的 事 件。 现 在 让 我 们 来 看 一 下 实 际 实 现。

书 写 组 合 框 事 件 处 理 代 码
一 个Windows 风 格 的 组 合 框(combo box), 是 一 个 非 常 有 用 的 用 户 界 面 组 件, 这 在Java 核API 中 是 缺 少 的。 因 为 组 合 框 是 我 们 仍 需 要 的 工 具, 而 一 个 这 样 的 要 求 会 引 起 内 部 事 件 处 理 的 麻 烦, 所 以 它 是 创 建 用 户 组 件 工 具 箱 中 的 一 个 很 好 的 候 选 项。 为 了 建 立 我 们 自 己 的 组 合 框, 我 们 可 以 使 用 标 准 的TextFieldList 对 象 一 起 工 作。 从 每 个 标 准 组 件 得 来 的 事 件 都 需 要 内 部 处 理, 当 用 户 做 了 选 择 时, 一 个 新 的Event 对 象 就 产 生 了, 并 且 将 通 过 带 有 合 适 串 的arg 参 数 来 传 送。

正 如 你 从 如 下 片 断 看 到 的,comboBox 继 承 了Panel 类. 这 意 味 着 我 们 有 了 简 洁 的 捕 捉 事 件 的 方 法,PaneL 是combo box 元 素 的 容 器。 为 了 使 这 个 组 件 对 应 用 程 序 是 有 用 的, 我 们 希 望 隐 藏 这 些 实 现 以 便 使 应 用 系 统 只 需 显 示ComboBox, 把 条 目 加 上 去, 或 者 把 它 加 入 到 一 个 容 器。 有 意 义 的 事 件 希 望 在 适 当 的 时 候 被 生 成, 从 这 种 观 点 来 看 应 用 程 序 使 用combo box 的 话, 只 能 生 成 一 个 事 件, 那 就 是 用 户 选 择 的 列 表 中 项 目 的 事 件。


import java.awt.*;

package my.components;

public class ComboBox extends Panel {

   TextField text;

   List list;

   public ComboBox() {

      text = new TextField();

      list = new List();

      list.setMultipleSelections(false);

      setLayout(new BorderLayout());

      add("North", text);

      add("Center", list);

   } // end constructor

我 们 注 意 到 这 里 是TextFieldList 对 象 的 全 局 引 用。 在 构 造 函 数 中, 基 本 组 件 被 实 例 化, 并 以 border style 风 格 排 列, 这 样 使 得 它 们 平 均 排 列。 列 表(List) 被 设 置 成 同 一 时 间 只 允 许 一 个 选 择。


   public void addItem(String item) {

      list.addItem(item);

   }

创 建 组 合 框 的 应 用 程 序 使 用 这 个 类 的addItem 方 法 增 加 条 目 到 组 合 框 中。 这 些 条 目 必 须 出 现 在 组 合 框 初 始 时 的 下 拉 列 表 中, 所 以 调 用 了 列 表 的addItem 方 法。

现 在 让 我 们 看 一 下 类 中 真 正 有 趣 的 部 分 - - 事 件 处 理:


   public boolean handleEvent(Event e) {

     if ((e.id == Event.ACTION_EVENT) && (e.target == text)) {

        postEvent(new Event(this, Event.ACTION_EVENT, text.getText())); 

     }

     else if (e.id == Event.LIST_SELECT) {

        text.setText(list.getSelectedItem());

        text.requestFocus();

     }

     else if (e.id == Event.KEY_ACTION) {

        if ((e.key == Event.UP) && (list.getSelectedIndex() > 0)) {

           list.select(list.getSelectedIndex() - 1);

           text.setText(list.getSelectedItem());

        }

        if ((e.key == Event.DOWN) && (list.getSelectedIndex() <list.countItems())) 
       { list.select(list.getSelectedIndex() + 1); 
        text.setText(list.getSelectedItem());
        }
       } 
       else if (e.id="=" Event.MOUSE_ENTER)
       { text.requestFocus(); } 
	return(super.handleEvent(e)); } // end handleEvent 
} // end ComboBox 

在 方 法 handleEvent 中, 我 们 需 要 测 试5 种 事 件, 一 个 文 本 框 中 的 动 作 事 件, 一 个 列 表 选 择, 一 个 向 上 箭 头 键 按 下, 一 个 向 下 箭 头 键 按 下, 一 个 鼠 标 器 的 按 下。 最 后 的 四 个 事 件 是 内 部 事 件, 不 应 被 传 输 到 应 用 程 序, 它 们 只 在 组 件 的 内 部 行 为 上 下 文 中 有 效。

当 用 户 点 中 了 列 表 条 目, 那 么 它 被 放 置 在 文 本 框 中, 然 后 得 到 一 个 输 入 焦 点, 如 果 用 户 按 下 了 向 上 或 向 下 箭 头 键, 则 选 择 列 表 的 条 目 就 发 生 变 化, 文 本 框 就 设 置 成 新 的 值。MOUSE_ENTER 事 件 是 被 监 视 的, 文 本 框 被 自 动 的 给 予 输 入 焦 点, 使 组 件 更 方 便 用 户 的 使 用, 这 就 意 味 着 用 户 在 使 用combo Box 时 不 用 击 中 任 何 地 方。

当 用 户 在 文 本 框 中 按 下 回 车 键, 那 意 味 着 已 经 做 出 了 最 后 的 选 择, 当 前 值 做 为 传 送 事 件 被 送 往 调 用 应 用 程 序。 一 个 新 的 带 有 目 标 是this (ComboBox 类 本 身) 的 事 件 对 象 创 建 了, 具 有 一 个ACTION_EVENT 的 标 志id 和 一 个 文 本 框 中 文 本 的arg 参 数, 我 在 这 里 使 用 的 构 造 函 数 如 下:


Event(Component target, int Event.id, Object arg);

使 用combo Box 的 应 用 程 序 将 检 查ACTION_EVENT, 通 过arg 参 数 可 获 得 的 其 值。

实 现 我 们 所 学 的
现 在 组 合 框 组 件 可 以 在 应 用 程 序 中 使 用。 下 面 的 这 段 小 应 用 程 序 代 码 证 明 了 它 的 使 用。 为 了 完 备 的 原 因, 我 还 用 了 其 它 组 件( 标 签label, 一 些 按 钮label, 文 本 区 域text area), 它 们 可 以 和 组 合 框 结 合 用 来 创 建 界 面。


import java.awt.*;

import java.applet.*;

import my.components.ComboBox;

public class Test extends Applet {

   TextArea textarea;

   public void init() {

      setLayout(new BorderLayout());

      ComboBox cb = new ComboBox();

      cb.addItem("One");

      cb.addItem("Two");

      cb.addItem("Three");

      cb.addItem("Four");

      cb.addItem("Five");

      Label l = new Label("This is a test of the combo box...");

      Panel p = new Panel();

      p.setLayout(new GridLayout(2, 3));

      p.add(new Button("One"));

      p.add(new Button("Two"));

      p.add(new Button("Three"));

      p.add(new Button("Four"));

      p.add(new Button("Five"));

      p.add(new Button("Six"));

      textarea = new TextArea();

      add("East", cb);

      add("Center", textarea);

      add("North", l);

      add("South", p);

   } // end init

   public boolean handleEvent(Event e) {

      if ((e.id == Event.ACTION_EVENT) && (e.target instanceof ComboBox)) {

         textarea.appendText("ComboBox yielded: " + (String)e.arg + "\n");

      }

      return(super.handleEvent(e));

   } // end handleEvent

} // end Test

让 我 们 看 一 下 这 儿 发 生 了 什 么。 首 先, 创 建 了 组 合 框, 加 入 了 条 目。 接 着 组 件 加 入 到 小 应 用 程 序 中, 就 象 加 入 其 它 组 件 一 样。( 把 用 户 组 件 分 组 放 入 单 独 的 程 序 包package 中, 使 得 它 们 可 以 被 其 它 程 序 员 轻 易 使 用。) 如 果 你 想 创 建 一 个 真 正 可 重 用 的 库, 使 你 的API 和 核 心API 一 样, 我 建 议 你 使 用 java 文 档 生 成 器(javadoc documentation generator) 要 求 的 注 解 格 式。

学 以 致 用
你 一 旦 理 解 了 这 种 技 术, 你 会 发 现 生 成 一 个 用 户 组 件 非 常 容 易。 你 可 能 还 想 生 成 一 些 其 它 组 件, 如 自 动 排 序 列 表 等,您 不 妨自 己 试 试 看