利用Java事件处理机制实现录制、回放功能

详细请点击:http://www.verydemo.com/demo_c89_i7965.html

利用Java 事件处理机制实现录制、回放 功能

目前在一些java应用程序的GUI 测试工具,可以提供捕获用户操作的能力并在代码被修改之后能够自动回放用户的操作。文章将分析Java的 事件处理模型及其原理,介绍了基于 事件源识别的捕获/回放所需要了解的关键技术并给出了两种 实现方式。
1、 Java 事件介绍
1.1什么是 事件
首先我们来回答"什么是 事件"这一基本问题。其实 事件本身就是一个抽象的概念,他是表现另一对象状态变化的对象。在面向对象的程序设计中, 事件消息是对象间通信的基本方式。在图形用户界面程序中,GUI组件对象根据用户的交互产生各种类型的 事件消息,这些 事件消息由应用程序的 事件处理代码捕获,在进行相应的 处理后驱动消息响应对象做出反应。我们在GUI上进行叫化操作的时候,在点击某个可响应的对象时如,按钮,菜单,我们都会期待某个 事件的发生。其实围绕 GUI的所有活动都会发生 事件,但Java 事件处理机制却可以让您挑选出您需要 处理事件事件在Java中和其他对象基本是一样的,但有一点不同的是, 事件是由系统自动生成自动传递到适当的 事件处理程序。
1.2Java 事件处理的演变
当java的开发者开始解决用java创建应用程序这一问题时,他们就认识到java 事件模型的必要性。下面对java 事件处理的发展做简要的概括。
在JDK1.0 的版本采用用的 事件模型,提供了基本的 事件处理功能。这是一种包容 模型,所有 事件都封装在单一的类Event中,所有 事件对象都由单一的方法 handleEvent来 处理,这些定义都在Component类中。为此,只有Component类的子类才能充当 事件处理程序, 事件处理传递到组件层次结构,如果目标组件不能完全 处理事件事件被传递到目标组件的容器。
JDK1.1是编程界的一次革命,修正了前面版本的一些缺陷,同时增加了一些重要的新 功能如,RMI、JNI、JDBC、JavaBean。在 事件模型上基本框架完全重写,并从Java1.0 模型迁移到委托 事件模型,在委托 模型事件源生成 事件,然后 事件处理委托给另一段代码。
从JDK1.2开始,引入了Swing包 事件处理模型功能更强大,更加可定制 GUI组件与他们相关联的支持类。在后面的版本基本保持了整个 事件模型,但加入了一些附加 事件类和接口。在1.3版本开始引入Rebot类,它能模拟鼠标和键盘 事件,并用于自动化 测试、自动运行演示、以及其他要求鼠标和键盘控制的应用程序。
我们把JDK1.0 事件处理模型成为java 1.0 事件模型,而从jdk1.1后的版本 事件处理模型称为Java 2 事件处理模型
2、 Java 2 事件处理模型
在Java1.0 事件处理模型事件处理是以如下方法执行的。deliverEvent()用于决定 事件的目标,目标是 处理事件的组件或容器,此过程开始于GUI层的最外部而向内运作。当按一个button时,如果检测到是该按钮激发的 事件,该按钮会访问它的deliverEvent()方法,这一操作由系统完成。一旦识别目标组件,正确 事件类型发往组件的postEvent()方法,该方法依次把 事件送到handleEvent()方法并且等待方法的返回值。 "true"表明 事件完全 处理,"false"将使postEvent()方法联系目标容器,希望完成 事件处理
下面给一个实例:



  import java.applet.*;

  import java.awt.*;

 

  public class Button1Applet extends Applet{

    public void init(){

add(new Button("Red"));

add(new Button("Blue"));

}

public boolean action(Enent evt,Object whatAction){

    if( !( evt.target  instanceof  Button))return false;

String buttonlabel=(String)whatAction;

if(buttonlabel=="Red")setBackground(Color.red);

if(buttonlabel==" Blue")setBackground(Color.blue);

repaint();

return true;

}



}

在Java2 处理事件时,没有采用dispatchEvent()-postEvent()-handleEvent()方式,采用了监听器类,每个 事件类都有相关联的监听器接口。 事件事件源到监听者的传递是通过对目标监听者对象的Java方法调用进行的。
对每个明确的 事件的发生,都相应地定义一个明确的Java方法。这些方法都集中定义在 事件监听者(EventListener)接口中,这个接口要继承 java.util.EventListener。 实现事件监听者接口中一些或全部方法的类就是 事件监听者。伴随着 事件的发生,相应的状态通常都封装在 事件状态对象中,该对象必须继承自java.util.EventObject。 事件状态对象作为单参传递给应响应该 事件的监听者方法中。发出某种特定 事件事件源的标识是:遵从规定的设计格式为 事件监听者定义注册方法,并接受对指定 事件监听者接口实例的引用。有时, 事件监听者不能直接 实现事件监听者接口,或者还有其它的额外动作时,就要在一个源与其它一个或多个监听者之间插入一个 事件适配器类的实例,来建立它们之间的联系。
我们来看下面一个简单的实例:



import javax.swing.*;

import java.awt.*;

import java.awt.event.*;



public class SimpleExample extends JFrame {

  JButton jButton1 = new JButton();



  public SimpleExample() {

    try {

      jbInit();

    }

    catch(Exception e) {

      e.printStackTrace();

    }

  }

  public static void main(String[] args) {

    SimpleExample simpleExample = new SimpleExample();

  }

  private void jbInit() throws Exception {

    jButton1.setText("jButton1");

    jButton1.addActionListener(new SimpleExample_jButton1_actionAdapter(this));

    jButton1.addActionListener(new SimpleExample_jButton1_actionAdapter(this));

    this.getContentPane().add(jButton1, BorderLayout.CENTER);

this.setVisible(true);

  }



  void jButton1_actionPerformed(ActionEvent e) {

    System.exit(0);

  }

}



class SimpleExample_jButton1_actionAdapter implements java.awt.event.ActionListener {

  SimpleExample adaptee;



  SimpleExample_jButton1_actionAdapter(SimpleExample adaptee) {

    this.adaptee = adaptee;

  }

  public void actionPerformed(ActionEvent e) {

    adaptee.jButton1_actionPerformed(e);

  }

}

3、 事件捕获与回放
3.1 Java 事件生命周期
Java 事件和万事一样有其生命周期,会出生也会消亡。下图3.1给出了Java 事件生命周期的示意图,


事件最初由 事件源产生, 事件源可以是GUI组件Java Bean或由生成 事件能力的对象,在GUI组件情况下, 事件源或者是组件的同位体(对于Abstract Window Toolkit[awt]GUI组件来说)或组件本身(对于Swing组件来说)。 事件生成后放在系统 事件队列内部。现在 事件处于 事件分发线程的控制下。 事件在队列中等待 处理,然后 事件事件队列中选出,送到dispatchEvent()方法,dispatchEvent()方法调用 processEvent()方法并将 事件的一个引用传递给processEvent()方法。此刻,系统会查看是否有送出 事件的位置,如果没有这种 事件类型相应的已经注册的监听器,或者如果没有任何组件受到激活来接收 事件类型, 事件就被抛弃。当然上图显示的是AWTEvent类的子类的生命周期。 dispatchEvent()方法和processEvent()方法把AWTEvent作为一个参数。但对,javax.swing.event并不是AWTEvent子类,而是从EventObject直接继承过来,生成这些 事件的对象也会定义fireEvent()方法,此方法将 事件送到包含在对象监听器列表内的那种类型的任何监听器。
3.2 Java 事件捕获
从上面的分析我们知道,任何 事件产生到dispatchEvent()方法分发方法前,所有的 事件都是存放在系统 事件的队列中,而且所有的 事件都由 dispatchEvent()方法来分派。所以只要能重载dispatchEvent()方法就可以获取系统的所有 事件,包括用户输入 事件。一般来说,系统 事件队列的操作对用户来说是可以控制。它在后台自动完成所要完成的事情,使用EventQueue类可以查看甚至操纵系统 事件队列。
Java 提供了EventQueue类来访问甚至操纵系统 事件队列。EventQueue类中封装了对系统 事件队列的各种操作,除dispatchEvent() 方法外,其中最关键的是提供了push()方法,允许用特定的EventQueue来代替当前的EventQueue。只要从EventQueue类中派生一个新类,然后通过push()方法用派生类来代替当前的EventQueue类即可。这样,所有的系统 事件都会转发到派生EventQueue类。然后,再在派生类中重载dispatchEvent()方法就可以截获所有的系统 事件,包括用户输入 事件。下面一段代码给出一个操纵EventQueue的实例:



import java.awt.*;

import java.awt.event.*;



public class GenerateEventQueue extends Frame implements ActionListener{

  Button button1 = new Button();

  TextField textField1 = new TextField();



  public GenerateEventQueue() {

    try {

      jbInit();

    }

    catch(Exception e) {

      e.printStackTrace();

    }

  }

  public static void main(String[] args) {

    GenerateEventQueue generateEventQueue = new GenerateEventQueue();

  }

  private void jbInit() throws Exception {

    button1.setLabel("button1");

    button1.addActionListener(this) ;

    textField1.setText("textField1");



    this.add(button1, BorderLayout.SOUTH);

    this.add(textField1, BorderLayout.CENTER);

    EventQueue eq=getToolkit().getSystemEventQueue() ;

    eq.postEvent(new ActionEvent(button1,ActionEvent.ACTION_PERFORMED,"test" )) ;

    addWindowListener(new WinListener());

    setBounds(100,100,300,200);

    setVisible(true);

  }



  public void actionPerformed(ActionEvent e) {

    textField1.setText("event is :" e.getActionCommand()) ;

  }



}



class WinListener extends WindowAdapter{

  public void windowClosing(WindowEvent we){

    System.exit(0) ;

  }

}

运行结果如下图所示:


在文本域中首先出现的是"event is :test",这是因为首先得到 处理的是EventQueue对象发送到系统 事件队列上的ActionEvent。
下面的代码简单说明了如何捕获 事件



  import java.awt.EventQueue;

import java.awt.*;

import java.util.*;

public class MyQueueEvent extends EventQueue {//定义EventQueue的子类

  public MyQueueEvent() {

  }

  public static void main(String[] args) {

    SimpleExample.main(new String[]{null}) ;

    MyQueueEvent myQueueEvent1 = new MyQueueEvent();

    Toolkit.getDefaultToolkit().getSystemEventQueue().push(myQueueEvent1) ;

  }

//在这里重载 事件分发的方法

  public void dispatchEvent(AWTEvent ae){

   

    if(ae.getSource() instanceof javax.swing.JButton)

    System.out.println("My apture:" ((javax.swing.JButton)ae.getSource()).getText()) ;

    super.dispatchEvent(ae);

  }

 
这个程序可以打印出当前应用的所有的 事件,可以将这些 事件中选出你需要的 事件保存当然你还需要解析该控件的特征。在上面加黑部分的代码,打印 事件源控件的名称。
除此之外,还可以通过 实现java.awt.event. AWTEventListener接口 实现事件的捕获。这个侦听器接口可以接收Component or MenuComponent 以及它们的派生类在整个系统范围内所分发的 事件,AWTEventListeners只是被动的监控这些 事件。如果要监控系统 事件,除了要 实现接口,还要用Toolkit的addAWTEventListener方法注册这个侦听器。
下面我们来看一个实例:



import java.awt.AWTEvent;

import java.awt.Frame;

import java.awt.Toolkit;

import java.awt.Window;

import java.awt.event.AWTEventListener;

import java.awt.event.WindowEvent;



import java.util.ArrayList;

import java.lang.ref.WeakReference;

public class MyAWTEventListener implements AWTEventListener{

  private  static MyAWTEventListener s_singleton = null;//保证该类只被初始化一次

  public static MyAWTEventListener getInstance(){

    if(s_singleton==null){

     s_singleton=new MyAWTEventListener();

    }

    return s_singleton;

  }

  private MyAWTEventListener(){

    //注意下面这行代码,如果没有这行代码,将无法接收到系统分发的 事件

   // 下面代码在注册时,只请求了接收WINDOW_EVENT_MASK 事件

   //但实际上,你可以接收其他AWTEvent中定义的 事件类型

Toolkit.getDefaultToolkit().addAWTEventListener(this,

        AWTEvent.COMPONENT_EVENT_MASK

);

  }

  /*

     这就是接口方法的 实现

*/

public void eventDispatched(final AWTEvent theEvent) {

        processEvent(theEvent);

  }

  private static void processEvent(final AWTEvent theEvent) {

        System.out.println(theEvent.getSource() ) ;//打印 事件

    switch (theEvent.getID()) {

        case WindowEvent.WINDOW_OPENED:

          //System.out.println(((Frame)theEvent.getSource()).getTitle() ) ;

        case WindowEvent.WINDOW_ACTIVATED:

        case WindowEvent.WINDOW_DEACTIVATED:

        case WindowEvent.WINDOW_CLOSING:

        default: break;

        }

    }

}

3.3 Java 事件回放
事件的回放其实比较简单了,比如我们现在记录的是frame1下的jButton1点击 事件回放。看下面一段简单的程序,只要点一下jButton1,就在控制台打印一次"click me"的字符串。



import java.awt.*;

import javax.swing.*;

import java.awt.event.*;



public class Frame1 extends JFrame {

  private JButton jButton1 = new JButton();



  public Frame1() {

    try {

      jbInit();

    }

    catch(Exception e) {

      e.printStackTrace();

    }

  }

  public static void main(String[] args) {

    Frame1 frame1 = new Frame1();

frame1.setVisible(true) ;



  }

  private void jbInit() throws Exception {

    jButton1.setText("jButton1");

    jButton1.addActionListener(new java.awt.event.ActionListener() {

      public void actionPerformed(ActionEvent e) {

        jButton1_actionPerformed(e);

      }

    });

    this.setTitle("Test");

    this.getContentPane().add(jButton1, BorderLayout.CENTER);

  }



  void jButton1_actionPerformed(ActionEvent e) {

    System.out.println("click me") ;

  }

}

下面是回放的程序,在下面的程序中用到了 java.awt.Robot类,这个类通常用来在自动化 测试或程序演示中模拟系统 事件,在某些需要控制鼠标或键盘的应用程序中这个类也是很有用,这个类主要的目的就是为方便的 实现java的GUI自动化 测试平台。在 事件回放时,我们同样需要该类来模拟生成系统的 事件,完成记录的操作的回放,在下面的代码中,给出了一个简单的例子。



import java.awt.*;

import javax.swing.*;

import java.awt.event.*;

public class TestReplay extends Thread{

  public static void main(String[] args) {

    try{

      //启动要回放的应用程序

      Frame1.main(new String[]{null}) ;

//等应用程序启动后延迟3秒再进行回放

      Thread.currentThread().sleep(3000) ;

      Robot robottest=new Robot();

      robottest.waitForIdle();

      //根据标题名获取当前应用的主窗体,在本例中为"test"

      Frame jframe=getFrame("test");;

     //根据给定的窗体和窗体中要find的控件的名称来获取控件的引用  

JButton jbtn=getButton(jframe,"jButton1");

//将鼠标移到控件所在的位置

      robottest.mouseMove(jbtn.getLocationOnScreen().x jbtn.getWidth()/2

  ,jbtn.getLocationOnScreen().y jbtn.getHeight()/2) ;

//在控件所在位置,生成鼠标点击 事件

      robottest.mousePress(InputEvent.BUTTON1_MASK ) ;

      robottest.mouseRelease(InputEvent.BUTTON1_MASK ) ;

    }catch(Exception ee){

      ee.printStackTrace() ;

    }

  }

  //获得标题为title的frame

  private static Frame getFrame(String title){

    Frame[] jframes=(Frame[])JFrame.getFrames();

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

      if(jframes[i].getTitle().equalsIgnoreCase(title))return jframes[i];

    }

    return null;



  }

  //获取某一个frame下的某个名为jButton1的控件

  private static JButton getButton(Frame jf,String text){

/*注意下面这行代码,因为实例比较简单只有ContentPane一个Container类型的控件,

如果在JFrame中有多个Container控件//的话,必须进行递归 处理,搜索出所有的控件

*/

    Component[] coms=((JFrame)jf).getContentPane().getComponents();

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

      if(!(coms[i] instanceof JButton))continue;

      if(((JButton)coms[i]).getText().equalsIgnoreCase(text))return (JButton)coms[i];

    }

    return null;

  }

  public void run(){



  }

}

该程序运行完,你会发现在控制台同样打印出了:
"click me"的字符串说明 事件被正确回放了。
当然还可以通过直接操纵系统 事件队列 实现输入 事件的回放。先通过记录下的窗口/组件名获得对应窗口引用,然后重构鼠标/键盘 事件,最后将重构的 事件直接放入系统 事件队列,由分派线程执行后续的 事件分派工作。还需要解决关键问题如何能根据窗口名称获得其引用。这里还是可以通过系统 事件队列来 实现的,因为 Java程序在新建/删除一个容器时都会向系统 事件队列发出一个Containerevent 事件,其中包含了对该容器的引用。所以, 事件回放器在载入被测 测试程序后便监视系统队列,截获所有的Containerevent 事件。如果新建容器,便获得新建Container的引用。因为所有的 Container都 实现了getComponets(),可以返回所有该容器所包含的组件或容器,只需要保存到一个HashMap结构中,需要时检索出来就可以了。该过程所用到的知识,其实在上面都有提到而且在实际引用中,既然Robot已经帮我们完成许多事情,也没有必要自己再去重构一个鼠标或键盘 事件了,不过有兴趣的朋友也可以去试试。
4、 结束语
随着我国 软件业的发展, 软件测试技术作为 软件质量保证的重要环节越来越受到重视,而在基于GUI的应用中采用自动化 测试工具可以提高 软件测试的有效性和效率,特别在回归 测试中可以大大减少人力投入,还可以提高 测试脚本的复用。因此, 软件自动 测试平台开发已经成为 软件测试的一个重要 领域。本文介绍了基于 Java的GUI应用的自动 测试平台开发需要的基本但关键的捕获、回放 功能,所有相关系统开发其实都离不开本文说的方法。


来源于: http://www.cnham.com/unix/html/24/2006/0312/6265.html











  Java 事件处理

  从概念上讲, 事件是一种在"源对象"和"监听者对象"之间,某种状态发生变化的传递 机制事件有许多不同的用途,例如在Windows系统中常要 处理的鼠标 事件、窗口边界改变 事件、键盘 事件等。在Java中则是定义了一个普通的、可扩充的 事件机制,这种 机制能够:

  对 事件类型和传递的 模型的定义和扩充提供一个公共框架,并适合于广泛的应用。

   与Java语言和环境有较高的集成度。

    事件能被描述环境捕获和触发。

   能使其它构造工具采取某种技术在设计时直接控制 事件,以及 事件源和 事件监听者之间的联系。

    事件机制本身不依赖于复杂的开发工具。

   事件事件源到监听者的传递是通过对目标监听者对象的Java方法调用进行的。 对每个明确的 事件的发生,都相应地定义一个明确的Java方法。这些方法都集中定义在 事件监听者(EventListener)接口中,这个接口要继承java.util.EventListener。 实现事件监听者接口中一些或全部方法的类就是 事件监听者。 伴随着 事件的发生,相应的状态通常都封装在 事件状态对象中,该对象必须继承自java.util.EventObject。 事件状态对象作为单参传递给应响应该 事件的监听者方法中。发出某种特定 事件事件源的标识是:遵从规定的设计格式为 事件监听者定义注册方法,并接受对指定 事件监听者接口实例的引用。有时, 事件监听者不能直接 实现事件监听者接口,或者还有其它的额外动作时,就要在一个源与其它一个或多个监听者之间插入一个 事件适配器类的实例,来建立它们之间的联系。

   事件状态对象(Event State Object)
 
  与 事件发生有关的状态信息一般都封装在一个 事件状态对象中,这种对象是java。util。EventObject的子类。按设计习惯,这种 事件状态对象类的名应以Event结尾。例如:
public class MouseMovedExampleEvent extends java。util。EventObject
{ protected int x, y;
/* 创建一个鼠标移动 事件MouseMovedExampleEvent */
 MouseMovedExampleEvent(java.awt.Component source, Point location) {
super(source);
x = location.x;
y = location.y;
}
/* 获取鼠标位置*/
public Point getLocation() {
return new Point(x, y);
}}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值