Отображать наложение при нажатии кнопки и снова исчезать, когда действие было выполнено с помощью Swing.

Я хотел бы отобразить непрозрачное наложение с описанием загрузки или счетчиком поверх моего JFrame при нажатии кнопки и снова исчезнуть, когда действие было выполнено. Я читал о Glass Pane, но не могу понять, как правильно это сделать под действием, выполняемым функцией с помощью кнопки. Есть ли способ сделать это с помощью Java и Swing? Кстати вот мой JFrame на данный момент...

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

person JS91    schedule 17.01.2020    source источник
comment
Определение мне непонятно. Вы хотите отображать JOptionPane поверх JFrame и удалять его по завершении определенного действия?   -  person c0der    schedule 17.01.2020
comment
Я хотел бы отобразить непрозрачное наложение с описанием загрузки или счетчиком - тогда просто используйте JProgressBar в качестве предложения в ответе ниже. В учебнике Swing есть рабочий пример. Если вам нужен больший контроль и вы хотите полупрозрачное наложение, вы можете использовать Glass Pane. См.: stackoverflow.com/questions/27822671 / для реализации с простым API.   -  person camickr    schedule 17.01.2020


Ответы (2)


Сложность заключается не в стеклянной панели, а в совместимости между пользовательским интерфейсом и SwingWorker.

Есть много способов сделать это, это только один.

Вам следует начать с прочтения Как использовать корневые панели, которые рассказывается, как использовать стеклянные панели и рабочие потоки и SwingWorker. , потому что, пока вы не разберетесь с этим, он будет вас бесить.

Здесь важно отметить следующее:

  • Swing является однопоточным, вы не должны блокировать поток диспетчеризации событий, для чего предназначен SwingWorker
  • Swing НЕ является потокобезопасным. Это означает, что вы не должны изменять или обновлять пользовательский интерфейс, прямо или косвенно, вне контекста потока диспетчеризации событий.

Следовательно, важность 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();
        }

    }

}

В качестве примечания я проверил, что PropertyChangeListener, используемый SwingWorker, обновляется в контексте EDT.

Вы также должны взглянуть на JLayer (ранее известный как JXLayer)

Например, пример

Это как стекло на стероидах

Теперь, если вы действительно хотите сделать что-то необычное, вы можете сделать что-то вроде это, например

person MadProgrammer    schedule 17.01.2020
comment
Большое спасибо @MadProgrammer! Это действительно помогло мне, и информация, которую вы предоставили, великолепна! Обязательно попробую что-нибудь из ваших идей. - person JS91; 18.01.2020

Если я правильно понял, нужно отображать какой-то текущий прогресс, пока кнопка не закончит свою работу.

Если это так, вы можете использовать CardLayout с неопределенным JProgressBar.

CardLayout – это LayoutManager (например, BorderLayout, FlowLayout и т. д.), с помощью которого можно определить карточки. Каждая карта представляет собой Component (например, Container с другими Component, например JPanel). В каждый момент времени может быть видна только одна карточка. Каждая карта связана со строкой, чтобы идентифицировать ее и иметь возможность выбрать ее как видимую над другими. Вы можете узнать больше о CardLayout в соответствующем руководстве по Java.

JProgressBar — это индикатор выполнения, т. е. JComponent, который показывает ход текущей задачи. Различают два режима: определенный и неопределенный. В детерминированном режиме вы указываете размер проблемы и сами продвигаете индикатор выполнения с помощью кода. В неопределенном режиме постоянно крутится ручка-индикатор (что позволяет пользователю узнать, что выполняется текущая задача и что программа не знает, сколько времени это займет). JProgressBar можно использовать как простой визуальный индикатор для пользователя. Вы можете узнать больше о JProgressBar в соответствующем руководстве по Java.

Так что в вашем случае можно использовать CardLayout с двумя карточками, где одна карточка содержит кнопку "Помощь", а другая неопределенная JProgressBar. Когда пользователь нажимает «Справка», вы показываете карточку индикатора выполнения, а когда прогресс завершен, вы снова переключаетесь на карточку кнопки «Справка».

Теперь, если вы выполняете прогресс внутри ActionListener кнопки «Справка», это будет выполняться в потоке отправки событий (или EDT для краткости). Таким образом, ваши карты не смогут быть переключены, потому что переключение происходит также внутри EDT. В этом случае мы собираемся создать отдельный SwingWorker для управления прогрессом. Так что единственное, что будет делать ActionListener, это создать и запустить такой SwingWorker. Таким образом, ActionListener закончится раньше, чем закончится прогресс, и карты будут заменены местами.

Рассмотрим следующий пример кода:

import java.awt.CardLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingWorker;

public class MyPanel extends JPanel {

    private static final Dimension PANEL_SIZE = new Dimension(500, 500);

    public MyPanel() {
        //Always prefer a layout instead of setting it to null.
        super(new CardLayout()); //Set the layout of the main panel to CardLayout, so we can add the cards...

        //Obtain the CardLayout we just created for this panel:
        final CardLayout cardLayout = (CardLayout) super.getLayout();

        //String names of each card:
        final String nameForCardWithButton = "BUTTON",
                     nameForCardWithProgress = "PROGRESS";

        //Creating first card...
        final JPanel cardWithButton = new JPanel(new GridBagLayout());
        /*Using a GridBagLayout in a panel which contains a single component (such as the
        cardWithButton panel, containing a single JButton) will layout the component in
        the center of the panel.*/
        final JButton btnH = new JButton("HELP");
        cardWithButton.add(btnH);

        // Action listener to listen to button click and display pop-up when received.
        btnH.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) {

                cardLayout.show(MyPanel.this, nameForCardWithProgress); //Switch to progress bar card...

                //Create and start worker Thread:
                new SwingWorker() {
                    @Override
                    protected Object doInBackground() throws Exception {
                        /*Simulate a long ongoing process without blocking the EDT...
                        Well, actually, the JOptionPane will block the EDT I think, so I will leave
                        it here for demonstration puprposes only.*/
                        JOptionPane.showMessageDialog(null, "Helpful info...", "info", JOptionPane.INFORMATION_MESSAGE);
                        return null; //Not sure if returning null here is a good practice or not.
                    }

                    @Override
                    protected void done() {
                        cardLayout.show(MyPanel.this, nameForCardWithButton); //Switch back to button card, when the job has finished.
                    }
                }.execute();
            }
        });

        //Creating second card...
        final JPanel cardWithProgress = new JPanel(new FlowLayout());
        final JProgressBar bar = new JProgressBar();
        bar.setIndeterminate(true); //Here we initialize the progress bar to indeterminate mode...
        cardWithProgress.add(bar);

        super.add(cardWithButton, nameForCardWithButton);
        super.add(cardWithProgress, nameForCardWithProgress);
        super.setPreferredSize(PANEL_SIZE);
    }

    private static void createAndDisplay() {
        JFrame frame = new JFrame("Frame");
        frame.getContentPane().add(new MyPanel());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    } 

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                createAndDisplay();
            }
        });
    }
}

Здесь вы можете увидеть создание и инициализацию MyPanel, который является контейнером с CardLayout. В него мы добавляем две карты. Один с кнопкой и один с индикатором выполнения.

person gthanop    schedule 17.01.2020
comment
Просто указываю, что Swing НЕ сохраняет потоки, и вы НЕ должны изменять состояние пользовательского интерфейса вне контекста EDT. - person MadProgrammer; 17.01.2020
comment
@MadProgrammer хорошо. Любой рекомендуемый способ улучшить мой ответ? Например, решит ли эту проблему SwingUtilities.invokeLater(...) для длительной задачи? - person gthanop; 17.01.2020
comment
@MadProgrammer, но опять же EDT будет заблокирован, пока выполняется длительная текущая задача. Я не знаю, что делать. Может быть, SwingWorker? - person gthanop; 17.01.2020
comment
Ну, пример использования Thread, я бы использовал SwingWorker, который предназначен для решения этой непосредственной проблемы. В вашем примере вам на самом деле вообще не нужен поток, JOptionPane будет (безопасно) блокировать EDT до тех пор, пока диалог не будет закрыт - person MadProgrammer; 17.01.2020
comment
Лично я уверен, что CardLayout будет лучшим выбором для этого, поскольку он предполагает определенный набор вариантов дизайна (т.е. вы не используете какой-либо другой механизм навигации, вы не пытаетесь разработать повторно используемое решение) - person MadProgrammer; 17.01.2020
comment
@MadProgrammer хорошо, я обновлюсь до SwingWorker. на самом деле вам вообще не нужен поток подождите, что? Я имею в виду, что я моделирую длительную текущую задачу с всплывающим окном JOptionPane... Где я могу прочитать о том, как разрабатывать многоразовые решения? - person gthanop; 17.01.2020
comment
Хорошо, во-первых, я понимаю, что это всего лишь пример, но вы могли бы сделать Thread.sleep или что-то в этом роде. Использование JOptionPane в этом случае, вероятно, было просто плохим выбором, особенно если учесть, что это нарушает возможности многопоточности Swing. Проблема с использованием CardLayout означает, что вы делаете предположение о том, как устроен и управляется пользовательский интерфейс, а это означает, что разработчику может потребоваться внести серьезные изменения для поддержки такого рабочего процесса, что не всегда возможно. возможно. - person MadProgrammer; 18.01.2020
comment
Любое решение должно, во-первых, стараться не делать никаких предположений о текущем дизайне, а во-вторых, не требовать от разработчика внесения серьезных изменений для его включения. Не всегда легкое дело. Об этом нигде нельзя прочитать, это то, чему нужно учиться. Попробуйте представить, что у вас очень большой и сложный пользовательский интерфейс со сложной иерархией. В этом случае у меня может возникнуть соблазн просто предоставить простую панель прогресса, которая будет обновлять индикатор выполнения на основе отзывов (например, с помощью ProgressListener) — это отделяет код, и ему не важно, как выполняется работа. - person MadProgrammer; 18.01.2020