【问题标题】:Display an overlay when a button is click and disappear again when the action has been performed using Swing单击按钮时显示叠加层,并在使用 Swing 执行操作时再次消失
【发布时间】:2020-05-04 04:00:27
【问题描述】:

我想在单击按钮时在我的 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();
        }
    });
}

【问题讨论】:

  • 这个定义我不清楚。是否要在 JFrame 上显示 JOptionPane 并在某个操作完成时将其删除?
  • 我想显示一个带有加载描述或微调器的不透明叠加层 - 然后只需在下面的答案中使用 JProgressBar 作为建议。 Swing 教程有一个工作示例。如果您想要更多控制并且想要半透明覆盖,那么您可以使用Glass Pane。请参阅:stackoverflow.com/questions/27822671/… 以了解使用简单 API 的实现。

标签: java swing jframe glasspane


【解决方案1】:

这方面的难点不是玻璃窗格,而是 UI 和 SwingWorker 之间的互操作性。

有很多方法可以做到这一点,这只是其中一种。

您应该先通读How to Use Root Panes,其中介绍了如何使用玻璃窗格和Worker Threads and SwingWorker,因为在您了解它之前,它会搞砸您。

这里需要注意的重要事项是:

  • Swing 是单线程的,你不应该阻塞 Event Dispatching Thread,这是 SwingWorker 的用途
  • Swing 不是线程安全的。这意味着您不应该直接或间接地从事件调度线程的上下文之外修改或更新 UI

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

    }

}

顺便说一句,我已验证 SwingWorker 使用的 PropertyChangeListener 已在 EDT 的上下文中更新

您还应该看看JLayer(正式名称为JXLayer

For example, example

这就像类固醇上的玻璃板

现在,如果你真的想做一些花哨的事情,你可以做类似this for example

【讨论】:

  • 非常感谢@MadProgrammer!这真的帮助了我,你提供的信息很棒!我一定会尝试你的一些想法。
【解决方案2】:

如果我理解正确,您需要显示某种正在进行的进度,直到按钮完成其工作。

如果是这样,您可以使用 CardLayout 和不确定的 JProgressBar

CardLayout 是一个LayoutManager(例如BorderLayoutFlowLayout 等...),它允许您定义卡片。每个卡片都是一个Component(例如Container和其他Components,例如JPanel)。任何时候都只能看到一张卡片。每个 card 都与一个字符串相关联,以识别它并能够将其选择为在其他卡片上可见。你可以阅读更多关于CardLayout in the corresponding Java tutorial的信息。

JProgressBar 是一个进度条,即JComponent,它向您显示正在进行的任务的进度。有两种模式:确定和不确定。在确定模式下,您可以指定问题的大小并通过代码自己推进进度条。在不确定模式下,指示旋钮不断旋转(这让用户知道有一个正在进行的任务正在进行中,并且程序不知道需要多长时间)。 JProgressBar 可以用作用户的简单视觉指示器。你可以阅读更多关于JProgressBar in the corresponding Java tutorial的信息。

因此,在您的情况下,您可以将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 的容器。在其中,我们添加了两张卡。一个带有按钮,一个带有进度条。

【讨论】:

  • 只是指出 Swing 不是线程保存,您不应该从 EDT 的上下文之外修改 UI 的状态
  • @MadProgrammer 好的。有什么推荐的方法来改善我的答案吗?例如,对于长期持续的任务,SwingUtilities.invokeLater(...) 会解决这个问题吗?
  • @MadProgrammer 但只要长时间持续的任务正在进行,EDT 就会再次被阻止。我不知道该怎么办。也许是一个 SwingWorker?.
  • 好的,首先,我很欣赏这只是一个“例子”——但你可以做Thread.sleep 或其他什么。在这种情况下使用JOptionPane 可能只是一个糟糕的选择——尤其是考虑到它违反了 Swing 线程功能。像这样使用 CardLayout 的问题意味着您正在假设 UI 的布局和管理方式,这意味着开发人员可能需要进行重大更改以支持这种工作流程,而这可能不会总是有可能的。
  • 任何“解决方案”首先应该尽量不要“假设”当前设计的任何内容,其次不要要求开发人员进行重大更改以合并它。并不总是一件容易的事。没有地方可以阅读有关此内容的信息,这是您需要学习的东西。试着想象你有一个非常大且复杂的 UI,具有复杂的层次结构。在这种情况下,我可能会想简单地提供一个简单的“进度窗格”,它会根据反馈更新进度条(例如通过ProgressListener)——这将代码解耦并且它不关心如何工作完成了
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-15
  • 2019-07-26
  • 1970-01-01
相关资源
最近更新 更多