Java Event Dispatch Thread blocked - java

I'm trying to change text of JLabel during program execution. I know that program execution blocks EDT so I use timer to do this work. But timer starts only after the cycle finishes it's execution although timer.start() is located before cycle in source code.
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
#SuppressWarnings("serial")
public class TestUpdate extends JFrame {
JLabel lab;
JButton btn;
public TestUpdate() {
super("Update test");
setDefaultCloseOperation(EXIT_ON_CLOSE);
btn = new JButton();
btn.setText("Start test");
btn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Timer tm = new Timer(1, new ActionListener() {
public void actionPerformed(ActionEvent e) {
lab.setText("Text is successfully changed");
lab.repaint();
}
});
tm.setRepeats(false);
tm.setInitialDelay(0);
tm.start();
long startTime=System.currentTimeMillis();
while (true) {
if (System.currentTimeMillis()-startTime >= 3000) break;
try {Thread.sleep(500);} catch (InterruptedException e1) {}
}
lab.setText("Three seconds elapsed");
btn.setEnabled(false);
}
});
add(btn,"South");
lab = new JLabel("Should be changed before 3 seconds elapsed");
lab.setHorizontalAlignment(SwingConstants.CENTER);
add(lab);
setSize(300, 200);
setLocationRelativeTo(null);
setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(
new Runnable() {
public void run() {new TestUpdate(); } });
}
}

Timer event is executed by the EDT, so first event will be executed when current task (your action listener code with while loop) will complete. That is why you observe such behaviour.

Related

Sleep causing frame with invisible content [duplicate]

This question already has answers here:
java thread.sleep puts swing ui to sleep too
(3 answers)
Closed 5 years ago.
I have a frame with menubar menu, when I'm selecting a menu item I want the program to show a message and then after few seconds(by sleep function) automatically close this message.
But instead of this I'm getting empty message (jdialog with invisible content):
If remove closing message, it content will appear after sleeping time.
What I need to change to get proper result?
I want to get this:
Full working code:
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.util.concurrent.TimeUnit;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.SwingUtilities;
public class MessageSleepTest extends JDialog {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable(){
public void run(){
new Frame("frame with menubar");
}
});
}
}
class Message extends JDialog {
public Message() {
this.setLayout(new GridLayout(0, 1));
this.add(new JLabel("Displaying this message for 3 seconds and then closing it...", JLabel.CENTER));
this.setAlwaysOnTop(true);
this.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
this.pack();
this.setLocationRelativeTo(null);
this.setVisible(true);
}
}
class Frame extends JFrame{
public Frame(String title){
super(title);
setJMenuBar(new MenuBar());
setPreferredSize(new Dimension(500, 300));
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
setLocationRelativeTo(null); // center the frame
setVisible(true);
}
}
class MenuBar extends JMenuBar implements ActionListener{
public static JMenuItem itmOpen;
public MenuBar() {
JMenu menuFile = new JMenu("File");
itmOpen = new JMenuItem("Open...");
itmOpen.addActionListener(this);
add(menuFile);
menuFile.add(itmOpen);
}
#Override
public void actionPerformed(ActionEvent e) {
JMenuItem source = (JMenuItem)e.getSource();
if(source == itmOpen){
JDialog message = new Message();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
WindowEvent windowClosing = new WindowEvent(message, WindowEvent.WINDOW_CLOSING);
message.dispatchEvent(windowClosing);
}
}
}
You have a threading problem:
The sleep is performed on AWT's Event Dispatch Thread, which does all the event handling stuff in AWT and Swing. Therefore, when you click on the menu item, it creates the objects for the Message, but gets stuck with the setVisible() method, which in general sends an event to the JDialog to lay out and show itself. This message gets on the queue of the EDT, and after your event handler (the actionPerformed method) finishes, it gets processed. However the sleep is between the two.
So try something like this:
#Override
public void actionPerformed(ActionEvent e) {
JMenuItem source = (JMenuItem) e.getSource();
if (source == itmOpen) {
final JDialog message = new Message();
new Thread( new Runnable() {
#Override
public void run() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException ex) {
// Do nothing with it
}
WindowEvent windowClosing = new WindowEvent(message, WindowEvent.WINDOW_CLOSING);
message.dispatchEvent(windowClosing);
}
}).start();
}
}
Another solution with using swing.Timer
#Override
public void actionPerformed(ActionEvent e) {
JMenuItem source = (JMenuItem)e.getSource();
if(source == itmOpen){
JDialog message = new Message();
Timer timer = new Timer(1000 * 3, new ActionListener() {
public void actionPerformed(ActionEvent e) {
WindowEvent windowClosing = new WindowEvent(message, WindowEvent.WINDOW_CLOSING);
message.dispatchEvent(windowClosing);
}
});
timer.setRepeats(false);
timer.start();
}
}

Use windowFocusListener on a countdown timer

I'm trying to make a countdown timer that only run when the window is on top of my screen.
I tried with this :
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
public class TimerVisible extends JFrame implements WindowFocusListener{
static TimerVisible frame = new TimerVisible("chrono",2,1,3);//I set a random time
JTextArea display;
private Counter counter;
public static void main(String[] args) {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.addComponentsToPane();
frame.pack();
frame.setVisible(true);
}
private void addComponentsToPane() {
display = new JTextArea();
display.setEditable(true);
JScrollPane scrollPane = new JScrollPane(display);
scrollPane.setPreferredSize(new Dimension(500, 450));
getContentPane().add(scrollPane, BorderLayout.CENTER);
addWindowFocusListener(this);
}
public TimerVisible(String name, int hours, int minutes, int secondes) {
super(name);
counter=new Counter(hours, minutes, secondes); //Counter is in secondes but is created with hours, minutes and seconds
}
public void windowGainedFocus(WindowEvent e) {
displayMessage("WindowFocusListener method called: windowGainFocus.");
try{
while(counter.getCounter()!=0){
Thread.sleep(1000);
displayMessage(counter.toString());
counter.decrement();
}
}
catch(InterruptedException exc){
System.exit(-1);
}
}
public void windowLostFocus(WindowEvent e) {
displayMessage("WindowFocusListener method called: windowLostFocus.");
}
private void displayMessage(String msg) {
display.append(msg+"\n");
System.out.println(msg);
}
}
When I run this program, it display the messages and the countdown on my terminal and not the window, but if I set the while loop under comment, it display correctly the message on the window. Is anybody got an idea why I got this difference?
Thank you
Your while loop is running on the Swing event thread, blocking it and preventing it from painting to the GUI or interacting with the user. Use a Swing Timer instead. Note that with a Swing Timer you won't have a while loop, but instead the actionPerformed will be called repeatedly until you stop the Timer.
Something like this could be close to working (code not tested)
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.Timer;
public class TimerVisible extends JFrame implements WindowFocusListener{
private static final int TIMER_DELAY = 1000;
static TimerVisible frame = new TimerVisible("chrono",2,1,3);//I set a random time
JTextArea display;
private Counter counter;
Timer timer = null;
public static void main(String[] args) {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.addComponentsToPane();
frame.pack();
frame.setVisible(true);
}
private void addComponentsToPane() {
display = new JTextArea();
display.setEditable(true);
JScrollPane scrollPane = new JScrollPane(display);
scrollPane.setPreferredSize(new Dimension(500, 450));
getContentPane().add(scrollPane, BorderLayout.CENTER);
addWindowFocusListener(this);
}
public TimerVisible(String name, int hours, int minutes, int secondes) {
super(name);
counter=new Counter(hours, minutes, secondes); //Counter is in secondes but is created with hours, minutes and seconds
}
public void windowGainedFocus(WindowEvent e) {
displayMessage("WindowFocusListener method called: windowGainFocus.");
if (timer != null && timer.isRunning()) {
return;
}
timer = new Timer(TIMER_DELAY, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (counter.getCounter() <= 0) {
timer.stop();
} else {
displayMessage(counter.toString());
counter.decrement();
}
}
});
timer.start();
}
public void windowLostFocus(WindowEvent e) {
displayMessage("WindowFocusListener method called: windowLostFocus.");
}
private void displayMessage(String msg) {
display.append(msg+"\n");
System.out.println(msg);
}
}

How do I call a method from an action listener without the JFrame freezing and waiting for the action listener to complete?

How do I set the text of a JTextArea while its JFrame is running, and refresh the JFrame to show the change, from another class?
I have a JFrame with a JTextArea which acts as a log, and the string it prints i update periodically with new activity from another class. My JFrame class (EnablePage) looks like this:
package bot;
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import javax.swing.JTextArea;
import javax.swing.JScrollPane;
import javax.swing.JButton;
import javax.swing.JLabel;
import java.awt.Font;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class EnablePage extends JFrame {
public static String enablePane;
private static JPanel contentPane;
public static JTextArea txtrHello = new JTextArea();
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
EnablePage frame = new EnablePage();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public EnablePage() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 594, 474);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
contentPane.setLayout(null);
JScrollPane scrollPane = new JScrollPane();
scrollPane.setToolTipText("");
scrollPane.setBounds(6, 89, 582, 357);
contentPane.add(scrollPane);
txtrHello.setEditable(false);
txtrHello.setText(enablePane);
txtrHello.setWrapStyleWord(true);
txtrHello.setLineWrap(true);
scrollPane.setViewportView(txtrHello);
JButton btnNewButton = new JButton("Enable");
btnNewButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
try {
navigator.navigator();
} catch (Exception e1) {
e1.printStackTrace();
}
}
});
btnNewButton.setBounds(59, 29, 117, 29);
contentPane.add(btnNewButton);
}
public static void update(String x) {
txtrHello.setText(enablePane+"\n"+x);
}
}
And from my navigator class I've been trying to use this line of code to update the JtextArea, while it manipulates a website. This code I didn't include, but replaced here with "Thread.sleep(100000);" to illustrate the problem:
package bot;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.swing.JOptionPane;
public class navigator {
public static DateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy(HH:mm:ss)");
public static void navigator() throws Exception {
Date date1 = new Date();
Thread.sleep(100000);
EnablePage.update("Bot enabled: "+dateFormat.format(date1));
}
}
However this is not updating the JFrame with the new text, because the EnablePage class is stuck waiting for the navigator() method to complete. What ends up happening is the Enable button stays blue because the actionlistener method is never broken from, because the nagivator() method never finished. What can I do to still call navigator() from the enable button but not have the EnablePage class freeze on this line?
Here's a simple example. A clock JTextField is updated from a Thread.
As you can see, there are no update, validate, or invalidate method calls.
Edited to add: The calls to the SwingUtilities invokeLater method are important, to ensure that the Swing components are created and updated on the Event Dispatch thread (EDT).
I also modified the Clock example to stop the Timer thread cleanly before disposing of the JFrame.
package com.ggl.testing;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
public class Clock implements Runnable {
private JFrame frame;
private JTextField clockDisplay;
private Timer timer;
#Override
public void run() {
frame = new JFrame("Clock");
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent event) {
exitProcedure();
}
});
JPanel panel = new JPanel();
clockDisplay = new JTextField(12);
clockDisplay.setEditable(false);
clockDisplay.setHorizontalAlignment(JTextField.CENTER);
panel.add(clockDisplay);
frame.add(panel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
timer = new Timer(this);
new Thread(timer).start();
}
public void exitProcedure() {
timer.setRunning(false);
frame.dispose();
System.exit(0);
}
public void setText(String text) {
clockDisplay.setText(text);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Clock());
}
public class Timer implements Runnable {
private volatile boolean running;
private Clock clock;
private SimpleDateFormat timeFormat;
public Timer(Clock clock) {
this.clock = clock;
this.running = true;
this.timeFormat = new SimpleDateFormat("h:mm:ss a");
}
#Override
public void run() {
while (running) {
displayTime();
sleep();
}
}
public void displayTime() {
Calendar calendar = Calendar.getInstance();
Date date = calendar.getTime();
final String s = timeFormat.format(date);
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
clock.setText(s);
}
});
}
public void sleep() {
try {
Thread.sleep(200L);
} catch (InterruptedException e) {
}
}
public synchronized void setRunning(boolean running) {
this.running = running;
}
}
}
JTextArea#append will allow you to append text to the JTextArea, both setText and append are bound methods, this means that they will trigger an update when they are called so you shouldn't need to do anything more. If it's not updating then it sounds like you have a reference issue.
You should consider providing a fully runnable example which demonstrates your problem. This will result in less confusion and better responses
You should avoid the use of static, especially when associated with UI components, as this really begins to give you trouble with what you are referencing and what's on the screen. static is NOT a cross communication mechanism for objects and shouldn't be used as such.
If you can, you should define some kind of interface which describes the actions which be executed on your log frame (ie addLog(String)), have your log frame implement this interface and then pass a reference of it to those classes that need it.
Alternatively, you could use a singleton pattern to allow your log window to be accessed from any where in your application, personally, I'd be tempted to devise a queue of some kind, where other classes pushed log events onto this (singleton) queue and you had your frame either poll it or use some kind of blocking queue mechanism to monitor for changes to the queue. This would require you to have a separate Thread (or SwingWorker) which monitored the queue in the background so you don't block the Event Dispatching Thread.
Avoid using null layouts, pixel perfect layouts are an illusion within modern ui design. There are too many factors which affect the individual size of components, none of which you can control. Swing was designed to work with layout managers at the core, discarding these will lead to no end of issues and problems that you will spend more and more time trying to rectify
Updated
Your runnable example works for me, more or less. Your reliance on static is worrying and Thread.sleep(100000); will block the Event Dispatching Thread, making your program look like it's hung (cause it has). The following is reworked version of your example, without null layouts, without static and using a Swing Timer instead of Thread.sleep. The great thing about this, is once you press the "Enable" button, the timer will update the text area every second...
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.Timer;
public class EnablePage extends JFrame {
private JTextArea txtrHello = new JTextArea(10, 20);
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
EnablePage frame = new EnablePage();
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public EnablePage() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new BorderLayout());
JScrollPane scrollPane = new JScrollPane(txtrHello);
scrollPane.setToolTipText("");
add(scrollPane);
txtrHello.setEditable(false);
txtrHello.setWrapStyleWord(true);
txtrHello.setLineWrap(true);
JButton btnNewButton = new JButton("Enable");
btnNewButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
try {
Navigator.navigator(EnablePage.this);
} catch (Exception e1) {
e1.printStackTrace();
}
}
});
add(btnNewButton, BorderLayout.NORTH);
}
public void update(String x) {
System.out.println("Update " + x + "\n");
txtrHello.append(x);
}
public static class Navigator {
public static DateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy(HH:mm:ss)");
public static void navigator(EnablePage page) throws Exception {
Timer timer = new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
Date date1 = new Date();
page.update("Bot enabled: " + dateFormat.format(date1));
}
});
timer.start();
}
}
}

Why does viewport changelistener get called multiple times

I've got a viewport, and I've attached a change listener to it. Whenever I scroll through my viewport, my change listener gets called about four times. I'm not sure how to avoid this; I only want the call to happen once?
There's no way around this, JViewport will fire several stateChanged events because it's providing notification about changes to a number of properties...
From the JavaDocs...
Adds a ChangeListener to the list that is notified each
time the view's size, position, or the viewport's extent size has
changed.
At this point, it's kind of hard to know what to suggest as we don't know what it is you are trying to achieve, however, if you have to use a ChangeListener, you could set up a coalescing mechanism. That is, rather then responding to each event, you basically wait until a long enough delay has occurred between events before responding to it...
For example...
public class DelayedChangeHandler implements ChangeListener {
private Timer timer;
private ChangeEvent last;
public DelayedChangeHandler() {
timer = new Timer(250, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
stableStateChanged();
}
});
timer.setRepeats(false);
}
#Override
public void stateChanged(ChangeEvent e) {
last = e;
timer.restart();
}
protected void stableStateChanged() {
System.out.println("Finally...");
}
}
Basically, this is a ChangeListener implementation that uses a non-repeating javax.swing.Timer with a short delay. Each time stateChanged is called, the timer is restart. Finally, when the timer is allowed to "tick", it calls stableStateChanged indicating that enough time has passed since the last event was raised.
This assumes that you don't so much care about what caused the event, only that the event occured...
A runnable example...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class TestViewport {
public static void main(String[] args) {
new TestViewport();
}
public TestViewport() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JPanel pane = new JPanel() {
#Override
public Dimension getPreferredSize() {
return new Dimension(1000, 1000);
}
};
JScrollPane sp = new JScrollPane(pane);
sp.getViewport().addChangeListener(new DelayedChangeHandler());
sp.getViewport().addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
System.out.println(evt.getPropertyName());
}
});
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(sp);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class DelayedChangeHandler implements ChangeListener {
private Timer timer;
private ChangeEvent last;
public DelayedChangeHandler() {
timer = new Timer(250, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
stableStateChanged();
}
});
timer.setRepeats(false);
}
#Override
public void stateChanged(ChangeEvent e) {
last = e;
timer.restart();
}
protected void stableStateChanged() {
System.out.println("Finally...");
}
}
}
You can try to use AdjustmentListener for gettign scroll event once, try next:
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.io.UnsupportedEncodingException;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
public class Example {
public static void main(String[] args) throws UnsupportedEncodingException {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JScrollPane pane = new JScrollPane(new JTextArea());
pane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
#Override
public void adjustmentValueChanged(AdjustmentEvent e) {
if(e.getValueIsAdjusting()){
return;
}
System.out.println("vertical scrolled");
System.out.println("bar value = " + e.getValue());
}
});
frame.setContentPane(pane);
frame.setSize(300, 200);
frame.setVisible(true);
}
}
Here is another example.

JProgressBar not updating

I have made a very simple code to show it here, i have a button that should show a JDialog to check the progress status, i am using the invoke late to go through EDT and my loop isn't in the run method, so why isn't my bar updating ?
here is the code
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class JBarEx extends JFrame {
private JTextField progStatus = new JTextField("Undefined");
private JButton dialogBtn = new JButton("Show Progression dialog");
final JDialog dlg = new JDialog((JFrame) null, "prog Title", false);
final JProgressBar dpb = new JProgressBar(0, 100);
public JBarEx() {
JPanel pan = new JPanel();
dialogBtn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
showProgress();
}
});
progStatus.setEditable(false);
pan.add(progStatus);
pan.add(dialogBtn);
setContentPane(pan);
this.setSize(200, 100);
setVisible(true);
}
public void showProgress() {
dlg.add(BorderLayout.CENTER, dpb);
dlg.add(BorderLayout.NORTH, new JLabel("prog message"));
dlg.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
dlg.setSize(300, 75);
dlg.setLocationRelativeTo(null);
dlg.setVisible(true);
for (int i = 0; i < 100; i++) {
final int ii = i;
try {
Thread.sleep(25);
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
updateBar(ii);
}
});
}
catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public void updateBar(int newValue) {
dpb.setValue(newValue);
}
public static void main(String[] args) {
JBarEx jbx = new JBarEx();
}
}
Your showProgress method is being executed within the context of the Event Dispatching Thread. The EDT is responsible for, amongst other things, processing paint requests. This means that so long as your for-loop is executing, the EDT can not process any new paint requests (or handle the invokeLater events either) as it is blocking the EDT.
While there are any number of possible ways to solve the problem, based on your code example, the simplest would be to use a SwingWorker.
It has the capacity to allow your to execute the long running task the a background thread (freeing up the EDT), but also allows you means for publishing updates (if required) so that they can be processed in the EDT and also provides handy progress notification.
For example...
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.EmptyBorder;
public class SwingWorkerProgress {
public static void main(String[] args) {
new SwingWorkerProgress();
}
public SwingWorkerProgress() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JProgressBar pbProgress;
private JButton start;
public TestPane() {
setBorder(new EmptyBorder(10, 10, 10, 10));
pbProgress = new JProgressBar();
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(4, 4, 4, 4);
gbc.gridx = 0;
gbc.gridy = 0;
add(pbProgress, gbc);
start = new JButton("Start");
gbc.gridy++;
add(start, gbc);
start.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
start.setEnabled(false);
ProgressWorker pw = new ProgressWorker();
pw.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
String name = evt.getPropertyName();
if (name.equals("progress")) {
int progress = (int) evt.getNewValue();
pbProgress.setValue(progress);
repaint();
} else if (name.equals("state")) {
SwingWorker.StateValue state = (SwingWorker.StateValue) evt.getNewValue();
switch (state) {
case DONE:
start.setEnabled(true);
break;
}
}
}
});
pw.execute();
}
});
}
}
public class ProgressWorker extends SwingWorker<Object, Object> {
#Override
protected Object doInBackground() throws Exception {
for (int i = 0; i < 100; i++) {
setProgress(i);
try {
Thread.sleep(25);
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
}
}
Check out Concurrency in Swing for more details
Even if you fix the loop as others have pointed out, you'd still block the event dispatch thread. The for loop is run in showProgress() which is called from an event listener. The updates are pushed to the event queue, but that does not get processed until the loop has completed.
Use a Swing Timer instead. Something like this:
Timer timer = new Timer(25, new ActionListener() {
private int position;
#Override
public void actionPerformed(ActionEvent e) {
position++;
if (position < lastPosition) {
updateBar(position);
} else {
((Timer) e.getSource).stop();
}
}
});
timer.start();
where lastPosition would be the state where you want the progress bar to stop.
Unrelated to that bug, but a bug still, you should not create swing components outside the event dispatch thread. It's best to do it right from the start:
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JBarEx jbx = new JBarEx();
}
});
}
for (int i = 0; i < 0; i++) {
You will never enter this code so will never call the updateBar(..) method
i needs to be greater than 0 in this case. If it is 1 then updateBar will be called once, if 2 then updateBar will be called twice etc
Also rather than doing
Thread.sleep(25);
take a look at java executors as these will help with your scheduling and remove the need for the sleep

Categories

Resources