Swing and threads
Hi
I am unsure on the exact rules of interacting with swing objects from different threads.
My current understanding is that everything is meant to happen from EventDispatchThread ie updating resizing setting values etc.
Currently I have windows that accept user input, I capture the enter key being pressed (or okay being pressed) . I then read in all the value from the swing objects whilst in the Event Dispatch Thread and save them to intermediary variables. I then start up another thread (use thread pools) and use the intermediary variables to access the information.
I was thinking, it shouldn't be a problem to just disable the windows when I capture the event and then on the other thread read the values back in and re enable ?
Just seems like I have duplicate all my variables because I don't want to do any processing in the Event Dispatch Thread as I am accessing DB's.
For people who normally build threaded swing apps, whats the best practice on this ?
another question from this is can I set the values of these swing objects from another thread ? ie is it just the building of the swing objects that has to be done on Event Dispatch Thread ?
Thanks
# 1
Hi,
You're on the right track. First of all, you only need to use the Event Dispatch Thread (EDT) to modify swing components (think write). You can use any thread to read from swing components. So you may not need so many duplicate variables.
Disabling all the components in a window while doing processing will work. There are generally three best practices:
- "Disable Everything": Disable all the buttons and display a message "Busy, please wait..."
- "Blocking glasspane": Use a glass pane to block mouse/keyboard input and display an hourglass cursor. No message is needed.
- "Progress dialog": Display a modal dialog that displays a message saying "Busy, please wait...". Optionally provide a cancel button.
There is a tradeoff in each approach.
- "Disable Everything" and ""Blocking glasspane" are good if the operations are short (less that 5-10 seconds).
- If the operation takes longer than 5-10 seconds, then use a progress dialog.
- In a large application there are progress dialogs (some with a % percentage complete and some that do not provide an estimate), and also use of the hourglass and/or disabling components. Each approach can be used when it works best.
# 4
Here is some code to play with:
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.border.*;
public class DisabledGlassPane 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();
public DisabledGlassPane()
{
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)
{
e.consume();
}
public void keyTyped(KeyEvent e) {}
public void keyReleased(KeyEvent 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();
}
public void deactivate()
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
setCursor(null);
setVisible( false );
}
});
}
public static void main(String[] args)
{
final DisabledGlassPane glassPane = new DisabledGlassPane();
glassPane.setBackground( new Color(255, 128, 128, 128) );
glassPane.setForeground( Color.WHITE );
final JTextField textField = new JTextField();
final JButton button = new JButton( "Click Me" );
button.setMnemonic('c');
button.addActionListener( new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
glassPane.activate("Please Wait...");
Thread thread = new Thread()
{
public void run()
{
try { this.sleep(5000); }
catch (InterruptedException ie) {}
glassPane.deactivate();
}
};
thread.start();
}
});
JFrame frame = new JFrame();
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.setGlassPane( glassPane );
frame.getContentPane().add(new JLabel("NORTH"), BorderLayout.NORTH );
frame.getContentPane().add( button );
//frame.getContentPane().add(new JTextField(), BorderLayout.SOUTH);
frame.getContentPane().add(textField, BorderLayout.SOUTH);
frame.setSize(300, 300);
frame.setLocationRelativeTo( null );
frame.setVisible(true);
}
}
> can I re enable the window from a non EDT - including reseting the mouse pointer etc
The theory is that all updates to the GUI should be done in the EDT to prevent multiple threads from updating the GUI at the same time. So changing the mouse cursor and removing the glass pane from the GUI result the a repainting of the GUI. So to be safe, yes it should be done in the EDT.
Will my example work if the deactivate() method is changed to not use invokeLater()? Yes, because in the millisecond or so it takes to remove the glass pane and repaint the GUI it is extremely unlikely that any other events affecting the GUI will be generated. But that one time it doesn't work you could spend hours trying to duplicate the problem.