2012-06-14 2 views
8

내 Swing 응용 프로그램의 통합 테스트에 java.awt.Robot을 사용하고 있지만 올바른 순서로 내 동작을 실행하는 데 문제가 있습니다. Swing이 해당 이벤트를 처리하기 전까지 robot.mousePressed(...) 호출을 차단하는 스레드에게 어떻게 알릴 수 있습니까? 분명히 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); 
    } 
} 

저는 Java 1.6을 우분투에서 실행하고 있습니다.

+0

+1 [sscce] (http://sscce.org/)에 대해 +1했다. [초기 스레드] (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는 필자가 필요로하는 것입니다. 그러나 "액세스 제한 : 필요한 라이브러리 /usr/lib/jvm/ia32-java-6-sun-1.6.0.26/jre/lib/rt의 제한으로 인해 SunToolkit 유형에 액세스 할 수 없기 때문에 코드를 컴파일 할 수 없습니다. .항아리". 그러나 reflection을 사용하면 ('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

재미있는 whats wrong +1 – mKorbel

관련 문제