单击按钮时显示叠加层,并在使用 Swing 执行操作后再次消失
Display an overlay when a button is click and disappear again when the action has been performed using 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();
}
});
}
如果我没理解错的话,您需要显示某种正在进行的进度,直到按钮完成其工作。
如果是这样,您可以使用 CardLayout
和不确定的 JProgressBar
。
CardLayout
是一个 LayoutManager
(例如 BorderLayout
、FlowLayout
等...),它允许您定义 cards.每个 卡片 是一个 Component
(例如 Container
和其他 Component
,例如 JPanel
)。任何时候都只能看到一张卡片。每个 card 都与一个字符串相关联以识别它并能够 select 它比其他卡片可见。您可以阅读有关 CardLayout
in the corresponding Java tutorial.
的更多信息
A JProgressBar
是进度条,即 JComponent
显示正在进行的任务的进度。有两种模式:确定和不确定。在确定模式下,您指定问题的大小,并通过代码自行推进进度条。在不确定模式下,指示器旋钮不断旋转(这让用户知道正在进行的任务正在进行中,并且程序不知道需要多长时间)。 JProgressBar
可以用作用户的简单视觉指示器。您可以阅读有关 JProgressBar
in the corresponding Java tutorial.
的更多信息
因此,在您的情况下,您可以将 CardLayout
与两张卡片一起使用,其中一张卡片包含 "Help" 按钮,另一张包含不确定的 JProgressBar
。当用户单击 "Help" 时,您会显示进度条卡片,当进度完成后,您会切换回 "Help" 按钮卡片。
现在,如果您在按钮 "Help" 的 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
的容器。在其中,我们添加了两张卡片。一个带按钮,一个带进度条。
这方面的难点不是玻璃面板,而是 UI 和 SwingWorker
之间的互操作性。
您可以通过多种方式执行此操作,这只是其中一种。
您应该从通读 How to Use Root Panes which goes into how to use glass panes and Worker Threads and SwingWorker 开始,因为在您了解它之前,它会把您搞得一团糟。
这里需要注意的重要事项是:
- Swing 是单线程的,你不应该阻塞事件调度线程,这就是
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
我想在单击按钮时在我的 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();
}
});
}
如果我没理解错的话,您需要显示某种正在进行的进度,直到按钮完成其工作。
如果是这样,您可以使用 CardLayout
和不确定的 JProgressBar
。
CardLayout
是一个 LayoutManager
(例如 BorderLayout
、FlowLayout
等...),它允许您定义 cards.每个 卡片 是一个 Component
(例如 Container
和其他 Component
,例如 JPanel
)。任何时候都只能看到一张卡片。每个 card 都与一个字符串相关联以识别它并能够 select 它比其他卡片可见。您可以阅读有关 CardLayout
in the corresponding Java tutorial.
A JProgressBar
是进度条,即 JComponent
显示正在进行的任务的进度。有两种模式:确定和不确定。在确定模式下,您指定问题的大小,并通过代码自行推进进度条。在不确定模式下,指示器旋钮不断旋转(这让用户知道正在进行的任务正在进行中,并且程序不知道需要多长时间)。 JProgressBar
可以用作用户的简单视觉指示器。您可以阅读有关 JProgressBar
in the corresponding Java tutorial.
因此,在您的情况下,您可以将 CardLayout
与两张卡片一起使用,其中一张卡片包含 "Help" 按钮,另一张包含不确定的 JProgressBar
。当用户单击 "Help" 时,您会显示进度条卡片,当进度完成后,您会切换回 "Help" 按钮卡片。
现在,如果您在按钮 "Help" 的 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
的容器。在其中,我们添加了两张卡片。一个带按钮,一个带进度条。
这方面的难点不是玻璃面板,而是 UI 和 SwingWorker
之间的互操作性。
您可以通过多种方式执行此操作,这只是其中一种。
您应该从通读 How to Use Root Panes which goes into how to use glass panes and Worker Threads and SwingWorker 开始,因为在您了解它之前,它会把您搞得一团糟。
这里需要注意的重要事项是:
- Swing 是单线程的,你不应该阻塞事件调度线程,这就是
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