您的位置 首页 java

Java事件处理基础实例:处理按钮点击+捕获窗口事件+改变观感

前言

对于图形用户界面的程序来说,事件处理是十分重要的。要想实现用户界面,必须掌握 java 事件处理的基本方法。本章将讲解Java AWT事件模型的工作机制,从中可以看到如何捕捉鼠标和键盘产生的事件。另外,本章还介绍如何使用最简单的 GUI 组件元素,如按钮,以及如何处理由这些组件产生的基本事件。在下一章中,将阐述如何将 swing 提供的多个组件组织在一起,并全面地讲述这些组件产生的事件。

事件处理基础

任何支持GUI的操作环境都要不断地监视敲击键盘或点击鼠标这样的事件。操作环境将这些事件报告给正在运行的应用程序。如果有事件产生,每个应用程序将决定如何对它们做出响应。

在Visual Basic这样的语言中,事件与代码之间的对应是明确的。程序员对相关的特定事件编写代码,并将这些代码放置在过程中,通常人们将它们称为事件过程(event procedure)。例如,有一个名为HelpButton的Visual Basic按钮有一个与之关联的HelpButton_Click事件过程。这个过程中的代码将在点击按钮后执行。每个Visual Basic的GUI组件都响应一个固定的事件集,不可能改变Visual Basic组件响应的事件集。

另一方面,如果使用像原始的C这样的语言进行事件驱动的程序设计,就需要编写代码来不断地检查事件队列,以便查询操作环境报告的内容。(通常这些代码被放置在包含很多switch语句的循环体中)。显然,这种方式编写的程序可读性很差,而且在有些情况下,编码的难度也非常大。它的好处在于响应的事件不受限制,而不像Visual Basic这样的语言,将事件队列对程序员隐藏起来。

Java程序设计环境折中了Visual Basic与原始C的事件处理方式,因此,它既有着强大的功能,又具有一定的复杂性。在AWT所知的事件范围内,完全可以控制事件从事件源(event source)例如按钮或滚动条,到事件监听器(event listener)的传递过程,并将任何对象指派给事件监听器。不过事实上,应该选择一个能够便于响应事件的对象。这种事件委托模型(event delegationmodel)与Visual Basic那种预定义监听器模型比较起来更加灵活,但却需要编写更多的代码,整理起来也非常困难(至少在熟悉它之前)。

事件源有一些向其注册事件监听器的方法。当某个事件源产生事件的时候,事件源会向为事件注册的所有事件监听器对象发送一个通告。

像Java这样的面向对象语言,都将事件的相关信息封装在一个事件对象中。在Java中,所有的事件对象都最终派生于java.util.EventObject类。当然,每个事件类型还有子类,例如,ActionEvent和WindowEvent。

不同的事件源可以产生不同类别的事件。例如,按钮可以发送一个ActionEvent对象,而窗口可以发送WindowEvent对象。

综上所述,下面给出AWT事件处理机制的概要:

• 监听器对象是一个实现了特定监听器接口(listener interface)的类的实例。

• 事件源是一个能够注册监听器对象并发送事件对象的对象。

• 当事件发生时,事件源将事件对象传递给所有注册的监听器。

• 监听器对象将利用事件对象中的信息决定如何对事件做出响应。

可以采用下列代码模型来为事件源对象注册监听器对象:

eventSourceObject .add Event Listener( eventListenerObject );

下面是一个例子:

ActionListener listener = . . .;

JButton button = new JButton(“Ok”);

button.addActionListener(listener);

现在,只要按钮产生了一个“动作事件”,listener对象就会得到通告。对于按钮来说,动作事件就是点击按钮。

上面的代码要求监听器对象所属的类必须实现相应的接口(在这个例子中是ActionListener接口)。

与Java中所有的接口一样,实现一个接口就意味着要用完全相同的签名实现每个方法。为了实现ActionListener接口,监听器类必须有一个被称为actionPerformed的方法,该方法接收一个ActionEvent对象参数。

只要用户点击按钮,JButton对象就会创建一个ActionEvent对象,然后调用listener.actionPerformed(event) 传递事件对象。可以将多个监听器对象添加到一个像按钮这样的事件源中。这样一来,只要用户点击按钮,按钮就会调用所有监听器的actionPerformed方法。

图8-1显示了事件源、事件监听器和事件对象之间的协作关系。

实例:处理按钮点击事件

为了加深对事件委托模型的理解,下面以一个响应按钮点击事件的简单例子来说明所需要知道的所有细节。在这个例子中,我们想要

• 在一个面板中放置三个按钮。

• 添加三个监听器对象用来作为按钮的动作监听器。

在这个情况下,只要用户点击面板上的任何一个按钮,相关的监听器对象就会接收到一个ActionEvent对象,它表示有个按钮被点击了。在示例程序中,监听器对象将改变面板的背景颜色。

在演示如何监听按钮点击事件之前,首先需要讲解一下如何创建按钮以及如何将它们添加到面板中。(有关GUI元素更加详细的内容请参阅第9章。)

可以通过在按钮 构造器 中指定一个标签 字符串 、一个图标或两项都指定来创建一个按钮。

下面是两个例子:

将按钮添加到面板中需要调用add方法(十分容易记忆)。add方法的参数指定了将要放置到容器中的组件。例如,

图8-2显示了结果。

至此,知道了如何将按钮添加到面板上,接下来需要增加让面板监听这些按钮的代码。这需要一个实现了ActionListener接口的类,如前所述,它应该包含一个actionPerformed方法,其签名为:

public void actionPerformed(ActionEvent event)

注意 :在按钮例子中使用的ActionListener接口并不仅限于按钮点击事件。它可以应用于很多情况:

• 当采用鼠标双击的方式选择了列表框中的一个选项时。

• 当选择一个菜单项时。

• 当在文本域中敲击ENTER键时。

• 对于一个Timer组件来说,当到达指定的时间间隔时。

在本章和下一章中,将会看到更加详细的内容。

在各种情况下,使用ActionListener接口的方式都是一样的:actionPerformed方法(ActionListener中的唯一方法)将接收一个ActionEvent类型的对象作为参数。这个事件对象包含了事件发生时的相关信息。

当按钮被点击时,我们希望将面板的背景颜色设置为指定的颜色。该颜色存储在监听器类中。

然后,为每种颜色构造一个对象,并将这些对象设置为按钮监听器。

例如,如果一个用户在标有“Yellow”的按钮上点击了一下,那么yellowAction对象的actionPerformed方法就会被调用。这个对象的backgroundColor实例域设置为Color.YELLOW,现在就将面板的背景色设置为黄色了。

这里还有一个需要考虑的问题。ColorAction对象没有权限访问panel变量。可以采用两种方式解决这个问题。一个是将面板存储在ColorAction对象中,并在ColorAction构造器中设置它;另一个是将ColorAction作为ButtonPanel类的内部类。这样一来,ColorAction就自动地拥有访问外部类的权限了(有关内部类的详细介绍请参阅第6章)。

这里使用第二种方法。下面说明一下如何将ColorAction类放置在ButtonPanel类中。

下面仔细地研究一下actionPerformed方法。在ColorAction类中没有setBackground方法,但在外部ButtonPanel类中却有。这个方法可以在ButtonPanel对象构造内部对象之后调用。(再次说明一下,outer不是Java程序设计语言的关键字,它只是一种表示符号,用于表示内部类对象不可见的外部类引用。)

这种情形十分常见。事件监听器对象通常需要执行一些对其他对象可能产生影响的操作。

可以策略性地将监听器类放置在需要修改状态的那个类中。

例8-1包含完整的程序。无论何时点击任何一个按钮,对应的动作监听器都会修改面板的背景颜色。

例8-1 ButtonTest.java

javax.swing.JButton 1.2

• JButton(String label)

构造一个按钮。标签可以是常规的文本,从JDK 1.3开始,也可以是HTML。例如,”<html><b>Ok</b></html>”。

参数:label 显示在按钮表面的文本

• JButton(Icon icon)

构造一个按钮。

参数:icon 显示在按钮表面的图标

• JButton(String label, Icon icon)

构造一个按钮。

参数:label 显示在按钮表面的文本

icon 显示在按钮表面的图标

java.awt.Container 1.0

• Component add(Component c)

将组件c添加到容器中。

javax.swing.ImageIcon 1.2

• ImageIcon(String filename)

构造一个图标,它的图像存储在一个文件中。通过媒体跟踪器自动地加载这个图像(参阅第7章)。

建议使用内部类

有些人不喜欢使用内部类,其原因是觉得类和对象的增殖会使得程序的执行速度变慢。下面让我们讨论一下这个问题。首先,不需要为每个用户界面组件定义一个新类。在前面列举的例子中,三个按钮共享同一个监听器类。当然,每个按钮分别使用不同的监听器对象。但是,这些对象并不大,它们只包含一个颜色值和一个面板的引用。而使用传统的if. . .else语句的解决方案也需要引用动作监听器存储的上述颜色对象,只不过这是一个局部变量,而不是实例域。

我们认为现在已经到了习惯使用内部类的时代了。我们建议为事件处理设计一个专门的内部类;而不要将一个已经存在的类转换为监听器,即使匿名内部类也有一定的应用市场。

下面是一个说明使用匿名内部类简化代码的例子。如果仔细看一下例8-1的代码,就会注意到每个按钮的处理过程都是一样的:

1)用标签字符串构造按钮。

2)将按钮添加到面板上。

3)用适当的颜色构造一个动作监听器。

4)添加动作监听器。

为了简化这些操作过程,可以设计一个辅助方法:

然后将ButtonPanel构造器简化为:

接着可以进一步简化。请注意,ColorAction类只在makeButton方法中用到一次。因此,可以将它设计为一个匿名类:

动作监听器代码现在变得更加简单了。actionPerformed方法仅仅引用参数变量backgroundColor。

(与内部类中访问的所有局部变量一样,应该将参数声明为final。)

这里不需要显式的构造器。在第6章中已经看到,内部类机制将自动地生成一个构造器,其中存储着所有用在内部类方法中的final局部变量。

提示 :匿名内部类看起来可能让人感觉有些困惑。如果训练自己的眼睛能够捕捉程序代码中的关键字,就可以破解它们,例如:

这就是说,按钮动作设置背景颜色。只要事件处理器包含的语句条数不多,就认为这段代码的可读性还是不错的,尤其是在对内部类机制没有什么抵触心理的情况下。

提示 :在JDK 1.4中引入了不使用内部类定义简单的事件监听器的机制。例如,假设有一个标签为Load的按钮,它的事件处理只包含下面一个方法调用:

Frame .loadData( );

当然,可以使用匿名内部类:

但是,EventHandler类可以使用下列调用,自动地创建这样一个监听器:

当然,仍然需要安装处理器:

这里的类型转换是必要的,因为create方法返回的是一个Object。有可能在JDK未来的版本中使用泛型类型,这样可以使得这个方法变得更加方便。

如果事件监听器调用的方法只包含一个从事件处理器继承来的参数,就可以使用另外一种形式的create方法。例如,调用:

等价于

需要注意,事件处理器将属性source和text的名字转换成符合JavaBeans习惯的方法调用getSource和getText。(有关属性和JavaBeans组件更加详细的论述请参阅卷II。)

然而,在实际中,这种情形并不常见,而且也没有相应的机制能够处理那些不是由事件对象继承而来的参数。

将组件变成事件监听器

任何实现了ActionListener接口的类对象都可以作为按钮监听器。我们更加倾向于为将要执行的按钮动作创建一个新类和该类的对象。然而,有些程序员不愿意使用内部类,而是选择了不同的策略。他们找到因事件而改变的组件,然后让这些组件实现ActionListener接口,并增加actionPerformed方法。在下面的例子中,可以将ButtonPanel 转换为动作监听器:

然后,将面板本身设置为三个按钮的监听器:

yellowButton.addActionListener(this);

blueButton.addActionListener(this);

redButton.addActionListener(this);

注意,现在这里的三个按钮不再是独立的监听器。它们共享一个监听器对象,即按钮面板。

因此,actionPerformed方法必须判断点击了哪个按钮。

EventObject类是所有事件类的超类,其中的getSource方法可以给出每个事件的事件源。事件源是产生事件和通告监听器的对象。

Object source = event.getSource( );

然后,actionPerformed方法可以检查哪个按钮是事件源:

if (source == yellowButton) . . .

else if (source == blueButton) . . .

else if (source == redButton) . . .

当然,这种方式需要包含按钮的面板拥有一个保存按钮引用的实例域。

可以看到,将按钮面板转换为动作监听器并不比定义内部类容易。当面板包含多个用户界面元素时,会变得格外混乱。

警告 :有些程序员采用另外一种方式在多个事件源共享的监听器对象中找出事件源。

ActionEvent类有一个getActionCommand方法,它将返回与这个动作有关的命令字符串。对于按钮,产生的默认命令字符串是按钮标签。如果采用这种方式,在actionPerformed方法中应该包含下列代码:

String command = event.getAction com mand( );

if (command.equals(“Yellow”)) . . . ;

else if (command.equals(“Blue”)) . . . ;

else if (command.equals(“Red”)) . . . ;

我们建议不要使用这种方式。依据按钮字符串存在着危险。很容易犯这类错误:

有一个按钮的标签为”Gray”,但在检测时拼写的字符串却有一点差别:

if (command.equals(“Grey”)) . . .

当需要将应用程序国际化时,按钮字符串会带来很多烦恼。例如,要想使用德国版的按钮标签“Gelb”、“Blau”和“Rot”,就必须在修改按钮标签的同时,还要修改actionPerformed方法中的字符串。

java.util.EventObject 1.1

• Object getSource( )

返回发生事件的对象引用。

java.awt.event.ActionEvent 1.1

• String getActionCommand( )

返回与这个动作事件相关的命令字符串。如果动作事件来源于按钮,命令字符串就等于按钮标签,除非已经使用setActionCommand方法对字符串进行了修改。

java.beans.EventHandler 1.4

• static Object create(Class listenerInterface, Object target, String action)

• static Object create(Class listenerInterface, Object target, String action, String eventProperty)

• static Object create(Class listenerInterface, Object target, String action, String eventProperty, String listenerMethod)

构造实现给定接口的代理类对象。无论是命名方法,还是接口的所有方法都将执行目标对象上的给定动作。

动作可以是一个方法名或目标的属性。如果是属性,执行它的设置方法。例如,动作”text”将变为调用setText方法。

事件属性由一个或多个用逗号分隔的属性名组成。第一个属性从监听器方法的参数中读出。

第二个属性由结果对象读出等等。最后的结果将作为动作的参数。例如,属性”source.text”将变为调用getSource和getText方法。

实例:改变观感

在默认情况下,Swing程序使用Metal观感,可以采用两种方式改变观感。第一种方式是在Java安装的子目录jre/lib下有一个文件swing.properties。在这个文件中,将属性swing.defaultlaf设置为所希望的观感类名。例如,

swing.defaultlaf = com.sun.java.swing.plaf.motif.MotifLookAndFeel

注意,Metal观感位于javax.swing包中。其他的观感包位于com.sun.java包中,并且不是在每个Java实现中都提供。现在,鉴于版权的原因,Windows和Mac的观感包只与Windows和Mac版本的Java运行时环境一起发布。

提示:下面给出一个对测试很有帮助的建议。由于属性文件中以#字符开始的行被忽略,所以,可以在swing.properties文件中提供几种观感选择,并通过增删#字符来切换选择:

 #swing.defaultlaf = javax.swing.plaf.metal.MetalLookAndFeel
swing.defaultlaf = com.sun.java.swing.plaf.motif.MotifLookAndFeel
#swing.defaultlaf = com.sun.java.swing.plaf.windows.WindowsLookAndFeel  

采用这种方式开启观感时必须重新启动程序。Swing程序只在启动时读取一次swing.properties文件。

第二种方式是动态地改变观感。这需要调用静态的UIManager.setLookAndFeel方法,并提供所想要的观感类名,然后再调用静态方法SwingUtilities.updateComponentTreeUI来刷新全部的组件集。这里需要向这个方法提供一个组件,并由此找到其他的所有组件。当UIManager.setLookAndFeel方法没有找到所希望的观感或在加载过程中出现错误时,将会抛出异常。与前面一样,建议暂且将异常处理的代码跳过,等到第11章详细地讲述异常时就会理解了。

下面是一个例子,它显示了如何在程序中切换到Motif观感:

 String plaf = "com.sun.java.swing.plaf.motif.MotifLookAndFeel";
try
{
UIMananger.setLookAndFeel(plaf);
SwingUtilities.updateComponentTreeUI(panel);
catch(Exception e) {e.printStackTrace( );}  

为了列举安装的所有观感实现,可以调用:

UIManager.LookAndFeelInfo[ ] infos = UIManager.getInstalledLookAndFeels( );

然后采用下列方式得到每一种观感的名字和类名:

String name = infos[i].getName( );

String className = infos[i].getClassName( );

例8-2是一个完整的程序,它演示了如何切换观感(如图8-3所示)的方式。这个程序与例8-1十分相似。我们遵循前一节的建议,使用辅助方法makeButton和匿名内部类指定按钮动作,即切换观感。

在这个程序中,还有一点需要注意的地方。在内部动作监听器类的actionPerformed方法中,需要将一个外部PlafPanel类的this引用传递给updateComponentTreeUI方法。回想一下第6章所说过的,外部对象的this指针必须将外部类名作为前缀:

SwingUtilities.updateComponentTreeUI( PlafPanel.this );

例8-2 PlafTest.java

javax.swing.UIManager 1.2

• static UIManager.LookAndFeelInfo[ ] getInstalledLookAndFeels( )

得到一个用于描述已安装的观感实现的对象数组。

• static setLookAndFeel(String className)

设置当前的观感。

参数:className 观感实现类的名称

javax.swing.UIManager.LookAndFeelInfo 1.2

• String getName( )

返回观感的显示名称。

• String getClassName( )

返回观感实现类的名称。

实例:捕获窗口事件

并不是所有的事件处理都像按钮点击那样简单。下面这个例子就有点复杂,在第7章中我们曾经简要的介绍过。在JDK 1.3中EXIT_ON_CLOSE选项出现之前,当关闭主框架的时候,程序员必须手工地退出程序。在正规的程序中,往往希望用户在确认没有丢失所做工作之后再关闭程序。

例如,当用户关闭框架时,可能希望弹出一个对话框来警告用户没有保存的工作有可能会丢失,只有在用户确认之后才退出程序。

当程序用户试图关闭一个框架窗口时,JFrame对象就是WindowEvent的事件源。在WindowListener接口中包含7个方法。当发生窗口事件时,框架将调用这些方法来响应7个不同的事件。它们的名字都是自解释的,唯一的例外是在Windows下,通常将“iconified”称为“minimized”。下面是完整的WindowListener接口:

注意 :为了能够查看窗口是否被最大化,需要安装WindowStateListener。有关更加详细的信息请参阅后面的API注释。

正像前面曾经说过的那样,在Java中,实现一个接口的任何类都必须实现其中的所有方法;在这里,意味着需要实现7个方法。然而我们只对其中的名为windowClosing的方法感兴趣。

当然,可以这样定义实现这个接口的类:在windowClosing方法中增加一个对System.exit(0) 的调用,其他6个方法什么也不做:

适配器类

书写6个没有任何操作的方法代码显然是一种乏味的工作。出于简化的目的,每个含有多个方法的AWT监听器接口都配有一个适配器(adapter)类,这个类实现了接口中的所有方法,但每个方法没有做任何事情。例如,WindowAdapter类有7个没有做任何事情的方法。这意味着适配器类自动地满足了Java实现相关监听器接口的技术需求。可以通过扩展适配器类来指定对某些事件的响应动作,而不必实现接口中的每个方法。(ActionListener这样的接口只有一个方法,因此没必要提供适配器类。)

下面使用窗口适配器。首先定义一个WindowAdapter类的扩展类,其中包含继承的6个没有做任何事情的方法和一个覆盖的方法windowClosing:

现在,可以将一个Terminator类型的对象注册为事件监听器:

只要框架产生了窗口事件,就会通过调用7个方法之中的一个方法将事件传递给listener对象(如图8-4所示),其中6个方法没有做任何事情;windowClosing方法调用System.exit(0)终止应用程序的执行。

警告:如果在扩展适配器类时将方法名拼写错了,编译器不会捕捉到这个错误。例如,如果在WindowAdapter中定义一个windowIsClosing方法,就会得到一个拥有8个方法的类,并且windowClosing方法没有做任何事情。

创建一个扩展于WindowAdapter的监听器类是一种很好的改进,但是还可以继续改进。事实上,没有必要为listener对象命名。只需写成:frame.addWindowListener(new Terminator( ));

不要就此止步!我们可以将监听器类定义为框架的匿名内部类。

这段代码具有下列作用:

• 定义了一个扩展于WindowAdapter类的无名类。

• 将windowClosing方法添加到匿名类中(与前面一样,这个方法将退出程序)。

• 从WindowAdapter继承6个没有做任何事情的方法。

• 创建这个类的一个对象,这个对象没有名字。

• 将这个对象传递给addWindowListener方法。

这里再次说明一下,使用匿名内部类的语法需要人们适应一段时间,但得到的是更加简炼的代码。

java.awt.event.WindowListener 1.1

• void windowOpened(WindowEvent e)

窗口打开后调用这个方法。

• void windowClosing(WindowEvent e)

在用户发出窗口管理器命令关闭窗口时调用这个方法。需要注意一点,仅当调用hide或

dispose方法后窗口才能够关闭。

• void windowClosed(WindowEvent e)

窗口关闭后调用这个方法。

• void windowIconified(WindowEvent e)

窗口图标化后调用这个方法。

• void windowDeiconified(WindowEvent e)

窗口非图标化后调用这个方法。

• void windowActivated(WindowEvent e)

激活窗口后调用这个方法。只有框架或对话框可以被激活。通常,窗口管理器会对活动窗

口进行修饰,比如,高亮度标题栏。

• void windowDeactivated(WindowEvent e)

窗口变为未激活状态后调用这个方法。

java.awt.event.WindowStateListener 1.4

• void windowStateChanged(WindowEvent event)

窗口被极大化、图标化或恢复为正常大小时调用这个方法。

java.awt.event.WindowEvent 1.1

• int getNewState( ) 1.4

• int getOldState( ) 1.4

返回窗口状态改变事件中窗口的新、旧状态。返回的整型数值是下列数值之一:

Frame.NORMAL

Frame.ICONIFIED

Frame.MAXIMIZED_HORIZ

Frame.MAXIMIZED_VERT

Frame.MAXIMIZED_BOTH

觉得文章不错的话,可以转发关注小编一下!!!

明天给大家讲述AWT事件继承层次、AWT的语义事件和低级事件这一技术点~~~~~~~~~~

文章来源:智云一二三科技

文章标题:Java事件处理基础实例:处理按钮点击+捕获窗口事件+改变观感

文章地址:https://www.zhihuclub.com/184745.shtml

关于作者: 智云科技

热门文章

网站地图