2012-06-14 39 views
8

我正在使用java.awt.Robot进行Swing应用程序的集成测试,但我无法按正确的顺序运行我的操作。我如何告诉调用robot.mousePressed(...)的线程阻塞,直到Swing完成调度该事件?显然,robot.setAutoWaitForIdle(true)没有好处。java.awt.Robot.waitForIdle()是否等待事件分派?

这是我的演示。我期待“机器人完成!”消息总是在“动作完成阻塞”之后出现,但相反,它往往发生得太快。

import java.awt.AWTException; 
import java.awt.GraphicsConfiguration; 
import java.awt.GraphicsDevice; 
import java.awt.GraphicsEnvironment; 
import java.awt.Point; 
import java.awt.Rectangle; 
import java.awt.Robot; 
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 
import java.awt.event.InputEvent; 
import java.sql.Date; 
import java.text.DateFormat; 
import java.util.logging.ConsoleHandler; 
import java.util.logging.Formatter; 
import java.util.logging.LogManager; 
import java.util.logging.LogRecord; 
import java.util.logging.Logger; 

import javax.swing.GroupLayout; 
import javax.swing.JButton; 
import javax.swing.JFrame; 
import javax.swing.SwingUtilities; 
import javax.swing.WindowConstants; 


public class RobotWaitForIdleDemo { 
    /** 
    * Create the device that contains the given point in screen coordinates. 
    * Robot has to be constructed differently for each monitor. 
    */ 
    public static GraphicsDevice getDevice(Point p) { 
     GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); 
     GraphicsDevice[] gs = ge.getScreenDevices(); 

     // Search the devices for the one that draws the specified point. 
     for (GraphicsDevice device : gs) { 
      GraphicsConfiguration configuration = device.getDefaultConfiguration(); 
      Rectangle bounds = configuration.getBounds(); 
      if (bounds.contains(p)) { 
       return device; 
      } 
     } 
     return null; 
    } 
    public static final Logger logger = Logger.getLogger(RobotWaitForIdleDemo.class.getName()); 
    public static void main(String[] args) { 
     LogManager.getLogManager().reset(); 
     Formatter formatter = new Formatter() { 
      @Override 
      public String format(LogRecord arg0) { 
       Date date = new Date(arg0.getMillis()); 
       DateFormat.getTimeInstance().format(date); 
       return String.format("%s %s %s %s%n", 
         DateFormat.getTimeInstance().format(date), 
         arg0.getLoggerName(), 
         arg0.getLevel(), 
         arg0.getMessage()); 
      } 
     }; 
     ConsoleHandler consoleHandler = new ConsoleHandler(); 
     consoleHandler.setFormatter(formatter); 
     logger.addHandler(consoleHandler); 

     final JFrame jframe = new JFrame("Robot experiment"); 
     GroupLayout groupLayout = new GroupLayout(jframe.getContentPane()); 

     final JButton jbutton = new JButton("Click me!"); 
     jbutton.addActionListener(new ActionListener() { 
      @Override public void actionPerformed(ActionEvent e) { 
       // Simulate a heavy Swing event handler. 
       logger.info("(swing thread) Action starting to block..."); 
       try { 
        Thread.sleep(500); 
       } catch (InterruptedException e1) {} 
       logger.info("(swing thread) Action finished blocking."); 
      } 
     }); 

     JButton tryAgainBUtton = new JButton("Automatically click above button."); 
     tryAgainBUtton.addActionListener(new ActionListener() { 
      @Override public void actionPerformed(ActionEvent e) { 
       new Thread(new Runnable() { 
        @Override public void run() { 
         try { 
          Point point = new Point(jbutton.getWidth()/2,jbutton.getHeight()/2); 
          SwingUtilities.convertPointToScreen(point, jbutton); 
          GraphicsDevice device = getDevice(point); 
          Point offset = device.getDefaultConfiguration().getBounds().getLocation(); 

          Robot robot = new Robot(device); 
          robot.setAutoWaitForIdle(true); 
          robot.setAutoDelay(30); 

          robot.mouseMove(point.x - offset.x, point.y - offset.y); 
          String threadName = Thread.currentThread().getName(); 
          logger.info(String.format("(%s) robot.mousePress(%d)", threadName, InputEvent.BUTTON1_MASK)); 
          robot.mousePress(InputEvent.BUTTON1_MASK); 
          logger.info(String.format("(%s) robot.mouseRelease(%d)", threadName, InputEvent.BUTTON1_MASK)); 
          robot.mouseRelease(InputEvent.BUTTON1_MASK); 
          logger.info(String.format("(%s) robot finished!", threadName, InputEvent.BUTTON1_MASK)); 
         } catch (AWTException ex) { 
          ex.printStackTrace(); 
         } 
        } 
       }, "robot thread").start(); 
      } 
     }); 

     jframe.getContentPane().setLayout(groupLayout); 
     groupLayout.setAutoCreateGaps(true); 
     groupLayout.setAutoCreateContainerGaps(true); 
     groupLayout.setVerticalGroup(
       groupLayout.createSequentialGroup() 
        .addComponent(jbutton) 
        .addComponent(tryAgainBUtton)); 
     groupLayout.setHorizontalGroup(
       groupLayout.createParallelGroup() 
        .addComponent(jbutton) 
        .addComponent(tryAgainBUtton)       ); 

     jframe.setSize(300, 300); 
     jframe.setVisible(true); 
     jframe.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); 
    } 
} 

我在Ubuntu上运行Java 1.6。

+0

+1 [sscce](http://sscce.org/);另请参阅[初始线程](http://download.oracle.com/javase/tutorial/uiswing/concurrency/initial.html)。 – trashgod

回答

5

也许这可以帮助您,通知没有Java7测试

您可以测试,在每一个步骤isEventDispatchThread()

import java.awt.*; 
import java.awt.event.*; 
import javax.swing.*; 
import sun.awt.SunToolkit; 

public class TestMenu { 

    /** 
    * Without a delay, SunToolkit may encounter a problem in SunToolkit (at 
    * least in JDK 6, where the drop down size problem is not present). 
    * 
    * Note: SunToolkit also has some mechanism to delay, but I forgot how it 
    * worked. 
    * 
    * <pre> 
    * Exception in thread "main" sun.awt.SunToolkit$InfiniteLoop 
    *   at sun.awt.SunToolkit.realSync(Unknown Source) 
    *   at TestMenu.syncAndDelay(TestMenu.java:172) 
    *   at TestMenu.click(TestMenu.java:88) 
    *   at TestMenu.moveAndClickCenter(TestMenu.java:150) 
    *   at TestMenu.main(TestMenu.java:45) 
    * </pre> 
    * 
    * As a bonus, the delay makes the scenario better visible for the human 
    * eye. 
    */ 
    private static int delay = 500; 
    private static JMenu[] menus = new JMenu[5]; 
    private static Dimension[] parentSizes; 
    private static Robot robot; 
    private static SunToolkit toolkit; 

    public static void main(String[] args) throws Exception { 
     robot = new Robot(); 
     toolkit = (SunToolkit) Toolkit.getDefaultToolkit(); 
     parentSizes = new Dimension[menus.length]; 
     createGUI(); // Open the first menu. Then get the drop down size of all menu's 
     moveAndClickCenter(menus[0]); 
     for (int index = 0; index < menus.length; index++) { 
      parentSizes[index] = getDropDownSize(index); 
     }// Click the last item on the last menu.   
     Component item = menus[menus.length - 1].getMenuComponent(menus[menus.length - 1].getMenuComponentCount() - 1); 
     moveAndClickCenter(item); 
     // Open the last drop down again. Then get the drop down sizes once more. If size not equal to previous size, then it's a bug. 
     boolean bug = false; 
     moveAndClickCenter(menus[menus.length - 1]); 
     for (int index = menus.length - 1; index >= 0; index--) { 
      Dimension currentSize = getDropDownSize(index); 
      System.out.print("old: " + parentSizes[index] + ", new: " + currentSize); 
      if (!parentSizes[index].equals(currentSize)) { 
       bug = true; 
       System.out.println(" ERROR"); 
      } else { 
       System.out.println(); 
      } 
     } 
     if (bug) { 
      throw new RuntimeException("JMenu drop down size is changed for no reason."); 
     } 

    } 

    private static Dimension getDropDownSize(int index) throws Exception { 
     moveToCenter(menus[index]); 
     return menus[index].getMenuComponent(0).getParent().getSize(); 
    } 

    private static void click() throws Exception { 
     robot.mousePress(InputEvent.BUTTON1_MASK); 
     robot.mouseRelease(InputEvent.BUTTON1_MASK); 
     syncAndDelay(); 
    } 

    private static void createGUI() throws Exception { 

     SwingUtilities.invokeAndWait(new Runnable() { 

      @Override 
      public void run() { 
       UIManager.LookAndFeelInfo[] infos = UIManager.getInstalledLookAndFeels();// The L&F defines the drop down policy. 
       for (final UIManager.LookAndFeelInfo info : infos) { 
        if (info.getName().toLowerCase().indexOf("metal") >= 0) { 
         if (!UIManager.getLookAndFeel().getName().equals(info.getName())) { 
          try { 
           UIManager.setLookAndFeel(info.getClassName()); 
           System.out.println("Attempt to set look and feel to " + info.getName()); 
          } catch (Exception e) { 
           e.printStackTrace(); 
          } 
         } else { 
          System.out.println("Metal look and feel is the default"); 
         } 
         break; 
        } 
       } 
       System.out.println("Testing with " + UIManager.getLookAndFeel().getName()); // Setup the GUI. 
       JFrame frame = new JFrame("A frame"); 
       frame.setJMenuBar(new JMenuBar()); 
       for (int menuIndex = 0; menuIndex < menus.length; menuIndex++) { 
        menus[menuIndex] = new JMenu("Menu " + menuIndex); 
        frame.getJMenuBar().add(menus[menuIndex]); 
        for (int itemIndex = 0; itemIndex <= menus.length - menuIndex; itemIndex++) { 
         // It seems that the problem only occurs if the drop down is displayed outside the frame at the right 
         // (not sure though). A rather long item name. 
         JMenuItem item = new JMenuItem("Menu " + menuIndex + " item " + itemIndex); 
         menus[menuIndex].add(item); 
        } 
       } 
       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
       frame.pack(); 
       frame.setLocationRelativeTo(null); 
       frame.setVisible(true); 
      } 
     }); 
     syncAndDelay(); 
    } 

    private static void moveAndClickCenter(Component c) throws Exception { 
     moveToCenter(c); 
     click(); 
    } 

    private static void moveToCenter(final Component c) throws Exception { 
     final Point cp = new Point(); 
     SwingUtilities.invokeAndWait(new Runnable() { 

      @Override 
      public void run() { 
       Point p = new Point(c.getWidth()/2, c.getHeight()/2); 
       SwingUtilities.convertPointToScreen(p, c); 
       cp.setLocation(p); 
      } 
     }); 
     robot.mouseMove(cp.x, cp.y); 
     syncAndDelay(); 
    } 

    private static void syncAndDelay() throws Exception { 
     if (delay > 0) { 
      Thread.sleep(delay); 
     } 
     toolkit.realSync(); 
    } 

    private TestMenu() { 
    } 
} 
+0

谢谢! SunToolkit.realSync正是我所需要的。但是,我无法编译代码,因为“访问限制:由于所需库的限制,无法访问类型SunToolkit /usr/lib/jvm/ia32-java-6-sun-1.6.0.26/jre/lib/rt 。罐”。但是,使用反射工作('toolkit.getClass()。getMethod(“realSync”)。invoke(toolkit)')。你用某种特殊的方式编译它了吗? – yonran

+0

从IDE,我会看看后来在家里(现在从cellmobile) – mKorbel

+0

测试,没有问题在我身边,从IDE运行,JDK6_019 ord JDK6_022 – mKorbel

2

mKorbel的答案(SunToolkit.realSync())是正确的,但realSync是缓慢的,投掷SunToolkit.InfiniteLoop。我在研究后最终使用这种变化realSync

import java.awt.Toolkit; 
import java.lang.reflect.InvocationTargetException; 
import java.lang.reflect.Method; 
import java.util.Arrays; 
import java.util.List; 

import javax.swing.SwingUtilities; 

import sun.awt.SunToolkit; 


public class ToolkitUtils { 
    private Method syncNativeQueue; 
    private boolean isSyncNativeQueueZeroArguments; 
    public ToolkitUtils() { 
     syncNativeQueue = null; 
     isSyncNativeQueueZeroArguments = true; 
     try { 
      // Since it's a protected method, we have to iterate over declared 
      // methods and setAccessible. 
      Method[] methods = SunToolkit.class.getDeclaredMethods(); 
      for (Method method: methods) { 
       String name = method.getName(); 
       if ("syncNativeQueue".equals(name)) { 
        List<Class<?>> parameterTypes = Arrays.asList(method.getParameterTypes()); 
        if (Arrays.<Class<?>>asList(long.class).equals(parameterTypes)) { 
         isSyncNativeQueueZeroArguments = false; 
        } else if (parameterTypes.isEmpty() && null == syncNativeQueue) { 
         isSyncNativeQueueZeroArguments = true; 
        } else { 
         continue; 
        } 
        syncNativeQueue = method; 
        syncNativeQueue.setAccessible(true); 
       } 
      } 
     } catch (SecurityException e) { 
      throw new RuntimeException(e); 
     } 
     if (syncNativeQueue == null) 
      throw new IllegalStateException("Could not find method SunToolkit.syncNativeQueue."); 
    } 

    /** 
    * Block until Swing has dispatched events caused by the Robot or user. 
    * 
    * <p> 
    * It is based on {@link SunToolkit#realSync()}. Use that method if you want 
    * to try to wait for everything to settle down (e.g. if an event listener 
    * calls {@link java.awt.Component#requestFocus()}, 
    * {@link SwingUtilities#invokeLater(Runnable)}, or 
    * {@link javax.swing.Timer}, realSync will block until all of those are 
    * done, or throw exception after trying). The disadvantage of realSync is 
    * that it throws {@link SunToolkit.InfiniteLoop} when the queues don't 
    * become idle after 20 tries. 
    * 
    * <p> 
    * Use this method if you only want to wait until the direct event listeners 
    * have been called. For example, if you need to simulate a user click 
    * followed by a stream input, then you can ensure that they will reach the 
    * program under test in the right order: 
    * 
    * <pre> 
    * robot.mousePress(InputEvent.BUTTON1); 
    * toolkitUtils.flushInputEvents(10000); 
    * writer.write("done with press"); 
    * </pre> 
    * 
    * @see {@link java.awt.Robot#waitForIdle()} is no good; does not wait for 
    *  OS input events to get to the Java process. 
    * @see {@link SunToolkit#realSync()} tries 20 times to wait for queues to 
    *  settle and then throws exception. In contrast, flushInputEvents does 
    *  not wait for queues to settle, just to flush what's already on them 
    *  once. 
    * @see {@link java.awt.Toolkit#sync()} flushes graphics pipeline but not 
    *  input events. 
    * 
    * @param syncNativeQueueTimeout 
    *   timeout to use for syncNativeQueue. Something like 10000 is 
    *   reasonable. 
    */ 
    public void flushInputEvents(long syncNativeQueueTimeout) { 
     SunToolkit toolkit = (SunToolkit) Toolkit.getDefaultToolkit(); 

     // 1) SunToolkit.syncNativeQueue: block until the operating system 
     // delivers Robot or user events to the process. 
     try { 
      if (isSyncNativeQueueZeroArguments) { 
       // java 1.6 
       syncNativeQueue.invoke(toolkit); 
      } else { 
       // java 1.7 
       syncNativeQueue.invoke(toolkit, syncNativeQueueTimeout); 
      } 
     } catch (IllegalArgumentException e) { 
      throw new RuntimeException(e); 
     } catch (IllegalAccessException e) { 
      throw new RuntimeException(e); 
     } catch (InvocationTargetException e) { 
      throw new RuntimeException(e); 
     } 

     // 2) SunToolkit.flushPendingEvents: block until the Toolkit thread 
     // (aka AWT-XAWT, AWT-AppKit, or AWT-Windows) delivers enqueued events 
     // to the EventQueue 
     SunToolkit.flushPendingEvents(); 

     // 3) SwingUtilities.invokeAndWait: block until the Swing thread (aka 
     // AWT-EventQueue-0) has dispatched all the enqueued input events. 
     try { 
      SwingUtilities.invokeAndWait(new Runnable(){ 
       @Override public void run() {}}); 
     } catch (InterruptedException e) { 
      throw new RuntimeException(e); 
     } catch (InvocationTargetException e) { 
      throw new RuntimeException(e); 
     } 
    } 
} 
+0

有趣的是什么错了+1 – mKorbel