13.13 action的替代品

正如早先指出的那样,action()并不是我们对所有事进行分类后自动为handleEvent()调用的唯一方法。有三个其它的被调用的方法集,如果我们想捕捉某些类型的事件(键盘、鼠标和焦点事件),因此我们不得不过载规定的方法。这些方法是定义在基础类组件里,所以他们几乎在所有我们可能安放在窗体中的组件中都是有用的。然而,我们也注意到这种方法在Java 1.1版中是不被支持的,同样尽管我们可能注意到继承代码利用了这种方法,我们将会使用Java 1.1版的方法来代替(本章后面有详细介绍)。

  • action(Event evt, Object what) 当典型的事件针对组件发生(例如,当按下一个按钮或下拉列表项目被选中)时调用
  • keyDown(Event evt, int key) 当按键被按下,组件拥有焦点时调用。第二个自变量是按下的键并且是冗余的是从evt.key处复制来的
  • keyup(Event evt, int key) 当按键被释放,组件拥有焦点时调用
  • lostFocus(Event evt, Object what) 焦点从目标处移开时调用。通常,what是从evt.arg里冗余复制的
  • gotFocus(Event evt, Object what) 焦点移动到目标时调用
  • mouseDown(Event evt, int x,int y) 一个鼠标按下存在于组件之上,在X,Y座标处时调用
  • mouseUp(Event evt, int x, int y) 一个鼠标升起存在于组件之上时调用
  • mouseMove(Event evt, int x, int y) 当鼠标在组件上移动时调用
  • mouseDrag(Event evt, int x, int y) 鼠标在一次mouseDown事件发生后拖动。所有拖动事件都会报告给内部发生了mouseDown事件的那个组件,直到遇到一次mouseUp为止
  • mouseEnter(Event evt, int x, int y) 鼠标从前不在组件上方,但目前在
  • mouseExit(Event evt, int x, int y) 鼠标曾经位于组件上方,但目前不在

当我们处理特殊情况时——一个鼠标事件,例如,它恰好是我们想得到的鼠标事件存在的座标,我们将看到每个程序接收一个事件连同一些我们所需要的信息。有趣的是,当组件的handleEvent()调用这些方法时(典型的事例),附加的自变量总是多余的因为它们包含在事件对象里。事实上,如果我们观察component.handleEvent()的源代码,我们能发现它显然将增加的自变量抽出事件对象(这可能是考虑到在一些语言中无效率的编码,但请记住Java的焦点是安全的,不必担心。)试验对我们表明这些事件事实上在被调用并且作为一个有趣的尝试是值得创建一个过载每个方法的程序片,(action()的过载在本章的其它地方)当事件发生时显示它们的相关数据。

这个例子同样向我们展示了怎样制造自己的按钮对象,因为它是作为目标的所有事件权益来使用。我可能会首先(也是必须的)假设制造一个新的按钮,我们从按钮处继承。但它并不能运行。取而代之的是,我们从画布组件处(一个非常普通组件)继承,并在其上不使用paint()方法画出一个按钮。正如我们所看到的,自从一些代码混入到画按钮中去,按钮根本就不运行,这实在是太糟糕了。(如果您不相信我,试图在例子中为画布组件交换按钮,请记住调用称为super的基础类构建器。我们会看到按钮不会被画出,事件也不会被处理。)

myButton类是明确说明的:它只和一个自动事件(AutoEvent)“父窗口”一起运行(父窗口不是一个基础类,它是按钮创建和存在的窗口。)。通过这个知识,myButton可能进入到父窗口并且处理它的文字字段,必然就能将状态信息写入到父窗口的字段里。当然这是一种非常有限的解决方法,myButton仅能在连结AutoEvent时被使用。这种代码有时称为“高度结合”。但是,制造myButton更需要很多的不是为例子(和可能为我们将写的一些程序片)担保的努力。再者,请注意下面的代码使用了Java 1.1版不支持的API。

//: AutoEvent.java
// Alternatives to action()
import java.awt.*;
import java.applet.*;
import java.util.*;
class MyButton extends Canvas {
  AutoEvent parent;
  Color color;
  String label;
  MyButton(AutoEvent parent, 
           Color color, String label) {
    this.label = label;
    this.parent = parent;
    this.color = color;
  }
  public void paint(Graphics  g) {
    g.setColor(color);
    int rnd = 30;
    g.fillRoundRect(0, 0, size().width, 
                    size().height, rnd, rnd);
    g.setColor(Color.black);
    g.drawRoundRect(0, 0, size().width, 
                    size().height, rnd, rnd);
    FontMetrics fm = g.getFontMetrics();
    int width = fm.stringWidth(label);
    int height = fm.getHeight();
    int ascent = fm.getAscent();
    int leading = fm.getLeading();
    int horizMargin = (size().width - width)/2;
    int verMargin = (size().height - height)/2;
    g.setColor(Color.white);
    g.drawString(label, horizMargin, 
                 verMargin + ascent + leading);
  }
  public boolean keyDown(Event evt, int key) {
    TextField t = 
      (TextField)parent.h.get("keyDown");
    t.setText(evt.toString());
    return true;
  }
  public boolean keyUp(Event evt, int key) {
    TextField t = 
      (TextField)parent.h.get("keyUp");
    t.setText(evt.toString());
    return true;
  }
  public boolean lostFocus(Event evt, Object w) {
    TextField t = 
      (TextField)parent.h.get("lostFocus");
    t.setText(evt.toString());
    return true;
  }
  public boolean gotFocus(Event evt, Object w) {
    TextField t = 
      (TextField)parent.h.get("gotFocus");
    t.setText(evt.toString());
    return true;
  }
  public boolean 
  mouseDown(Event evt,int x,int y) {
    TextField t = 
      (TextField)parent.h.get("mouseDown");
    t.setText(evt.toString());
    return true;
  }
  public boolean 
  mouseDrag(Event evt,int x,int y) {
    TextField t = 
      (TextField)parent.h.get("mouseDrag");
    t.setText(evt.toString());
    return true;
  }
  public boolean 
  mouseEnter(Event evt,int x,int y) {
    TextField t = 
      (TextField)parent.h.get("mouseEnter");
    t.setText(evt.toString());
    return true;
  }
  public boolean 
  mouseExit(Event evt,int x,int y) {
    TextField t = 
      (TextField)parent.h.get("mouseExit");
    t.setText(evt.toString());
    return true;
  }
  public boolean 
  mouseMove(Event evt,int x,int y) {
    TextField t = 
      (TextField)parent.h.get("mouseMove");
    t.setText(evt.toString());
    return true;
  }
  public boolean mouseUp(Event evt,int x,int y) {
    TextField t = 
      (TextField)parent.h.get("mouseUp");
    t.setText(evt.toString());
    return true;
  }
}
public class AutoEvent extends Applet {
  Hashtable h = new Hashtable();
  String[] event = {
    "keyDown", "keyUp", "lostFocus", 
    "gotFocus", "mouseDown", "mouseUp", 
    "mouseMove", "mouseDrag", "mouseEnter", 
    "mouseExit"
  };
  MyButton 
    b1 = new MyButton(this, Color.blue, "test1"),
    b2 = new MyButton(this, Color.red, "test2");
  public void init() {
    setLayout(new GridLayout(event.length+1,2));
    for(int i = 0; i < event.length; i++) {
      TextField t = new TextField();
      t.setEditable(false);
      add(new Label(event[i], Label.CENTER)); 
      add(t);
      h.put(event[i], t);
    }
    add(b1);
    add(b2);
  }
} ///:~

我们可以看到构建器使用利用自变量同名的方法,所以自变量被赋值,并且使用this来区分:this.label = label;

paint()方法由简单的开始:它用按钮的颜色填充了一个“圆角矩形”,然后画了一个黑线围绕它。请注意size()的使用决定了组件的宽度和长度(当然,是像素)。这之后,paint()看起来非常的复杂,因为有大量的预测去计算出怎样利用“font metrics”集中按钮的标签到按钮里。我们能得到一个相当好的关于继续关注方法调用的主意,它将程序中那些相当平凡的代码挑出,当我们想集中一个标签到一些组件里时,我们正好可以对它进行剪切和粘贴。

您直到注意到AutoEvent类才能正确地理解keyDown(),keyUp()及其它方法的运行。这包含一个Hashtable(译者注:散列表)去控制字符串来描述关于事件处理的事件和TextField类型。当然,这些能被静态的创建而不是放入Hashtable但我认为您会同意它是更容易使用和改变的。特别是,如果我们需要在AutoEvent中增加或删除一个新的事件类型,我们只需要简单地在事件列队中增加或删除一个字符串——所有的工作都自动地完成了。

我们查出在keyDown(),keyup()及其它方法中的字符串的位置回到myButton中。这些方法中的任何一个都用父句柄试图回到父窗口。父类是一个AutoEvent,它包含Hashtable h和get()方法,当拥有特定的字符串时,将对一个我们知道的TextField对象产生一个句柄(因此它被选派到那)。然后事件对象修改显示在TextField中的字符串陈述。从我们可以真正注意到举出的例子在我们的程序中运行事件时以来,可以发现这个例子运行起来颇为有趣的。