Running on Java 24-ea+21-2447 (Preview)
Home of The JavaSpecialists' Newsletter

007java.awt.EventQueue

Author: Dr. Heinz M. KabutzDate: 2001-02-01Java Version: 1.3Category: GUI
 

Abstract: In this newsletter, we learn how we can create our own EventQueue and then use that to intercept all the events that arrive in AWT/Swing.

 

Welcome to the 8th issue of The Java(tm) Specialists' Newsletter! Ok, it is only the 7th issue, just testing if you're awake. This week I want to tell you the answer to my question in the first newsletter, namely, "under what circumstances is it possible to have more than one AWT event queue?" I didn't particularly go looking for an answer, but found one "by accident" while looking for a solution to another problem.

But, before I tell you how to use multiple event queues, let me bore you with a tale of why I went looking for this. A bit over a year ago, SUN released JDK 1.3 beta, and one of my colleagues, Java GUI fundi Herman Lintvelt, told me that it contained a new class called java.awt.Robot. This "Robot" could be instructed to issue native mouse events, keyboard events or take screen shots. The purpose of this Robot was to make it possible to write tests that emulated real users by issuing native actions using a platform-independent Java interface. It is difficult to find testers willing to do repetitive, boring testing, such as clicking ALL the buttons on an application each time a build is made. The Robot could be instructed to jump to a specific place on the screen and press mouse buttons and take screen shots if necessary.

JavaSpecialists quickly got stuck in and developed a testing framework around this "Robot". It is driven by scripts in which you can specify the text on the component that should be "clicked". The framework found the exact location of the component on the screen and issued a native windows click using the java.awt.Robot. Components could thus be located precisely inspite of layout managers. The code contained some magic tricks, as you might imagine, which I will not reveal (for now - maybe later, once I've sold enough copies of the framework). Luckily I had fellow Java Contractor Guild member Sydney Redelinghuys on my payroll and together we eeked information from the VM using the standard interfaces which a casual observer would not see.

The framework could be instructed to take screen shots at certain check- points of the script, or to take screen shots if it detected an error. It could find components located on different tab sheets, components not visible on the screen due to scrolling, items in combo boxes, etc.

The main "acceptance" problem we have experienced with the UIRobot is that it is quite difficult to write a comprehensive script, especially if you are not a programmer by profession. Most testers I have met struggle to write such scripts, and would prefer an automatic procedure of recording the script.

javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.

java.awt.EventQueue

In order to make my UIRobot more acceptable to the marketplace, I needed a way in which I could unobtrusively hook myself into the AWT event system. I wanted a hotkey that users of the UIRobot could employ from any window to open the UIRobot dialog. The user could then "record" a script, "play" a script or "edit" a script, much like the macro recorder in MS Office (TM), using text on the components to locate them again. The only thing I expect the client code to do is to call

Class.forName("eu.javaspecialists.ui.robot.UIRobot");

in order to give the UIRobot class a chance to register itself.

Once that happens, I want the hotkey to be available from any frame, dialog, component, focused or not, etc. It should be a global hotkey that you can press to activate the UIRobot dialog. I had a look at the EventQueue for my first newsletter and noticed that it followed, roughly, the Chain of Responsibility design pattern. You can register a new event queue and make it responsible for handling any new events that arrive.

I saw this pattern quite quickly, but it took me 3 hours (!) to finally get it working. Had I been more careful, it should have taken not more than 5 minutes, but sometimes I am a bit slow on the uptake. One of the disadvantages of having written too much generic Java code is that I don't recognise "private" methods as an obstacle, because I can just invoke them anyway. Designers of frameworks are not necessarily very skilled at guessing how I want to extend their framework so they often make a method private instead of protected. I even went so far as to decompile SUN's implementation of java.awt.Toolkit, namely sun.awt.SunToolkit, to try and find some way of hooking into the event queue. In the end the correct and most simple way of doing this was to write a subclass of EventQueue, called MyEventQueue, and to register it as the now reigning king of the Democratic Republic of Events with the command:

Toolkit.getDefaultToolkit().getSystemEventQueue().push(
  new MyEventQueue());

The reason it took me 3 hours to figure these couple of lines out was because I overrode the postEvent() method, instead of the dispatchEvent() method, duh! An example of MyEventQueue could look like this:

//: MyEventQueue.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class MyEventQueue extends EventQueue {
  protected void dispatchEvent(AWTEvent event) {
    // the only functionality I add is that I print out all the events
    System.out.println(event);
    super.dispatchEvent(event);
  }
}

So, what type of functionality can you achieve with this code? You can write a global hotkey manager, you can write a recorder that generates scripts for the UIRobot or you can disable all user actions while the GUI is busy with something else. Those of you who've tried to disable GUI input have probably used the GlassPane of the JFrame which can catch all mouse events, but not keyboard shortcuts. Thanks to David Geary for that idea in his classic book on Swing!

I mentioned to one of our system engineers the possibility of using this event queue mechanism as a global hotkey manager. He got very excited and called Herman away from the company month-end barbacue to come talk to me. We had been struggling to get application-wide global hotkeys working for 3.5 years in our application and one mayor customer site was holding back on a purchase because of that problem. Herman and I sat down and we came up with the GlobalHotkeyManager, where you can register any input / action combination and it matches the two for you on a global scale. Note that you need JDK 1.3 to use ActionMap and InputMap. The other parts work in JDK 1.2.

//: GlobalHotkeyManager.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class GlobalHotkeyManager extends EventQueue {
  private static final boolean DEBUG = true; // BUG? what's that? ;-))
  private static final GlobalHotkeyManager instance =
    new GlobalHotkeyManager();
  private final InputMap keyStrokes = new InputMap();
  private final ActionMap actions = new ActionMap();
  static {
    // here we register ourselves as a new link in the chain of
    // responsibility
    Toolkit.getDefaultToolkit().getSystemEventQueue().push(instance);
  }
  private GlobalHotkeyManager() {} // One is enough - singleton
  public static GlobalHotkeyManager getInstance() {
    return instance;
  }
  public InputMap getInputMap() {
    return keyStrokes;
  }
  public ActionMap getActionMap() {
    return actions;
  }
  protected void dispatchEvent(AWTEvent event) {
    if (event instanceof KeyEvent) {
      // KeyStroke.getKeyStrokeForEvent converts an ordinary KeyEvent
      // to a keystroke, as stored in the InputMap.  Keep in mind that
      // Numpad keystrokes are different to ordinary keys, i.e. if you
      // are listening to
      KeyStroke ks = KeyStroke.getKeyStrokeForEvent((KeyEvent)event);
      if (DEBUG) System.out.println("KeyStroke=" + ks);
      String actionKey = (String)keyStrokes.get(ks);
      if (actionKey != null) {
        if (DEBUG) System.out.println("ActionKey=" + actionKey);
        Action action = actions.get(actionKey);
        if (action != null && action.isEnabled()) {
          // I'm not sure about the parameters
          action.actionPerformed(
            new ActionEvent(event.getSource(), event.getID(),
              actionKey, ((KeyEvent)event).getModifiers()));
          return; // consume event
        }
      }
    }
    super.dispatchEvent(event); // let the next in chain handle event
  }
}

Together with the GlobalHotkeyManager.java we have a test program:

//: GlobalHotkeyManagerTest.java
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class GlobalHotkeyManagerTest extends JFrame {
  private static final String UIROBOT_KEY = "UIRobot";
  private final KeyStroke uirobotHotkey = KeyStroke.getKeyStroke(
    KeyEvent.VK_R, KeyEvent.CTRL_MASK + KeyEvent.ALT_MASK, false);
  private final Action uirobot = new AbstractAction() {
    public void actionPerformed(ActionEvent e) {
      setEnabled(false); // stop any other events from interfering
      JOptionPane.showMessageDialog(GlobalHotkeyManagerTest.this,
        "UIRobot Hotkey was pressed");
      setEnabled(true);
    }
  };
  public GlobalHotkeyManagerTest() {
    super("Global Hotkey Manager Test");
    setSize(500,400);
    getContentPane().setLayout(new FlowLayout());
    getContentPane().add(new JButton("Button 1"));
    getContentPane().add(new JTextField(20));
    getContentPane().add(new JButton("Button 2"));
    GlobalHotkeyManager hotkeyManager = GlobalHotkeyManager.getInstance();
    hotkeyManager.getInputMap().put(uirobotHotkey, UIROBOT_KEY);
    hotkeyManager.getActionMap().put(UIROBOT_KEY, uirobot);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // JDK 1.3
    setVisible(true);
  }
  public static void main(String[] args) {
    new GlobalHotkeyManagerTest();
  }
}

We basically match a KeyStroke (CTRL+ALT+R) with an Action. Since the action can be invoked from anywhere, we must remember to switch it off while we are handling it, otherwise it could be invoked again by mistake. Try out what happens when you don't disable the action and press the hotkey twice.

This is one of the most interesting things I've discovered in Swing and is so extremely useful that I do not understand why it is not more widely publicised. Please let me know if you've done something similar in your coding. It seems that SUN are quite good at adding new features or improving code without bothering to tell anyone, or at least not me! ;-) At more than 500'000 lines of code in the JDK 1.3, it becomes tiresome to read through it all each time a new release comes out.

By now, you have hopefully seen the value of understanding OO Design Patterns if you want to become good at Java. I have found that Java lends itself to good OO design, certainly more than C++.

I want to thank all of you who took the time to read and respond to my newsletters, your feedback is what really makes this worthwhile.

Regards

Heinz

 

Comments

We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)

When you load these comments, you'll be connected to Disqus. Privacy Statement.

Related Articles

Browse the Newsletter Archive

About the Author

Heinz Kabutz Java Conference Speaker

Java Champion, author of the Javaspecialists Newsletter, conference speaking regular... About Heinz

Superpack '23

Superpack '23 Our entire Java Specialists Training in one huge bundle more...

Free Java Book

Dynamic Proxies in Java Book
Java Training

We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.

Java Consulting

We can help make your Java application run faster and trouble-shoot concurrency and performance bugs...