Type ahead buffer
I have a data entry application written in Java.
The user can insert data into a form and, when
he presses the OK button, all the informations
will be sent to the server for validation. Then
the Java application waits for the server answer.
If all is OK, the application shows some another
form. If there is an error, the form remains the
same and the error message will be shown using a
dialog box.
The problem is when the Java application waits
for the server answer. During this time, the user,
that knows that data is correct, don't want to
wait but keep typing information using keyboard.
As I don't know the next text field that will get
the focus, this events will be lost.
Now, how to temporary stop the key event dispatching
thread so it can keep this events into his buffer
and dispatch them only when I want?
Thanks a lot
[960 byte] By [
CanapaGa] at [2007-11-27 1:57:46]

# 1
The code is messy. I just combined code for two examples:
a) one that shows how to use a Robot to playback KeyEvents
b) another that uses a GlassPane to intercept events on a frame while executing some long running task
But it may give you some ideas. Click on the "Connect to Server" button and the frame will be disabled for 10 seconds. Then click on the "Playback the Buffer" button and any KeyStrokes will be displayed in the text fields.
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.border.*;
public class RobotBuffer extends JComponent
implements KeyListener
{
private final static Color DEFAULT_BACKGROUND = new Color(128, 128, 128, 128);
private final static Border MESSAGE_BORDER = new EmptyBorder(10, 10, 10, 10);
private JLabel message = new JLabel();
private java.util.List playback = new ArrayList();
public RobotBuffer()
{
setOpaque( false );
setBackground( DEFAULT_BACKGROUND );
setLayout( new GridBagLayout() );
add(message, new GridBagConstraints());
message.setOpaque(true);
message.setBorder(MESSAGE_BORDER);
// Disable Mouse, Key and Focus events for the glass pane
addMouseListener( new MouseAdapter() {} );
addMouseMotionListener( new MouseMotionAdapter() {} );
addKeyListener( this );
setFocusTraversalKeys(
KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, Collections.EMPTY_SET );
setFocusTraversalKeys(
KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, Collections.EMPTY_SET );
}
protected void paintComponent(Graphics g)
{
g.setColor( getBackground() );
g.fillRect(0, 0, getSize().width, getSize().height);
}
public void setBackground(Color background)
{
super.setBackground( background );
Color messageBackground =
new Color(background.getRed(), background.getGreen(), background.getBlue());
message.setBackground( messageBackground );
}
public void keyPressed(KeyEvent e)
{
playback.add(e);
e.consume();
}
public void keyTyped(KeyEvent e) {}
public void keyReleased(KeyEvent e)
{
playback.add(e);
e.consume();
}
public void activate(String text)
{
if (text != null && text.length() > 0)
{
message.setVisible( true );
message.setText( text );
message.setForeground( getForeground() );
}
else
message.setVisible( false );
setVisible( true );
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
requestFocusInWindow();
playback.clear();
}
public void deactivate()
{
setCursor(null);
setVisible( false );
}
public void playback()
{
try
{
Robot robot = new Robot();
robot.setAutoDelay( 5 );
for (int i = 0; i < playback.size();i++)
{
KeyEvent event = (KeyEvent)playback.get(i);
if (event.getID() == KeyEvent.KEY_PRESSED)
if (event.getKeyCode() == KeyEvent.VK_TAB)
KeyboardFocusManager.getCurrentKeyboardFocusManager().focusNextComponent();
else
robot.keyPress( event.getKeyCode() );
else
robot.keyRelease( event.getKeyCode() );
}
}
catch(Exception exc)
{
System.out.println(exc);
}
}
public static void main(String[] args)
{
final RobotBuffer glassPane = new RobotBuffer();
glassPane.setBackground( new Color(255, 128, 128, 128) );
glassPane.setForeground( Color.WHITE );
final JPanel north = new JPanel();
north.add( new JTextField(5) );
north.add( new JTextField(5) );
north.add( new JTextField(5) );
north.add( new JTextField(5) );
final JTextField textField = new JTextField();
final JButton serverButton = new JButton( "Connect to Server" );
serverButton.addActionListener( new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
glassPane.activate("Connecting to Server...");
Thread thread = new Thread()
{
public void run()
{
try { this.sleep(10000); }
catch (InterruptedException ie) {}
glassPane.deactivate();
}
};
thread.start();
}
});
final JButton playbackButton = new JButton( "PLayback the Buffer" );
playbackButton.addActionListener( new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
Thread playback = new Thread()
{
public void run()
{
north.getComponent(0).requestFocus();
glassPane.playback();
}
};
playback.start();
}
});
JFrame frame = new JFrame();
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.setGlassPane( glassPane );
frame.getContentPane().add(north, BorderLayout.NORTH );
frame.getContentPane().add( serverButton );
frame.getContentPane().add(playbackButton, BorderLayout.SOUTH);
frame.setSize(300, 300);
frame.setLocationRelativeTo( null );
frame.setVisible(true);
}
}
# 2
Thanks a lot Camickr.
Let me change your code just a little bit. Now, after the end of
the long running task, I'd like to playback the buffer automatically.
In fact, for users that run my application, this trick should be
invisible. So, what happens? If the user keep typing, the new
events produced by the keyboard will be mixed up with the buffered
event reproduced by the Robot. For example:
- The application state is: "Connecting to server...";
- I'm typing: 12345 6789 ;
- After the number 5 , the "Playback the buffer" starts but I
keep typing;
At the end, in the text field, eventually, I will obtain: 126345789
that is in the wrong order.
That's why I'm looking for a "native solution" that temporary stops
the JVM dispatching system or something ... that work! ^_^
Regards
# 3
> That's why I'm looking for a "native solution" that temporary stops the JVM dispatching system or something
Well, I don't know how you can stop the dispatching system completely, since you need to store the events and then play them back, before switching back to the regular dispatching system. So you somehow need to make sure that all events have been played back from the buffer before you can start accepting events in the normal matter again.
You could try having the Robot do the playback in the GUI Event Thread rather than creating a non GUI Thread. This means that the events added to the GUI Event Thread can't be executed until the playback is finished executing. You do this by wrapping the "playback" method in a SwingUtilities.invokeLater(...);
Also, instead of dealing with all the glass pane code I gave you it may be easier to temporarily use your own EventQueue. So when you start connect to the server you could instal your own EventQueue to save the events. Then when the server connection is finished you would start the playback on the GUI event Thread and then restore the default EventQueue before looping through all the events to play back.
You override the Event Queue like this:
EventQueue queue = new EventQueue()
{
protected void dispatchEvent(AWTEvent event)
{
if (event.getID() == KeyEvent.KEY_TYPED
|| event.getID() == KeyEvent.KEY_PRESSED
|| event.getID() == KeyEvent.KEY_RELEASED)
{
//add events to playback list
return;
}
else
super.dispatchEvent(event);
}
};
Toolkit.getDefaultToolkit().getSystemEventQueue().push(queue);