|
The Java Specialists' Newsletter
Issue 007 2001-02-01
Category:
GUI
Java version: java.awt.EventQueueby Dr. Heinz M. Kabutz
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.
Maximum Solutions 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.
Upcoming Java Specialist Master Courses:
- please click here to sign up.
As from May 2010, we are also offering this course on the island of Crete. We
only accept 6 students per class in Crete, due to the size of our conference
room. Please book early to avoid disappointment!
San Jose CA, Mar 16-19 2010, $3500 Ottawa, Canada, Mar 22-25 2010, $3500 Oslo, Norway, Apr 13-16 2010, Kr 24500 Montreal, Canada, Apr 20-23 2010, $3500 Toronto, Canada, May 17-20 2010, $3500 Chania, Crete, May 25-28, Jun 29-Jul 2 or Aug 24-27 2010, €2500
In-house courses if these dates or locations do not suit you - click here for more information. Multiple event queues
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("com.maxoft.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 final static 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
GUI Articles
Related Java Course
|