2012-06-14 18 views
8

Estoy usando java.awt.Robot para las pruebas de integración de mi aplicación Swing, pero tengo problemas para ejecutar mis acciones en el orden correcto. ¿Cómo puedo decirle al hilo que llama al robot.mousePressed(...) que bloquee hasta que Swing termine de despachar ese evento? Aparentemente, robot.setAutoWaitForIdle(true) no sirve.¿java.awt.Robot.waitForIdle() espera a que se envíen los eventos?

Aquí está mi demostración. ¡Espero que el "robot haya terminado!" mensaje para que siempre aparezca después de "Acción finalizó el bloqueo", pero en su lugar, a menudo sucede demasiado pronto.

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); 
    } 
} 

Estoy ejecutando Java 1.6 en Ubuntu.

+0

+1 para [sscce] (http://sscce.org/); ver también [Temas iniciales] (http://download.oracle.com/javase/tutorial/uiswing/concurrency/initial.html). – trashgod

Respuesta

5

tal vez esto se puede ayudar a usted, aviso no probados en Java7

puede probar que en cada uno de los pasos para 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

¡Gracias! SunToolkit.realSync era exactamente lo que necesitaba. Sin embargo, no pude compilar su código porque "Restricción de acceso: No se puede acceder al tipo SunToolkit debido a la restricción en la biblioteca requerida /usr/lib/jvm/ia32-java-6-sun-1.6.0.26/jre/lib/rt .tarro". Sin embargo, el uso de la reflexión funcionó ('toolkit.getClass(). GetMethod (" realSync "). Invoke (toolkit)'). ¿Lo compilaste de alguna manera especial? – yonran

+0

de IDE, lo veré más tarde en casa (ahora desde el móvil) – mKorbel

+0

probado, no hay problema de mi parte, ejecutándose desde IDE, JDK6_019 ord JDK6_022 – mKorbel

2

respuesta de mKorbel (SunToolkit.realSync()) es correcta, pero realSync es lento y lanza SunToolkit.InfiniteLoop. Terminé usando esta variación después de estudiar 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

interesante cuál es incorrecto +1 – mKorbel

Cuestiones relacionadas