I have a swing application in which I need to enable a JMenu from a different thread. I'm doing this on OSX and am using the native screen menu via apple.laf.useScreenMenuBar. Since switching to java 7 the initially disabled menus now never become enabled and I can't figure out why. I've attached a small program which illustrates the problem. Clicking on the Fixed > Change menu item should enable the test menu after a brief pause (a dialog should open and close).
Using java6 it works fine. In java 7 the menu is not enabled. If I don't use the screen menu it works in 6 or 7, and if I use EventQueue.invokeAndWait it works in 6 or 7, but I don't think I should need to do this.
Is this a bug, or am I mis understanding how interactions between swing threads should work?
import java.awt.EventQueue;
import java.awt.event.*;
import javax.swing.*;
public class MainWindow extends JFrame implements ActionListener {
private JMenu testMenu;
public MainWindow () {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setTitle("Menu Enable Test");
JMenuBar bar = new JMenuBar();
JMenu fixedMenu = new JMenu("Fixed");
JMenuItem change = new JMenuItem("Change");
change.addActionListener(this);
fixedMenu.add(change);
bar.add(fixedMenu);
testMenu = new JMenu("Test");
testMenu.setEnabled(false);
JMenuItem seeMe = new JMenuItem("Can you see me?");
testMenu.add(seeMe);
bar.add(testMenu);
setJMenuBar(bar);
setSize(800,600);
setLocationRelativeTo(null);
setVisible(true);
}
public void makeVisible () {
testMenu.setEnabled(true);
}
public void actionPerformed(ActionEvent ae) {
new RemoteChanger(this);
}
private class RemoteChanger extends JDialog implements Runnable {
private MainWindow window;
public RemoteChanger (MainWindow window) {
super(window);
setSize(200,100);
setLocationRelativeTo(window);
this.window = window;
setVisible(true);
Thread t = new Thread(this);
t.start();
}
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
EventQueue.invokeLater(new Runnable() {
public void run() {
window.makeVisible();
}
});
setVisible(false);
}
}
public static void main (String [] args) {
System.setProperty("apple.laf.useScreenMenuBar", "true");
new MainWindow();
}
}
I think the source of the issue is your setVisible(false) being outside of the Swing thread. Moving it inside of the invokeLater() call seems more correct and gives you the expected behavior.
EventQueue.invokeLater(new Runnable() {
public void run() {
window.makeVisible();
setVisible(false);
}
});
Related
I'm making a program that has a popup menu with two buttons, one of which should close the popup menu, but I have no idea how to do that and googling hasn't gone too well.
I've tried using popup.hide() but then the menu wouldn't come back, despite doing so when I tried just moving the popup. It also required me to put a SuppressWarning in that case and it took a few seconds for it to close at all. Is there any better way of doing it?
I'm not sure what kind of code is relevant, but here's the relevant buttons and their roles in this(I skipped all the creating the GUI parts that didn't seem relevant, everything looks good and I know that the buttons are working):
package test;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
interface CustomButton {
JButton create();
void react(JPopupMenu popup, JFrame frame);
}
class ErrandsButton implements CustomButton {
private JButton errands = new JButton("Errands");
public JButton create() {
return errands;
}
public void react(JPopupMenu popup, JFrame frame) {
errands.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
popup.show(frame, 120, 65);
}
});
}
}
class Test {
static JFrame frame = new JFrame("List");
static CustomButton errands = new ErrandsButton();
static JButton cancelTask = new JButton("Cancel");
static JPopupMenu popup = new JPopupMenu();
static void cancelTask() {
cancelTask.addActionListener(new ActionListener() {
#SuppressWarnings("deprecation")
public void actionPerformed(ActionEvent e) {
popup.hide();
}
});
}
public static void main(String args[]) {
createInterface();
cancelTask();
errands.react(popup, frame);
}
static void createInterface() {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(500, 500);
JPanel popup1 = new JPanel();
JPanel button = new JPanel();
popup1.add(cancelTask);
popup.add(popup1);
frame.add(popup);
button.add(errands.create());
frame.getContentPane().add(BorderLayout.CENTER, button);
frame.setVisible(true);
}
}
Use popup.setVisible(true) and popup.setVisible(false).
frame.add(popup); is the problem. Do not add a JPopupMenu to a Container. Instead, use setComponentPopupMenu.
Alternatively, you could do the work yourself by adding a MouseListener whose mousePressed, mouseReleased and mouseClicked methods call isPopupTrigger and show. (It is vital that you do this in all three of those methods—different platforms have different conditions for showing popup menus.)
But really, using setComponentPopupMenu is easier.
I created swing application that do some operations in performedAction methode, but when i create a thread that shows the progressBar, the progressBar will not visible during the performedAction methode, but at the end the progressBar will be visible with a value of 100% directly
main class:
import javax.swing.*;
import javax.swing.plaf.ButtonUI;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class main {
public static void main(String[] args) {
JFrame win = new JFrame("Test");
FlowLayout layout = new FlowLayout();
Button b1 = new Button("Click ");
win.add(b1);
b1.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
window win = new window();
win.start();
try
{
Thread.sleep(2000);
}
catch(InterruptedException e5){}
}
});
win.setLayout(layout);
win.setSize(500, 300);
win.setLocationRelativeTo(null);
win.setVisible(true);
win.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
}
window class:
import javax.swing.*;
import java.awt.*;
public class window extends Thread{
public window(){
}
#Override
public void run() {
JFrame fen = new JFrame("New window");
FlowLayout layout = new FlowLayout();
fen.setLayout(layout);
Button b2 = new Button();
fen.setVisible(true);
fen.setSize(100, 100);
fen.setLocationRelativeTo(null);
fen.add(b2);
try
{
for(int i = 0; i <= 100; i++)
{
b2.setLabel("Button " + i);
Thread.sleep(10);
}
}
catch(InterruptedException e2){
}
}
}
Yes, it is safe to create a new Thread inside of an actionPerformed handler.
But, no thread other than the Event Dispatching Thread (EDT) must interact with the Swing components.
To create animations, or delays inside of Swing, you must use a javax.swing.Timer. Executing a Thread.sleep() on the EDT is never allowed, and won't do want you hope it will.
To return from a spawned thread to the EDT, you must use SwingUtilities.invokeLater() or SwingUtilities.invokeAndWait().
Using a SwingWorker is the preferred way to execute background tasks in Swing. It handles communication and publication of partial and final results from the background task to the EDT for displaying in the GUI components.
The following is a translation of your code to a working example, using a SwingWorker. Instead of AWT Button's, the Swing JButton is used. The program is created using invokeAndWait to ensure the main window construction occurs on the EDT. A lambda function is used, but you can replace this with new Runnable() { } inner class, if desired. The "Button 0" through "Button 100" progress reports are published from the SwingWorker background task, for processing in the EDT. Multiple results can be generated at by the background thread before the EDT has a chance to process them; here, we take only the last result to display in the button.
public class Main {
public static void main(String[] args) throws Exception {
SwingUtilities.invokeAndWait(() -> {
JFrame win = new JFrame("Test");
win.setLayout(new FlowLayout());
JButton b1 = new JButton("Click");
b1.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
DoWork work = new DoWork();
work.execute();
}
});
win.add(b1);
win.setSize(500, 300);
win.setLocationRelativeTo(null);
win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
win.setVisible(true);
});
}
}
class DoWork extends SwingWorker<Void, String> {
JFrame fen;
JButton b2;
DoWork() {
fen = new JFrame("New window");
fen.setLayout(new FlowLayout());
b2 = new JButton();
fen.add(b2);
fen.setSize(100, 100);
fen.setLocationRelativeTo(null);
fen.setVisible(true);
fen.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
}
#Override
protected Void doInBackground() throws Exception {
for (int i = 0; i <= 100; i++) {
publish("Button " + i);
Thread.sleep(10);
}
return null;
}
#Override
protected void process(List<String> data) {
String last = data.get(data.size() - 1);
b2.setText(last);
}
#Override
protected void done() {
fen.dispose();
}
}
There are still a number of improvements that can be made to this code. A JLabel instead of a JButton for displaying the results, a JDialog for the progress window, or perhaps better a ProgressMonitor. These are left as an exercise to the student.
Is there a way how to use a dialog in Swing which prohibits any gui activity under it but at the same time DOES NOT stop execution on the thread where it was set to visible?
Yes it can be done .
dlg.setModal(false);
or
dlg.setModalityType(Dialog.ModalityType.MODELESS);
where dlg is instance of your JDialog .
The basic idea of a JDialog IS to block the underlying thread until the user reacts to it. If you need to run something on the UI thread which should not be interrupted, consider using an additional worker thread for it. This way, the UI will be blocked by the JDialog, but the underlying process won't.
Yes, there is a little trick to make it work. We simply deactivate modality and manually disable the JFrame we want to make unclickable.
private final static JDialog dialog; static {
JOptionPane pane = new JOptionPane();
pane.setOptions(new Object[]{}); // Removes all buttons
dialog = pane.createDialog(frame, ""); // Create dialog with pane
dialog.setModal(false); // IMPORTANT! Now the thread isn't blocked
dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
}
Now you can use it like this:
dialog.setVisible(true);
frame.setEnabled(false);
// Logic
dialog.setVisible(false);
frame.setEnabled(true);
Technically, no. Like MadProgrammer wrote in a comment, you are never expected to access any Swing component off-EDT, JDialogs included, therefore the situation you hinted at in the question can never happen (there can never be any thread other than EDT that sets a dialog visible).
You could make it seem like it is, though. That's what SwingUtilities.invokeLater(Runnable) is for (doc).
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BorderFactory;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class BlockingDialogDemo extends JFrame {
private Timer timer;
private JDialog blocker;
public BlockingDialogDemo() {
setTitle("Blocking Dialog");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(300, 200);
setLocationRelativeTo(null);
blocker = new JDialog(this, true);
blocker.setLayout(new FlowLayout());
blocker.setUndecorated(true);
blocker.getRootPane().setBorder(BorderFactory.createMatteBorder(1, 1, 1, 1, Color.black));
blocker.add(new JLabel("I'm blocking EDT!"));
JProgressBar progress = new JProgressBar();
progress.setIndeterminate(true);
blocker.add(progress);
blocker.pack();
timer = new Timer(3000, new ActionListener() {
public void actionPerformed(ActionEvent e) {
doSomeWork();
}
});
timer.setRepeats(false);
timer.start();
}
private void doSomeWork() {
// this executes on-EDT
Runnable runnable = new Runnable() {
public void run() {
// this executes off-EDT - never ever access Swing components here
showBlocker();
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
System.out.println("Ummm.. I was sleeping here!");
} finally {
hideBlocker();
}
}
};
new Thread(runnable).start();
}
private void showBlocker() {
// this executes off-EDT
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// this executes on-EDT
blocker.setLocationRelativeTo(BlockingDialogDemo.this);
blocker.setVisible(true);
}
});
}
private void hideBlocker() {
// this executes off-EDT
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// this executes on-EDT
blocker.setVisible(false);
timer.restart();
}
});
}
public static void main(String[] args) {
// this is called off-EDT
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// this is called on-EDT
new BlockingDialogDemo().setVisible(true);
}
});
}
}
This works for me... sometimes:
public class NonBlockingModalDialogDemo extends JFrame{
JButton btnDoIt;
public NonBlockingModalDialogDemo() {
setTitle("NonBlockingModalDialog Demo");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(300,300);
setLayout(new FlowLayout());
btnDoIt = new JButton("Non-Blocking Notify");
btnDoIt.addActionListener( new ActionListener(){
#Override
public void actionPerformed(ActionEvent arg0) {
JDialog asyncDialog = createNonBlockingModalDialog("Please wait while we do some work", "Please wait");
doWork(50);
//Once your done, just dispose the dialog to allow access to GUI
asyncDialog.dispose();
}
});
this.add(btnDoIt);
}
private JDialog createNonBlockingModalDialog(String message, String title)
{
final JDialog dialog = new JDialog();
dialog.setLayout(new FlowLayout());
dialog.add(new JLabel(message));
dialog.setTitle(title);
dialog.setModal(true);
dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
dialog.setAlwaysOnTop(true);
dialog.pack();
Runnable dialogDisplayThread = new Runnable() {
public void run() {
dialog.setVisible(true);
}};
new Thread(dialogDisplayThread).start();
//Need to wait until dialog is fully visible and then paint it
//or else it doesn't show up right
while(!dialog.isVisible()){/*Busy wait*/}
dialog.paint(dialog.getGraphics());
return dialog;
}
private void doWork(int amount) {
for(int i = 0; i < amount; i++)
{
System.out.println("Doing work step number " + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {}
}
System.out.println("done");
}
public static void main(String[] args) {
new NonBlockingModalDialogDemo().setVisible(true);
}
}
I don't really like that it has a busy wait in it to check if the Dialog is visible yet, but so far I haven't found a way around it. At any rate, the busy wait should not take very long at all, so it really shouldn't matter.
Edit:
I did something very similar to this and for some reason, on some machines, sometimes, it just blocks forever without even showing the dialog.
I haven't figured out the root cause, but this leads me to conclude that all the people who say "never modify the GUI outside of the Event Dispatch Thread" may be on to something.
Perhaps rather than trying to continue the work you need to do on the EDT, maybe you should try something like this:
https://stackoverflow.com/a/4413563/2423283
Which uses SwingWorker to spawn a new thread and then allows you to update the GUI components when you are done.
i tried to make a simple button inside a frame but button icon is not displayed properly my question is why??
2.) also why cant we put
wingUtilities.invokeLater(new Runnable() {
public void run() {
new SwingDemo();
}
});S inside SwingDemo(),or public void actionPerformed(ActionEvent ae)
3.) which statement makes the edt thread??
please try to explain in simple terms otherwise more doubts will creep up.
import javax.swing.*;
import java.awt.event.*;
class SwingDemo implements ActionListener {
SwingDemo() {
JFrame jfrm = new JFrame("A Simple Swing Application");
jfrm.setSize(275, 100);
jfrm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JButton jlab = new JButton(" Button ");
jfrm.add(jlab);
jfrm.setVisible(true);
}
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable() { //2
public void run() {
new SwingDemo();
}
});
}
public void actionPerformed(ActionEvent ae) {
//jlab.setText("You selected " + ae.getActionCommand());
//showStatus("You selected " + ae.getActionCommand());
}
}
The call to SwingUtilities.invokeLater(...) it made at the initial stage of an application so that the creation of the UI occurs in the Event Dispatch Thread. It need to happen before the constructor or any subsequent method within the application is called. The thread created is known as an initial thread.
I have a GUI which is quite heavy to build/initialize on the platform on which it runs.. Therefore I want to update progress while it initializes..
I have a small undecorated JDialog containing a JLabel and a JProgressBar which I want to update at specific places during initialization, however, because the event dispatch thead (as per Swing rules) is used to build/initialize the GUI, the progress is of course not updated until the EDT is idle again (i.e. initialization is finished)..
The JProgressBar I have gotten to redraw using "paintImmediately", but I can't seem to make it work properly for the JLabel and the dialog itself.. Is there any simple recommended/proven method to accomplish this?
cheers...
EDIT: Adding an example of what it is I'm trying to do; greatly simplified, of course.
private JLabel progressLabel;
private JProgressBar progressBar;
public static int main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
showProgressDialog();
progressLabel.setText("construct 1");
constructSomeHeavyGUI();
progressLabel.setText("construct 2");
progressBar.setValue(33);
constructSomeMoreHeavyGUI();
progressLabel.setText("construct 3");
progressBar.setValue(67);
constructEvenMoreHeavyGUI();
progressLabel.setText("done");
progressBar.setValue(100);
hideProgressDialog();
showHeavyGUI();
}
});
}
the repaints caused by the calls to progressBar.setValue()/progressLabel.setText() above will of course get queued as long as the EDT is busy and result in a repaint after we are all done instead of updating along the way..
I would suggest that by using SwingWorker , then you can update the JProgressBar correctly on EDT and without any freeze or isuees with Concurency in Swing,
there is another option by using Runnable#thread, but then you have to wrapp all output to the GUI into invokeLater();
for example:
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
public class TestProgressBar {
private static void createAndShowUI() {
JFrame frame = new JFrame("TestProgressBar");
frame.getContentPane().add(new TestPBGui().getMainPanel());
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() {
#Override
public void run() {
createAndShowUI();
}
});
}
private TestProgressBar() {
}
}
class TestPBGui {
private JPanel mainPanel = new JPanel();
public TestPBGui() {
JButton yourAttempt = new JButton("WRONG attempt to show Progress Bar");
JButton myAttempt = new JButton("BETTER attempt to show Progress Bar");
yourAttempt.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
yourAttemptActionPerformed();
}
});
myAttempt.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
myAttemptActionPerformed();
}
});
mainPanel.add(yourAttempt);
mainPanel.add(myAttempt);
}
private void yourAttemptActionPerformed() {
Window thisWin = SwingUtilities.getWindowAncestor(mainPanel);
JDialog progressDialog = new JDialog(thisWin, "Uploading...");
JPanel contentPane = new JPanel();
contentPane.setPreferredSize(new Dimension(300, 100));
JProgressBar bar = new JProgressBar(0, 100);
bar.setIndeterminate(true);
contentPane.add(bar);
progressDialog.setContentPane(contentPane);
progressDialog.pack();
progressDialog.setLocationRelativeTo(null);
Task task = new Task("Your attempt");
task.execute();
progressDialog.setVisible(true);
while (!task.isDone()) {
}
progressDialog.dispose();
}
private void myAttemptActionPerformed() {
Window thisWin = SwingUtilities.getWindowAncestor(mainPanel);
final JDialog progressDialog = new JDialog(thisWin, "Uploading...");
JPanel contentPane = new JPanel();
contentPane.setPreferredSize(new Dimension(300, 100));
final JProgressBar bar = new JProgressBar(0, 100);
bar.setIndeterminate(true);
contentPane.add(bar);
progressDialog.setContentPane(contentPane);
progressDialog.pack();
progressDialog.setLocationRelativeTo(null);
final Task task = new Task("My attempt");
task.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equalsIgnoreCase("progress")) {
int progress = task.getProgress();
if (progress == 0) {
bar.setIndeterminate(true);
} else {
bar.setIndeterminate(false);
bar.setValue(progress);
progressDialog.dispose();
}
}
}
});
task.execute();
progressDialog.setVisible(true);
}
public JPanel getMainPanel() {
return mainPanel;
}
}
class Task extends SwingWorker<Void, Void> {
private static final long SLEEP_TIME = 4000;
private String text;
public Task(String text) {
this.text = text;
}
#Override
public Void doInBackground() {
setProgress(0);
try {
Thread.sleep(SLEEP_TIME);// imitate a long-running task
} catch (InterruptedException e) {
}
setProgress(100);
return null;
}
#Override
public void done() {
System.out.println(text + " is done");
Toolkit.getDefaultToolkit().beep();
}
}
EDIT:
1) you showed another issues, why do you create lots of Top-Level Containers on Fly/Runtime, create only required numbers of Containers and re-use that by removeAll()
2) here is probably what you needed, all those JProgressBars in the JTable are pretty accesible and configurable
3) this is your paintImmediately(), that really reason why not painting any of Progress to the JLabel but using JProgressBar#setValue(int);
instead,
It's possible that constructSome*HeavyGUI() really takes long enough to matter, but it's more likely that filling in the data model(s) is the problem. Instead, construct and show the empty GUI elements and launch one or more SwingWorker instances to marshal each element's data. There are related examples here and here.
Addendum: If the problem is instantiating components, and not loading data models, you can chain the calls to invokeLater(), as suggested in a comment below. If you're instantiating that many components, consider the flyweight pattern. JTable is a familiar example.
Move the long running code in a separate thread and use SwingUtilities.invokeAndWait or invokeLater to update GUI.
Either use SwingUtilities.invokeLater(...) as suggested by #StanislavL, or use SwingWorker.
See also:
Worker Threads and SwingWorker