I would like to display an opaque overlay with a loading description or spinner over the top of my JFrame when a button is clicked and disappear again when the action has been performed. I have read up on Glass Pane but I cannot understand the right way to do this underneath an action performed function with a button. Is there a way to do this using Java and Swing? By the way here is my JFrame at the moment...
public class Frame {
private JButton btnH;
/** Main Panel */
private static final Dimension PANEL_SIZE = new Dimension(500, 500);
private static JPanel panel = new JPanel();
public Frame() {
init();
panel.setLayout(null);
panel.setPreferredSize(PANEL_SIZE);
}
public void init() {
btnH = new JButton("HELP");
btnH.setBounds(50, 50, 100, 25);
panel.add(btnH);
// Action listener to listen to button click and display pop-up when received.
btnH.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
// Message box to display.
JOptionPane.showMessageDialog(null, "Helpful info...", JOptionPane.INFORMATION_MESSAGE);
}
});
}
public JComponent getComponent() {
return panel;
}
private static void createAndDisplay() {
JFrame frame = new JFrame("Frame");
frame.getContentPane().add(new Frame().getComponent());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
createAndDisplay();
}
});
}
There hard part about this isn't the glass pane, but the interoperability between the UI and the SwingWorker
.
There are lots of ways you might do this, this is just one.
You should start by reading through How to Use Root Panes which goes into how to use glass panes and Worker Threads and SwingWorker, because until you get your head around it, it will mess with you.
The important things to note here are:
SwingWorker
is forHence, the importance of the SwingWorker
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.MouseAdapter;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new GridBagLayout());
JButton workButton = new JButton("Do some work already");
add(workButton);
workButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
workButton.setEnabled(false);
ProgressPane progressPane = new ProgressPane();
// This is a dangrous kind of thing to do and you should
// check that the result is a JFrame or JDialog first
JFrame parent = (JFrame) SwingUtilities.windowForComponent(TestPane.this);
parent.setGlassPane(progressPane);
progressPane.setVisible(true);
// This is a little bit of overkill, but it allows time
// for the component to become realised before we try and
// steal focus...
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
progressPane.requestFocusInWindow();
}
});
Worker worker = new Worker();
worker.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if ("state".equals(evt.getPropertyName())) {
if (worker.getState() == SwingWorker.StateValue.DONE) {
progressPane.setVisible(false);
workButton.setEnabled(true);
}
} else if ("progress".equals(evt.getPropertyName())) {
double value = (int) evt.getNewValue() / 100.0;
progressPane.progressChanged(value);
}
}
});
worker.execute();
}
});
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
public class Worker extends SwingWorker<Object, Object> {
@Override
protected Object doInBackground() throws Exception {
for (int value = 0; value < 100; value++) {
Thread.sleep(100);
value++;
setProgress(value);
}
return this;
}
}
public interface ProgressListener {
public void progressChanged(double progress);
}
public class ProgressPane extends JPanel implements ProgressListener {
private JProgressBar pb;
private JLabel label;
private MouseAdapter mouseHandler = new MouseAdapter() {
};
private KeyAdapter keyHandler = new KeyAdapter() {
};
private FocusAdapter focusHandler = new FocusAdapter() {
@Override
public void focusLost(FocusEvent e) {
if (isVisible()) {
requestFocusInWindow();
}
}
};
public ProgressPane() {
pb = new JProgressBar(0, 100);
label = new JLabel("Doing important work here...");
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.insets = new Insets(8, 8, 8, 8);
add(pb, gbc);
add(label, gbc);
setOpaque(false);
}
@Override
public void addNotify() {
super.addNotify();
addMouseListener(mouseHandler);
addMouseMotionListener(mouseHandler);
addMouseWheelListener(mouseHandler);
addKeyListener(keyHandler);
addFocusListener(focusHandler);
}
@Override
public void removeNotify() {
super.removeNotify();
removeMouseListener(mouseHandler);
removeMouseMotionListener(mouseHandler);
removeMouseWheelListener(mouseHandler);
removeKeyListener(keyHandler);
removeFocusListener(focusHandler);
}
@Override
public void progressChanged(double progress) {
pb.setValue((int) (progress * 100));
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(new Color(128, 128, 128, 224));
g2d.fillRect(0, 0, getWidth(), getHeight());
g2d.dispose();
}
}
}
Just as a side note, I have verified that the PropertyChangeListener
used by the SwingWorker
is updated within the context of the EDT
You should also take a look at JLayer
(formally known as JXLayer
)
It's like glass pane on steroids
Now, if you really want to do something fancy, you could do something like this for example