您的位置:首页 > 编程语言 > Java开发

53.java编程思想——创建窗口和程序片 新型AWT

2016-04-30 20:23 501 查看
53.java编程思想——创建窗口和程序片 新型AWT
在Java 1.1 中一个显著的改变就是完善了新AWT 的创新。大多数的改变围绕在Java 1.1 中使用的新事件模型:老的事件模型是糟糕的、笨拙的、非面向对象的,而新的事件模型可能是我所见过的最优秀的。难以理解一个如此糟糕的(老的AWT)和一个如此优秀的(新的事件模型)程序语言居然出自同一个集团之手。新的考虑事件的方法看来中止了,因此争议不再变成障碍,从而轻易进入我们的意识里;相反,它是一个帮助我们设计系统的工具。它同样是Java Beans
的精华,我们会在本章后面部分进入讲述。

新的方法设计对象做为“事件源”和“事件接收器”以代替老AWT 的非面向对象串联的条件语句。正象我们将看到的内部类的用途是集成面向对象的原始状态的新事件。另外,事件现在被描绘为在一个类体系以取代单一的类并且我们可以创建自己的事件类型。

我们同样会发现,如果我们采用老的AWT 编程,Java 1.1 版会产生一些看起来不合理的名字转换。例如,setsize()改成resize()。当我们学习Java Beans 时这会变得更加的合理,因为Beans 使用一个独特的命名协议。名字必须被修改以在Beans 中产生新的标准AWT 组件。剪贴板操作在Java 1.1 版中也得到支持,尽管拖放操作“将在新版本中被支持”。我们可能访问桌面色彩组织,所以我们的Java 可以同其余桌面保持一致。可以利用弹出式菜单,并且为图像和图形作了改进。也同样支持鼠标操作。还有简单的为打印的API
以及简单地支持滚动。

1     新的事件模型

在新的事件模型的组件可以开始一个事件。每种类型的事件被一个个别的类所描绘。当事件开始后,它受理一个或更多事件指明“接收器”。因此,事件源和处理事件的地址可以被分离。每个事件接收器都是执行特定的接收器类型接口的类对象。因此作为一个程序开发者,我们所要做的是创建接收器对象并且在被激活事件的组件中进行注册。event-firing 组件调用一个addXXXListener()方法来完成注册,以描述XXX 事件类型接受。我们可以容易地了解到以addListened
名的方法通知我们任何的事件类型都可以被处理,如果我们试图接收事件我们会发现编译时我们的错误。Java Beans 同样使用这种addListener 名的方法去判断那一个程序可以运行。

我们所有的事件逻辑将装入到一个接收器类中。当我们创建一个接收器类时唯一的一点限制是必须执行专用的接口。我们可以创建一个全局接收器类,这种情况在内部类中有助于被很好地使用,不仅仅是因为它们提供了一个理论上的接收器类组到它们服务的UI 或业务逻辑类中,但因为(正像我们将会在本章后面看到的)事实是一个内部类维持一个句柄到它的父对象,提供了一个很好的通过类和子系统边界的调用方法。一个简单的例子将使这一切变得清晰明确。

2     代码

import java.awt.*;

import java.awt.event.*;
// Must add this

import java.applet.*;

public
class
Button2New extends Applet {

    Button b1 =
new Button("Button 1"),
b2 = new Button("Button 2");

 

    public
void
init() {

        b1.addActionListener(new B1());

        b2.addActionListener(new B2());

        add(b1);

        add(b2);

    }

 

    class B1
implementsActionListener {

        public
void
actionPerformed(ActionEvent
e) {

            getAppletContext().showStatus("Button 1");

        }

    }

 

    class B2
implementsActionListener {

        public
void
actionPerformed(ActionEvent
e) {

            getAppletContext().showStatus("Button 2");

        }

    }

    /*

     * The old way: public boolean action(Event
evt,Object arg) {

     * if(evt.target.equals(b1))getAppletContext().showStatus("Button 1"); else

     * if(evt.target.equals(b2))getAppletContext().showStatus("Button 2"); //

     * Let the base class handle it: else 410return super.action(evt,
arg);

     * return true; // We've handled it here }

     */

} /// :~

3     执行

可比较两种方法,老的代码在左面作为注解。在init()方法里,只有一个改变就是增加了下面的两行:

b1.addActionListener(new B1());

b2.addActionListener(new B2());

按钮按下时,addActionListener()通知按钮对象被激活。B1 和B2 类都是执行接口ActionListener的内部类。这个接口包括一个单一的方法actionPerformed()(这意味着当事件激活时,这个动作将被执行)。注意actionPreformed()方法不是一个普通事件,说得更恰当些是一个特殊类型的事件,ActionEvent。如果我们想提取特殊ActionEvent 的信息,因此我们不需要故意去测试和下溯造型自变量。

对编程者来说一个最好的事便是actionPerformed()十分的简单易用。它是一个可以调用的方法。同老的action()方法比较,老的方法我们必须指出发生了什么和适当的动作,同样,我们会担心调用基础类action()的版本并且返回一个值去指明是否被处理。在新的事件模型中,我们知道所有事件测试推理自动进行,因此我们不必指出发生了什么;我们刚刚表示发生了什么,它就自动地完成了。如果我们还没有提出用新的方法覆盖老的方法,我们会很快提出。

4     事件和接收者类型

所有AWT 组件都被改变成包含addXXXListener()和removeXXXListener()方法,因此特定的接收器类型可从每个组件中增加和删除。我们会注意到“XXX”在每个场合中同样表示自变量的方法,例如,

addFooListener(FooListener fl)。。

事件,接收器接口及添加和删除方法 支持这个事件的组件一旦知道了一个特定的组件支持哪些事件,就不必再去寻找任何东西来响应那个事件。只需简单地:

(1) 取得事件类的名字,并删掉其中的“Event”字样。在剩下的部分加入“Listener”字样。这就是在我们的内部类里需要实现的接收器接口。

(2) 实现上面的接口,针对想要捕获的事件编写方法代码。例如,假设我们想捕获鼠标的移动,所以需要为MouseMotiionListener 接口的mouseMoved()方法编写代(当然还必须实现其他一些方法,但这里有捷径可循,马上就会讲到这个问题)。

(3) 为步骤2 中的接收器类创建一个对象。随自己的组件和方法完成对它的注册,方法是在接收器的名字里加入一个前缀“add”。比如addMouseMotionListener()。

4.1     用接收器适配器简化操作

注意到一些接收器接口只有唯一的一个方法。它们的执行是无轻重的,因为我们仅当需要书写特殊方法时才会执行它们。然而,接收器接口拥有多个方法,使用起来却不太友好。例如,我们必须一直运行某些事物,当我们创建一个应用程序时对帧提供一个WindowListener,以便当我们得到windowClosing()事件时可以调用System.exit(0)以退出应用程序。但因为WindowListener 是一个接口,我们必须执行其它所有的方法即使它们不运行任何事件。这真令人讨厌。

为了解决这个问题,每个拥有超过一个方法的接收器接口都可拥有适配器,它们的名我们可以在上面的表格中看到。每个适配器为每个接口方法提供默认的方法。(WindowAdapter 的默认方法不是windowClosing(),而是System.exit(0)方法。)此外我们所要做的就是从适配器处继承并过载唯一的需要变更的方法。例如,典型的WindowListener 我们会像下面这样的使用。

class MyWindowListener extendsWindowAdapter {

public voidwindowClosing(WindowEvent e) {

System.exit(0);

}

}

适配器的全部宗旨就是使接收器的创建变得更加简便。但所谓的“适配器”也有一个缺点,而且较难发觉。假定我们象上面那样写一个WindowAdapter :

class MyWindowListener extendsWindowAdapter {

public voidWindowClosing(WindowEvent e) {

System.exit(0);

}

}

表面上一切正常,但实际没有任何效果。每个事件的编译和运行都很正常——只是关闭窗口不会退出程序。

在方法的名字里:是WindowClosing(),而不是windowClosing()。大小写的一个简单失误就会造成一个崭新的方法。但是,这并非我们关闭窗口时调用的方法,所以当然没有任何效果。

4.2     用J a v a 1 . 1 A W T制作窗口和程序片

我们经常都需要创建一个类,使其既可作为一个窗口调用,亦可作为一个程序片调用。为做到这一点,只需为程序片简单地加入一个main()即可,令其在一个Frame(帧)里构建程序片的一个实例。作为一个简单的示例,下面让我们来看看如何对Button2New.java 作一番修改,使其能同时作为应用程序和程序片使用:

4.2.1       代码

import java.awt.*;

import java.awt.event.*;
// Must add this

import java.applet.*;

 

public
class
Button2NewB extends Applet {

    Button b1 =
new Button("Button 1"),
b2 = new Button("Button 2");

    TextField t =
new TextField(20);

 

    public
void
init() {

        b1.addActionListener(new B1());

        b2.addActionListener(new B2());

        add(b1);

        add(b2);

        add(t);

    }

 

    class B1
implementsActionListener {

        public
void
actionPerformed(ActionEvent
e) {

            t.setText("Button 1");

        }

    }

 

    class B2
implementsActionListener {

        public
void
actionPerformed(ActionEvent
e) {

            t.setText("Button 2");

        }

    }

 

    // To closethe application:

    static
class
WL extendsWindowAdapter {

        public
void
windowClosing(WindowEvent
e) {

            System.exit(0);

        }

    }

 

    // A main()for the application:

    public
staticvoid
main(String[]
args){

        Button2NewB applet =
new Button2NewB();

        Frame aFrame =
new Frame("Button2NewB");

        aFrame.addWindowListener(new WL());

        aFrame.add(applet, BorderLayout.CENTER);

        aFrame.setSize(300, 200);

        applet.init();

        applet.start();

        aFrame.setVisible(true);

    }

} /// :~

4.2.2       执行

内部类WL 和main()方法是加入程序片的唯一两个元素,程序片剩余的部分则原封未动。事实上,我们通常将WL 类和main()方法做一结小的改进复制和粘贴到我们自己的程序片里(请记住创建内部类时通常需要一个外部类来处理它,形成它静态地消除这个需要)。我们可以看到在main()方法里,程序片明确地初始化和开始,因为在这个例子里浏览器不能为我们有效地运行它。当然,这不会提供全部的浏览器调用stop()和destroy()的行为,但对大多数的情况而言它都是可接受的。如果它变成一个麻烦,我们可以:

(1) 使程序片句柄为一个静态类(以代替局部可变的main()),然后:

(2) 在我们调用System.exit()之前在WindowAdapter.windowClosing()中调用applet.stop()和applet.destroy()。

注意最后一行:

aFrame.setVisible(true);

这是Java 1.1 AWT 的一个改变。show()方法不再被支持,而setVisible(true)则取代了show()方法。当我们学习Java Beans 时,这些表面上易于改变的方法将会变得更加的合理。

这个例子同样被使用TextField 修改而不是显示到控制台或浏览器状态行上。在开发程序时有一个限制条件就是程序片和应用程序我们都必须根据它们的运行情况选择输入和输出结构。

这里展示了Java 1.1 AWT 的其它小的新功能。我们不再需要去使用有错误倾向的利用字符串指定BorderLayout 定位的方法。当我们增加一个元素到Java 1.1 版的BorderLayout 中时,我们可以这样写:

aFrame.add(applet,BorderLayout.CENTER);

我们对位置规定一个BorderLayout 的常数,以使它能在编译时被检验(而不是对老的结构悄悄地做不合适的事)。这是一个显著的改善。

4.3     将窗口接收器变成匿名类

任何一个接收器类都可作为一个匿名类执行,但这一直有个意外,那就是我们可能需要在其它场合使用它们的功能。但是,窗口接收器在这里仅作为关闭应用程序窗口来使用,因此我们可以安全地制造一个匿名类。

然后,main()中的下面这行代码:

aFrame.addWindowListener(newWL());

会变成:

aFrame.addWindowListener(

new WindowAdapter() {

public voidwindowClosing(WindowEvent e) {

System.exit(0);

}

});

这有一个优点就是它不需要其它的类名。我们必须对自己判断是否它使代码变得易于理解或者更难。匿名内部类将通常被使用在窗口接收器中。

4.4     将程序片封装到JAR 文件里

一个重要的JAR 应用就是完善程序片的装载。在Java 1.0 版中,人们倾向于试法将它们的代码填入到单个的程序片类里,因此客户只需要单个的服务器就可适合下载程序片代码。但这不仅使结果凌乱,难以阅读(当然维护也然)程序,但类文件一直不能压缩,因此下载从来没有快过。

JAR 文件将我们所有的被压缩的类文件打包到一个单个儿的文件中,再被浏览器下载。现在我们不需要创建一个糟糕的设计以最小化我们创建的类,并且用户将得到更快地下载速度。仔细想想上面的例子,这个例子看起来像Button2NewB,是一个单类,但事实上它包含三个内部类,因此共有四个。每当我们编译程序,我会用这行代码打包它到一个JAR 文件:

jar cf Button2NewB.jar *.class

这是假定只有一个类文件在当前目录中,其中之一来自Button2NewB.java(否则我们会得到特别的打包)。

现在我们可以创建一个使用新文件标签来指定JAR 文件的HTML 页,如下所示:

<head><title>Button2NewBExample Applet

</title></head>

<body><appletcode="Button2NewB.class"

archive="Button2NewB.jar"

width=200 height=150>

</applet>

</body>

与HTML 文件中的程序片标记有关的其他任何内容都保持不变。

4.5   以前的例子

为注意到一些利用新事件模型的例子和为学习程序从老到新事件模型改变的方法,下面的例子回到在利用事件模型来证明的一些争议。另外,每个程序包括程序片和应用程序现在都可以借助或不借助浏览器来运行。

4.5.1       代码2

import java.awt.*;

import java.awt.event.*;

import java.applet.*;

 

public
class
TextNew extends Applet {

    Button b1 =
new Button("Get Text"),
b2 = new Button("Set Text");

    TextField t1 =
new TextField(30),
t2 = new TextField(30),
t3 = new TextField(30);

    String s =
new String();

 

    public
void
init() {

        b1.addActionListener(new B1());

        b2.addActionListener(new B2());

        t1.addTextListener(new T1());

        t1.addActionListener(new T1A());

        t1.addKeyListener(new T1K());

        add(b1);

        add(b2);

        add(t1);

        add(t2);

        add(t3);

    }

    class T1
implementsTextListener {

        public
void
textValueChanged(TextEvent
e) {

            t2.setText(t1.getText());

        }

    }

    class T1A
implementsActionListener {

        private
int
count = 0;

        public
void
actionPerformed(ActionEvent
e) {

            t3.setText("t1 Action Event " +
count++);

        }

    }

    class T1K
extendsKeyAdapter {

        public
void
keyTyped(KeyEvent
e) {

            String ts =
t1.getText();

            if (e.getKeyChar() == KeyEvent.VK_BACK_SPACE) {

                // Ensure it's not empty:

                if (ts.length() > 0) {

                    ts =
ts.substring(0, ts.length()- 1);

                    t1.setText(ts);

                }

            } else

                t1.setText(t1.getText() + Character.toUpperCase(e.getKeyChar()));

            t1.setCaretPosition(t1.getText().length());

            // Stop regular character from appearing:

            e.consume();

        }

    }

    class B1
implementsActionListener {

        public
void
actionPerformed(ActionEvent
e) {

            s =
t1.getSelectedText();

            if (s.length() == 0)

                s =
t1.getText();

            t1.setEditable(true);

        }

    }

    class B2
implementsActionListener {

        public
void
actionPerformed(ActionEvent
e) {

            t1.setText("Inserted by Button 2: "+
s);

            t1.setEditable(false);

        }

    }

    public
staticvoid
main(String[]
args){

        TextNew applet =
new TextNew();

        Frame aFrame =
new Frame("TextNew");

        aFrame.addWindowListener(new WindowAdapter() {

            public
void
windowClosing(WindowEvent
e) {

                System.exit(0);

            }

        });

        aFrame.add(applet, BorderLayout.CENTER);

        aFrame.setSize(300, 200);

        applet.init();

        applet.start();

        aFrame.setVisible(true);

    }

} /// :~

4.5.2       执行

当TextField t1 的动作接收器被激活时,TextField t3 就是一个需要报告的场所。我们注意到仅当我们按下“enter”键时,动作接收器才会为“TextField”所激活。

TextField t1 附有几个接收器。T1 接收器从t1 复制所有文字到t2,强制所有字符串转换成大写。我们会发现这两个工作同是进行的,并且如果我们增加T1K 接收器后我们再增加T1 接收器,它就不那么重要:在文字字段内的所有的字符串将一直被强制变为大写。这看起来键盘事件一直在文字组件事件前被激活,并且如果我们需要保留t2 的字符串原来输入时的样子,我们就必须做一些特别的工作。

T1K 有着其它的一些有趣的活动。我们必须测试backspace(因为我们现在控制着每一个事件)并执行删除。

caret 必须被明确地设置到字段的结尾;否则它不会像我们希望的运行。最后,为了防止原来的字符串被默认的机制所处理,事件必须利用为事件对象而存在的consume()方法所“耗尽”。这会通知系统停止激活其余特殊事件的事件处理器。

这个例子同样无声地证明了设计内部类的带来的诸多优点。注意下面的内部类:

class T1 implements TextListener{

public voidtextValueChanged(TextEvent e) {

t2.setText(t1.getText());

}

}

t1 和t2 不属于T1 的一部分,并且到目前为止它们都是很容易理解的,没有任何的特殊限制。这是因为一个内部类的对象能自动地捕捉一个句柄到外部的创建它的对象那里,因此我们可以处理封装类对象的方法和内容。正像我们看到的,这十分方便。它也解决了“回调”的问题,不必为Java 加入任何令人恼火的“方法指针”特性。

4.5.3       代码3

Java 1.1 版中Text Area 最重要的改变就滚动条。对于TextArea 的构建器而言,我们可以立即控制TextArea 是否会拥有滚动条:水平的,垂直的,两者都有或者都没有。这个例子更正了前面Java 1.0 版TextArea1.java 程序片,演示了Java 1.1 版的滚动条构建器:

import java.awt.*;

import java.awt.event.*;

import java.applet.*;

public
class
TextAreaNew extends Applet {

    Button b1 =
new Button("Text Area 1");

    Button b2 =
new Button("Text Area 2");

    Button b3 =
new Button("Replace Text");

    Button b4 =
new Button("Insert Text");

    TextArea
t1 = new
TextArea("t1", 1, 30);

    TextArea
t2 = new
TextArea("t2", 4, 30);

    TextArea
t3 = new
TextArea("t3", 1, 30,
TextArea.SCROLLBARS_NONE);

    TextArea
t4 = new
TextArea("t4", 10, 10,
TextArea.SCROLLBARS_VERTICAL_ONLY);

    TextArea
t5 = new
TextArea("t5", 4, 30,
TextArea.SCROLLBARS_HORIZONTAL_ONLY);

    TextArea
t6 = new
TextArea("t6", 10, 10,
TextArea.SCROLLBARS_BOTH);

    public
void
init() {

        b1.addActionListener(new B1L());

        add(b1);

        add(t1);

        b2.addActionListener(new B2L());

        add(b2);

        add(t2);

        b3.addActionListener(new B3L());

        add(b3);

        b4.addActionListener(new B4L());

        add(b4);

        add(t3);

        add(t4);

        add(t5);

        add(t6);

    }

    class B1L
implementsActionListener {

        public
void
actionPerformed(ActionEvent
e) {

            t5.append(t1.getText() +
"\n");

        }

    }

    class B2L
implementsActionListener {

        public
void
actionPerformed(ActionEvent
e) {

            t2.setText("Inserted by Button 2");

            t2.append(": " +
t1.getText());

            t5.append(t2.getText() +
"\n");

        }

    }

    class B3L
implementsActionListener {

        public
void
actionPerformed(ActionEvent
e) {

            String s =
"Replacement ";

            t2.replaceRange(s, 3, 3 +
s.length());

        }

    }

    class B4L
implementsActionListener {

        public
void
actionPerformed(ActionEvent
e) {

            t2.insert(" Inserted ", 10);

        }

    }

    public
staticvoid
main(String[]
args){

        TextAreaNew applet =
new TextAreaNew();

        Frame aFrame =
new Frame("TextAreaNew");

        aFrame.addWindowListener(new WindowAdapter() {

            public
void
windowClosing(WindowEvent
e) {

                System.exit(0);

            }

        });

        aFrame.add(applet, BorderLayout.CENTER);

        aFrame.setSize(300, 725);

        applet.init();

        applet.start();

        aFrame.setVisible(true);

    }

} /// :~

4.5.4       执行

只能在构造TextArea 时能够控制滚动条。同样,即使TE AR 没有滚动条,我们滚动光标也将被制止(可通过运行这个例子中验证这种行为)。

4.5.5       代码4

复选框和单选钮都是同一个类建立的。单选钮和复选框略有不同,它是复选框安置到CheckboxGroup 中构成的。在其中任一种情况下,有趣的ItemEvent 事件为我们创建一个ItemListener 项目接收器。

当处理一组复选框或者单选钮时,我们有一个不错的选择。我们可以创建一个新的内部类去为每个复选框处理事件,或者创建一个内部类判断哪个复选框被单击并注册一个内部类单独的对象为每个复选对象。下面的例子演示了两种方法:

import java.awt.*;

import java.awt.event.*;

import java.applet.*;

public
class
RadioCheckNew extends Applet {

    TextField t =
new TextField(30);

    Checkbox[] cb = {
new Checkbox("Check Box 1"),
new Checkbox("Check Box 2"),newCheckbox("Check Box 3") };

    CheckboxGroup g =
new CheckboxGroup();

    Checkbox cb4 =
new Checkbox("four",
g, false),
cb5 = new Checkbox("five",g,
true),

            cb6 =
new Checkbox("six",
g, false);

 

    public
void
init() {

        t.setEditable(false);

        add(t);

        ILCheck il =
new ILCheck();

        for (int
i = 0; i <
cb.length;
i++) {

            cb[i].addItemListener(il);

            add(cb[i]);

        }

        cb4.addItemListener(new IL4());

        cb5.addItemListener(new IL5());

        cb6.addItemListener(new
IL6());

        add(cb4);

        add(cb5);

        add(cb6);

    }

 

    // Checkingthe source:

    class ILCheck
implements ItemListener {

        public
void
itemStateChanged(ItemEvent
e) {

            for (int
i = 0; i <
cb.length;
i++) {

                if (e.getSource().equals(cb[i])) {

                    t.setText("Check box " + (i + 1));

                    return;

                }

            }

        }

    }

    // vs.an individual class for each item:

    class IL4
implementsItemListener {

        public
void
itemStateChanged(ItemEvent
e) {

            t.setText("Radio button four");

        }

    }

    class IL5
implementsItemListener {

        public
void
itemStateChanged(ItemEvent
e) {

            t.setText("Radio button five");

        }

    }

    class IL6
implementsItemListener {

        public
void
itemStateChanged(ItemEvent
e) {

            t.setText("Radio button six");

        }

    }

 

    public
staticvoid
main(String[]
args){

        RadioCheckNew
applet = new RadioCheckNew();

        Frame aFrame =
new Frame("RadioCheckNew");

        aFrame.addWindowListener(new WindowAdapter() {

            public
void
windowClosing(WindowEvent
e) {

                System.exit(0);

            }

        });

        aFrame.add(applet, BorderLayout.CENTER);

        aFrame.setSize(300, 200);

        applet.init();

        applet.start();

        aFrame.setVisible(true);

    }

} /// :~

4.5.6       执行

ILCheck 拥有当我们增加或者减少复选框时自动调整的优点。当然,我们对单选钮使用这种方法也同样的好。但是,它仅当我们的逻辑足以普遍的支持这种方法时才会被使用。如果声明一个确定的信号——我们将重复利用独立的接收器类,否则我们将结束一串条件语句。

4.5.7       代码5

import java.awt.*;

import java.awt.event.*;

import java.applet.*;

public
class
ChoiceNew extends Applet {

    String[] description = {
"Ebullient", "Obtuse","Recalcitrant",
"Brilliant", "Somnescent",
"Timorous", "Florid",

            "Putrescent" };

    TextField t =
new TextField(100);

    Choice c =
new Choice();

    Button b =
new Button("Add items");

    int
count= 0;

    public
void
init() {

        t.setEditable(false);

        for (int
i = 0; i < 4;
i++)

            c.addItem(description[count++]);

        add(t);

        add(c);

        add(b);

        c.addItemListener(new CL());

        b.addActionListener(new BL());

    }

 

    class CL
implementsItemListener {

        public
void
itemStateChanged(ItemEvent
e) {

            t.setText("index: " +
c.getSelectedIndex() +
" " + e.toString());

        }

    }

 

    class BL
implementsActionListener {

        public
void
actionPerformed(ActionEvent
e) {

            if (count <
description.length)

                c.addItem(description[count++]);

        }

    }

 

    public
staticvoid
main(String[]
args){

        ChoiceNew applet =
new ChoiceNew();

        Frame aFrame =
new Frame("ChoiceNew");

        aFrame.addWindowListener(new WindowAdapter() {

            public
void
windowClosing(WindowEvent
e) {

                System.exit(0);

            }

        });

        aFrame.add(applet, BorderLayout.CENTER);

        aFrame.setSize(750, 100);

        applet.init();

        applet.start();

        aFrame.setVisible(true);

    }

} /// :~

4.5.8       代码6

我们消除了Java 1.0 中List 设计的一个缺陷,就是List 不能像我们希望的那样工作:它会与单击在一个列表元素上发生冲突。

import java.awt.*;

import java.awt.event.*;

import java.applet.*;

public
class
ListNew extends Applet {

    String[] flavors = {
"Chocolate", "Strawberry","Vanilla Fudge Swirl",
"Mint Chip", "Mocha Almond Fudge",

            "Rum Raisin",
"Praline Cream","Mud Pie" };

    // Show 6items, allow multiple selection:

    List lst =
new List(6, true);

    TextArea t =
new TextArea(flavors.length, 30);

    Button b =
new Button("test");

    int
count= 0;

 

    public
void
init() {

        t.setEditable(false);

        for (int
i = 0; i < 4;
i++)

            lst.addItem(flavors[count++]);

        add(t);

        add(lst);

        add(b);

        lst.addItemListener(new LL());

        b.addActionListener(new BL());

    }

 

    class LL
implementsItemListener {

        public
void
itemStateChanged(ItemEvent
e) {

            t.setText("");

            String[]
items = lst.getSelectedItems();

            for (int
i = 0; i <
items.length;
i++)

                t.append(items[i] +
"\n");

        }

    }

    class BL
implementsActionListener {

        public
void
actionPerformed(ActionEvent
e) {

            if (count <
flavors.length)

                lst.addItem(flavors[count++], 0);

        }

    }

    public
staticvoid
main(String[]
args){

        ListNew applet =
new ListNew();

        Frame aFrame =
new Frame("ListNew");

        aFrame.addWindowListener(new WindowAdapter() {

            public
void
windowClosing(WindowEvent
e) {

                System.exit(0);

            }

        });

        aFrame.add(applet, BorderLayout.CENTER);

        aFrame.setSize(300, 200);

        applet.init();

        applet.start();

        aFrame.setVisible(true);

    }

} /// :~

4.5.9       代码7

为菜单处理事件看起来受益于Java 1.1 版的事件模型,但Java 生成菜单的方法常常麻烦并且需要一些手工编写代码。生成菜单的正确方法看起来像资源而不是一些代码。请牢牢记住编程工具会广泛地为我们处理创建的菜单,因此这可以减少我们的痛苦(只要它们会同样处理维护任务!)。另外,我们将发现菜单不支持并且将导致混乱的事件:菜单项使用ActionListeners(动作接收器),但复选框菜单项使用ItemListeners(项目接收器)。菜单对象同样能支持ActionListeners(动作接收器),但通常不那么有用。一般来说,我们会附加接收器到每个菜单项或复选框菜单项,但下面的例子(对先前例子的修改)演示了一个联合捕捉多个菜单组件到一个单独的接收器类的方法。正像我们将看到的,它或许不值得为这而激烈地争论。

import java.awt.*;

import java.awt.event.*;

public
class
MenuNew extends Frame {

    String[] flavors = {
"Chocolate", "Strawberry","Vanilla Fudge Swirl",
"Mint Chip", "Mocha Almond Fudge",

            "Rum Raisin",
"Praline Cream","Mud Pie" };

    TextField t =
new TextField("No flavor", 30);

    MenuBar mb1 =
new MenuBar();

    Menu f =
new
Menu("File");

    Menu m =
new
Menu("Flavors");

    Menu s =
new
Menu("Safety");

    // Alternativeapproach:

    CheckboxMenuItem[]
safety = { new CheckboxMenuItem("Guard"),
new CheckboxMenuItem("Hide")};

    MenuItem[] file = {

            // No menu shortcut:

            new MenuItem("Open"),

            // Adding a menu shortcut is very simple:

            new MenuItem("Exit",
new MenuShortcut(KeyEvent.VK_E)) };

    // A secondmenu bar to swap to:

    MenuBar mb2 =
new MenuBar();

    Menu fooBar =
new Menu("fooBar");

    MenuItem[] other = {
new MenuItem("Foo"),
new MenuItem("Bar"),
new MenuItem("Baz"), };

    //Initialization code:

    {

        ML ml =
new ML();

        CMIL
cmil = newCMIL();

        safety[0].setActionCommand("Guard");

        safety[0].addItemListener(cmil);

        safety[1].setActionCommand("Hide");

        safety[1].addItemListener(cmil);

        file[0].setActionCommand("Open");

        file[0].addActionListener(ml);

        file[1].setActionCommand("Exit");

        file[1].addActionListener(ml);

        other[0].addActionListener(new FooL());

        other[1].addActionListener(new BarL());

        other[2].addActionListener(new BazL());

    }

    Button b =
new Button("Swap Menus");

    public MenuNew() {

        FL fl =
new FL();

        for (int
i = 0; i <
flavors.length;
i++) {

            MenuItem
mi = new MenuItem(flavors[i]);

            mi.addActionListener(fl);

            m.add(mi);

            // Add separators at intervals:

            if ((i + 1) % 3 == 0)

                m.addSeparator();

        }

        for (int
i = 0; i <
safety.length;
i++)

            s.add(safety[i]);

        f.add(s);

        for (int
i = 0; i <
file.length;
i++)

            f.add(file[i]);

        mb1.add(f);

        mb1.add(m);

        setMenuBar(mb1);

        t.setEditable(false);

        add(t, BorderLayout.CENTER);

        // Set up the system for swapping menus:

        b.addActionListener(new BL());

        add(b, BorderLayout.NORTH);

        for (int
i = 0; i <
other.length;
i++)

            fooBar.add(other[i]);

        mb2.add(fooBar);

    }

 

    class BL
implementsActionListener {

        public
void
actionPerformed(ActionEvent
e) {

            MenuBar m = getMenuBar();

            if (m ==
mb1)

                setMenuBar(mb2);

            else
if
(m ==
mb2)

                setMenuBar(mb1);

        }

    }

 

    class ML
implementsActionListener {

        public
void
actionPerformed(ActionEvent
e) {

            MenuItem
target = (MenuItem) e.getSource();

            String actionCommand =
target.getActionCommand();

            if (actionCommand.equals("Open")){

                String
s = t.getText();

                boolean
chosen = false;

                for (int
i = 0; i <
flavors.length;
i++)

                    if (s.equals(flavors[i]))

                       
chosen = true;

                if (!chosen)

                    t.setText("Choose a flavor first!");

                else

                    t.setText("Opening " +
s + ". Mmm, mm!");

            } else
if
(actionCommand.equals("Exit")){

                dispatchEvent(new WindowEvent(MenuNew.this, WindowEvent.WINDOW_CLOSING));

            }

        }

    }

 

    class FL
implementsActionListener {

        public
void
actionPerformed(ActionEvent
e) {

            MenuItem
target = (MenuItem) e.getSource();

            t.setText(target.getLabel());

        }

    }

 

    //Alternatively, you can create a different

    // class foreach different MenuItem. Then you

    // Don't haveto figure out which one it is:

    class FooL
implements ActionListener {

        public
void
actionPerformed(ActionEvent
e) {

            t.setText("Foo selected");

        }

    }

 

    class BarL
implements ActionListener {

        public
void
actionPerformed(ActionEvent
e) {

            t.setText("Bar selected");

        }

    }

 

    class BazL
implements ActionListener {

        public
void
actionPerformed(ActionEvent
e) {

            t.setText("Baz selected");

        }

    }

 

    class
CMIL implements ItemListener {

        public
void
itemStateChanged(ItemEvent
e) {

            CheckboxMenuItem
target = (CheckboxMenuItem) e.getSource();

            String actionCommand =
target.getActionCommand();

            if (actionCommand.equals("Guard"))

                t.setText("Guard the Ice Cream! "+
"Guarding is " + target.getState());

            else
if
(actionCommand.equals("Hide"))

                t.setText("Hide the Ice Cream! " +
"Is it cold? "+ target.getState());

        }

    }

 

    public
staticvoid
main(String[]
args){

        MenuNew f =
new MenuNew();

        f.addWindowListener(new WindowAdapter() {

            public
void
windowClosing(WindowEvent
e) {

                System.exit(0);

            }

        });

        f.setSize(300, 200);

        f.setVisible(true);

    }

} /// :~

开始初始化节的前面部分的代码同先前(Java 1.0 版)版本相同。这里我们可以注意到项目接收器和动作接收器被附加在不同的菜单组件上。

Java 1.1 支持“菜单快捷键”,因此我们可以选择一个菜单项目利用键盘替代鼠标。这十分的简单;我们只要使用过载菜单项构建器设置第二个自变量为一个MenuShortcut(菜单快捷键事件)对象即可。菜单快捷键构建器设置重要的方法,当它按下时不可思议地显示在菜单项上。上面的例子增加了Control-E 到“Exit”菜单项中。

我们同样会注意setActionCommand()的使用。这看似一点陌生因为在各种情况下“actioncommand”完全同菜单组件上的标签一样。为什么不正好使用标签代替可选择的字符串呢?这个难题是国际化的。如果我们重新用其它语言写这个程序,我们只需要改变菜单中的标签,并不审查代码中可能包含新错误的所有逻辑。因此使这对检查文字字符串联合菜单组件的代码而言变得简单容易,当菜单标签能改变时“动作指令”可以不作任何的改变。所有这些代码同“动作指令”一同工作,因此它不会受改变菜单标签的影响。注意在这个程序中,不是所有的菜单组件都被它们的动作指令所审查,因此这些组件都没有它们的动作指令集。

大多数的构建器同前面的一样,将几个调用的异常增加到接收器中。大量的工作发生在接收器里。在前面例子的BL 中,菜单交替发生。在ML 中,“寻找ring”方法被作为动作事件(ActionEvent)的资源并对它进行造型送入菜单项,然后得到动作指令字符串,再通过它去贯穿串联组,当然条件是对它进行声明。这些大多数同前面的一样,但请注意如果“Exit”被选中,通过进入封装类对象的句柄(MenuNew.this)并创建一个WINDOW_CLOSING 事件,一个新的窗口事件就被创建了。新的事件被分配到封装类对象的dispatchEvent()方法,然后结束调用windowsClosing()内部帧的窗口接收器(这个接收器作为一个内部类被创建在main()里),似乎这是“正常”产生消息的方法。通过这种机制,我们可以在任何情况下迅速处理任何的信息,因此,它非常的强大。FL
接收器是很简单尽管它能处理特殊菜单的所有不同的特色。如果我们的逻辑十分的简单明了,这种方法对我们就很有用处,但通常,我们使用这种方法时需要与FooL,BarL 和BazL 一道使用,它们每个都附加到一个单独的菜单组件上,因此必然无需测试逻辑,并且使我们正确地辨识出谁调用了接收器。这种方法产生了大量的类,内部代码趋向于变得小巧和处理起来简单、安全。

4.5.10    代码8

重写了早期的ToeTest.java 程序。在这个新的版本里,任何事件都被安放进一个内部类中。虽然这完全消除了需要记录产生的任何类的麻烦,作为ToeTest.java 的一个例子,它能使内部类的概念变得不那遥远。在这点,内嵌类被嵌套达四层之深!我们需要的这种设计决定了内部类的优点是否值得增加更加复杂的事物。另外,当我们创建一个非静态的内部类时,我们将捆绑非静态类到它周围的类上。有时,单独的类可以更容易地被复用。

import java.awt.*;

import java.awt.event.*;

public
class
ToeTestNew extends Frame {

    TextField rows =
new TextField("3");

    TextField cols =
new TextField("3");

    public ToeTestNew() {

        setTitle("Toe Test");

        Panel p =
new Panel();

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

        p.add(new Label("Rows", Label.CENTER));

        p.add(rows);

        p.add(new Label("Columns", Label.CENTER));

        p.add(cols);

        add(p, BorderLayout.NORTH);

        Button b =
new Button("go");

        b.addActionListener(new BL());

        add(b, BorderLayout.SOUTH);

    }

 

    static
finalintBLANK
= 0;

    static
finalintXX
= 1;

    static
finalintOO
= 2;

 

    class
ToeDialog extends Dialog {

        // w = number of cells wide

        // h = number of cells high

        int
turn = XX;
// Start with x's turn

 

        public ToeDialog(int
w, int
h) {

            super(ToeTestNew.this,
"The game itself",false);

            setLayout(new GridLayout(w,
h));

            for (int
i = 0; i <
w * h; i++)

                add(new ToeButton());

            setSize(w * 50,
h * 50);

            addWindowListener(new WindowAdapter() {

                public
void
windowClosing(WindowEvent
e) {

                    dispose();

                }

            });

        }

 

        class
ToeButton extends Canvas {

            int
state = BLANK;

 

            ToeButton() {

                addMouseListener(new ML());

            }

 

            public
void
paint(Graphics
g) {

                int
x1 = 0;

                int
y1 = 0;

                int
x2 = getSize().width- 1;

                int
y2 = getSize().height- 1;

                g.drawRect(x1,
y1, x2,y2);

                x1 =
x2 / 4;

                y1 =
y2 / 4;

                int
wide = x2/ 2;

 

                int
high = y2/ 2;

                if (state ==
XX) {

                    g.drawLine(x1,
y1, x1+
wide,y1+ high);

                    g.drawLine(x1,
y1 + high,x1+
wide,y1);

                }

                if (state ==
OO) {

                    g.drawOval(x1,
y1, x1+
wide/ 2, y1+
high/ 2);

                }

            }

 

            class ML
extends MouseAdapter {

                public
void
mousePressed(MouseEvent
e) {

                    if (state ==
BLANK) {

                       
state = turn;

                       
turn = (turn == XX ?
OO : XX);

                    } else

                       
state = (state == XX ?
OO : XX);

                    repaint();

                }

            }

        }

    }

 

    class BL
implementsActionListener {

        public
void
actionPerformed(ActionEvent
e) {

            Dialog d =
new ToeDialog(Integer.parseInt(rows.getText()), Integer.parseInt(cols.getText()));

            d.show();

        }

    }

 

    public
staticvoid
main(String[]
args){

        Frame f =
new ToeTestNew();

        f.addWindowListener(new WindowAdapter() {

            public
void
windowClosing(WindowEvent
e) {

                System.exit(0);

            }

        });

        f.setSize(200, 100);

        f.setVisible(true);

    }

} /// :~

由于“静态”的东西只能位于类的外部一级,所以内部类不可能拥有静态数据或者静态内部类。

4.5.11    代码9

import java.awt.*;

import java.awt.event.*;

public
class
FileDialogNew extends Frame {

    TextField filename =
new TextField();

    TextField directory =
new TextField();

    Button open =
new Button("Open");

    Button save =
new Button("Save");

 

    public FileDialogNew() {

        setTitle("File Dialog Test");

        Panel p =
new Panel();

        p.setLayout(new FlowLayout());

        open.addActionListener(new OpenL());

        p.add(open);

        save.addActionListener(new SaveL());

        p.add(save);

        add(p, BorderLayout.SOUTH);

        directory.setEditable(false);

        filename.setEditable(false);

        p =
new
Panel();

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

        p.add(filename);

        p.add(directory);

        add(p, BorderLayout.NORTH);

    }

 

    class OpenL
implements ActionListener {

        public
void
actionPerformed(ActionEvent
e) {

            // Two arguments, defaults to open file:

            FileDialog
d = new FileDialog(FileDialogNew.this,
"What file do you want to open?");

            d.setFile("*.java");

            d.setDirectory(".");
// Current directory

            d.show();

            String yourFile =
"*.*";

            if ((yourFile =
d.getFile()) != null) {

                filename.setText(yourFile);

                directory.setText(d.getDirectory());

            } else {

                filename.setText("You pressed cancel");

                directory.setText("");

            }

        }

    }

 

    class SaveL
implements ActionListener {

        public
void
actionPerformed(ActionEvent
e) {

            FileDialog
d = new FileDialog(FileDialogNew.this,
"What file do you want to save?", FileDialog.SAVE);

            d.setFile("*.java");

 

            d.setDirectory(".");

            d.show();

            String saveFile;

            if ((saveFile =
d.getFile()) != null) {

                filename.setText(saveFile);

                directory.setText(d.getDirectory());

            } else {

                filename.setText("You pressed cancel");

                directory.setText("");

            }

        }

    }

 

    public
staticvoid
main(String[]
args){

        Frame f =
new FileDialogNew();

        f.addWindowListener(new WindowAdapter() {

            public
void
windowClosing(WindowEvent
e) {

                System.exit(0);

            }

        });

        f.setSize(250, 110);

        f.setVisible(true);

    }

} /// :~

4.5.12    代码10

新AWT 事件模型给我们带来的一个好处就是灵活性。在老的模型中我们被迫为我们的程序动作艰难地编写代码。但新的模型我们可以用单一方法调用增加和删除事件动作。下面的例子证明了这一点:

import java.awt.*;

import java.awt.event.*;

import java.util.*;

public
class
DynamicEvents extends Frame {

    Vector v =
new Vector();

    int
i = 0;

    Button b1 =
new Button("Button 1"),
b2 = new Button("Button 2");

 

    public DynamicEvents() {

        setLayout(new FlowLayout());

        b1.addActionListener(new B());

        b1.addActionListener(new B1());

        b2.addActionListener(new B());

        b2.addActionListener(new B2());

        add(b1);

        add(b2);

    }

 

    class B
implementsActionListener {

        public
void
actionPerformed(ActionEvent
e) {

            System.out.println("A button was pressed");

        }

    }

 

    class CountListener
implements ActionListener {

        int
index;

 

        public CountListener(int
i) {

            index =
i;

        }

 

        public
void
actionPerformed(ActionEvent
e) {

            System.out.println("Counted Listener "+
index);

        }

    }

 

    class B1
implementsActionListener {

        public
void
actionPerformed(ActionEvent
e) {

            System.out.println("Button 1 pressed");

            ActionListener
a = new CountListener(i++);

            v.addElement(a);

            b2.addActionListener(a);

        }

    }

 

    class B2
implementsActionListener {

        public
void
actionPerformed(ActionEvent
e) {

            System.out.println("Button 2 pressed");

            int
end = v.size() - 1;

            if (end >= 0) {

                b2.removeActionListener((ActionListener)
v.elementAt(end));

                v.removeElementAt(end);

            }

        }

    }

 

    public
staticvoid
main(String[]
args){

        Frame f =
new DynamicEvents();

        f.addWindowListener(new WindowAdapter() {

            public
void
windowClosing(WindowEvent
e) {

                System.exit(0);

            }

        });

        f.setSize(300, 200);

        f.show();

    }

} /// :~

这个例子采取的新手法包括:

(1) 在每个按钮上附着不少于一个的接收器。通常,组件把事件作为多造型处理,这意味着我们可以为单个事件注册许多接收器。当在特殊的组件中一个事件作为单一造型被处理时,我们会得到TooManyListenersException(即太多接收器异常)。

(2) 程序执行期间,接收器动态地被从按钮B2 中增加和删除。增加用我们前面见到过的方法完成,但每个组件同样有一个removeXXXListener()(删除XXX 接收器)方法来删除各种类型的接收器。这种灵活性为我们的编程提供了更强大的能力。

注意到事件接收器不能保证在命令他们被增加时可被调用(虽然事实上大部分的执行工作都是用这种方法完成的)。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: