javaswingjframeglasspane

Display an overlay when a button is click and disappear again when the action has been performed using Swing


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();
        }
    });
}

Solution

  • 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:

    Hence, 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)

    For example, example

    It's like glass pane on steroids

    Now, if you really want to do something fancy, you could do something like this for example