Component waits for later code to execute before it draws - java

I have a class called WaitBox that I would like to pop up, show a progress bar, and then close when the progress bar reaches 100%. Right now, it pops up, but what's weird is that it waits until the code after it finished to draw the components (progress bar) in the WaitBox...
When I run the below code, the JDialog pops up, but it's just white, like how it looks before the components are drawn on it. It runs the coded below it (the nested for loops), and when that finished, it draws the components on the JDialog. Why is this happening, and how can I fix it?
WaitBox class:
public class WaitBox extends JDialog implements Runnable {
private static final long serialVersionUID = 1L;
private int width = 450;
private int height = 200;
public static int widthOfBar = 400;
private int heightOfBar = 50;
private JPanel container;
private JProgressBar progressBar;
private Thread thread;
private boolean running = false;
public WaitBox() {
initializeComponents();
add(container);
pack();
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
setTitle("Loading...");
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
setResizable(false);
setLocationRelativeTo(null);
setVisible(true);
}
private void initializeComponents() {
container = new JPanel();
container.setPreferredSize(new Dimension(width, height));
progressBar = new JProgressBar(0, widthOfBar);
progressBar.setPreferredSize(new Dimension(widthOfBar, heightOfBar));
progressBar.setStringPainted(true);
progressBar.setValue(0);
container.add(progressBar);
}
public void run() {
while (running) {
System.out.println(Fill.currentPos);
progressBar.setValue((int) Fill.currentPos);
if (progressBar.getValue() >= widthOfBar) running = false;
}
stop();
}
public synchronized void start() {
running = true;
thread = new Thread(this);
thread.start();
}
public synchronized void stop() {
running = false;
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
setCursor(null);
dispose();
}
}
How it's being called:
WaitBox waitBox = new WaitBox();
waitBox.start();
for (int yy = 0; yy < Constants.map_height; yy++) {
for (int xx = 0; xx < Constants.map_width; xx++) {
image.setRGB(xx * 32, yy * 32, 32, 32, pixels, 0, 32);
}
}

Your stop method is waiting for thread to end, which is blocking the Event Dispatching Thread.
See Concurrency in Swing for more details
Your run method is violating the single thread rules of Swing, updating the UI from outside the Event Dispatching Thread, remember, Swing is single threaded, but is also not thread safe.
You should consider using a SwingWorker instead, which has progress and PropertyChange support. See Worker Threads and SwingWorker for more details
Which is demonstrated:
Issues with SwingWorker and JProgressBar
java swingworker thread to update main Gui
Progress Bar Java
JProgressBar isn't progressing
SwingWorker in another SwingWorker's done method
SwingWorker In multithreaded Jframes
Progress Bar Java
among other places

Related

Progress Bar for a GUI application using Java

I am trying to write a progress bar for an application that is downloading information before the GUI runs. Because this is such a long process to download and organize the information, I am wanting to inform the user of the progress. I decided on using a progress bar late in the game and, as such, a majority of the code is written and I'm trying to incorporate the progress bar into the code without a drastic re-working of the code. The following is the code for the progress bar. Currently, the progress bar comes up AFTER everything runs and the GUI pops up.
static class PopulatingCardsWorker extends SwingWorker<Void, Integer> {
JProgressBar jpb;
int max;
JLabel label;
public PopulatingCardsWorker(JProgressBar jpb, int maximum, JLabel label) {
this.jpb = jpb;
this.max = maximum;
this.label = label;
}
#Override
protected void process(List<Integer> chunks) {
int i = chunks.get(chunks.size()-1);
jpb.setValue(i); // The last value in this array is all we care about.
System.out.println(i);
label.setText("Loading " + i + " of " + max);
}
#Override
protected Void doInBackground() throws Exception {
for(int i = 0; i < max; i++) {
Thread.sleep(10); // Illustrating long-running code.
publish(i);
}
return null;
}
#Override
protected void done() {
try {
get();
JOptionPane.showMessageDialog(jpb.getParent(), "Success", "Success", JOptionPane.INFORMATION_MESSAGE);
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}
}
private static void go(int max) {
JFrame frame = new JFrame();
JPanel panel = new JPanel();
JLabel label = new JLabel("Loading...");
JProgressBar jpb = new JProgressBar();
jpb.setIndeterminate(false);
jpb.setMaximum(max);
panel.add(label);
panel.add(jpb);
frame.add(panel);
frame.pack();
frame.setSize(200,90);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
new PopulatingCardsWorker(jpb, max, label).execute();
}
The program initially calls the GUI application and then runs the database acquisition code as shown below.
public static void main(String args[]) {
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new MagicTheGatheringUI().setVisible(true);
}
});
}
public MagicTheGatheringUI() {
int i, size;
colorRefinementFilter = "selected";
try {
my_list=new CardDatabase();
my_list.sortByName(my_list.all_cards);
my_list.populateSubArrays();
size = my_list.all_cards.size();
for(i = 0; i < size; i++)
{
namesList.addElement(my_list.all_cards.get(i).name);
}
} catch(Exception e) {
System.out.println(e);
System.exit(0);
}
initComponents();
}
The swing worker is created during the creation of "my_list=new CardDatabase();". In that class, I have the swing worker and the process the swing worker is supposed to monitor.
I currently call swing worker in a method called "populate_cards()" and I use the following code to create the swing worker. The swing worker is supposed to monitor what's going on in the populate_cards() method. All of the data in the swing worker methods are just temporary until I better understand how to make it work the way I want it to.
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
go(1000);
}
});
I believe the issue is that I'm calling the progress bar inside of the "invokeLater" method for the actual GUI.
I have looked at the following questions to try and solve my problem.
How to add a progress bar?
Can a progress bar be used in a class outside main?
and I have also looked at tutorials.
Your code is ok. The reason why the progressBar (and the gui itself) pops after the code ends is that by default in java, the code and the gui runs on the same Thread. Because the code has higher priority then the gui, all the code executes first and after that the gui updates.
You should do that in the "go" method:
new Thread(){
public void run(){
new PopulatingCardsWorker(jpb, max, label).execute();
}
} start();
see that article for more information about threads
Im answering from my phone, so sorry about the bad writing.
Code and GUi threads
and from Wikipedia
last one

Java game loop (painting) freezes my window

I'm changing "views" with cardLayout (this class has a JFrame variable). When a user clicks a new game button this happens:
public class Views extends JFrame implements ActionListener {
private JFrame frame;
private CardLayout cl;
private JPanel cards;
private Game game;
public void actionPerformed(ActionEvent e) {
String command = e.getActionCommand();
if (command.equals("New game")) {
cl.show(cards, "Game");
game.init();
this.revalidate();
this.repaint();
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
game.loop();
}
});
}
}
}
Game's loop method and heading of class:
public class Game extends JPanel implements KeyListener {
public void loop() {
while (player.isAlive()) {
try {
this.update();
this.repaint();
// first class JFrame variable
jframee.getFrame().repaint();
// first class JFrame variable
jframee.getFrame().revalidate();
Thread.sleep(17);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void update() {
System.out.println("updated");
}
}
I'm painting using paintComponent()
public void paintComponent(Graphics g) {
System.out.println("paint");
...
}
Actually it's not painting anything. When I do not call loop() method (so it paints it just once) all images are painted correctly. But when I call loop() method, just nothing is happening in the window. (Even close button on JFrame doesn't work.)
How to fix that? (When I was creating JFrame inside game class everything worked fine, but now I want to have more views so I need JFrame in other class.)
Thanks.
Precursor: The Event Dispatch Thread (EDT).
Swing is single-threaded. What does this mean?
All processing in a Swing program begins with an event. The EDT is a thread that processes these events in a loop along the following lines (but more complicated):
class EventDispatchThread extends Thread {
Queue<AWTEvent> queue = ...;
void postEvent(AWTEvent anEvent) {
queue.add(anEvent);
}
#Override
public void run() {
while (true) {
AWTEvent nextEvent = queue.poll();
if (nextEvent != null) {
processEvent(nextEvent);
}
}
}
void processEvent(AWTEvent theEvent) {
// calls e.g.
// ActionListener.actionPerformed,
// JComponent.paintComponent,
// Runnable.run,
// etc...
}
}
The dispatch thread is hidden from us through abstraction: we generally only write listener callbacks.
Clicking a button posts an event (in native code): when the event is processed, actionPerformed is called on the EDT.
Calling repaint posts an event: when the event is processed, paintComponent is called on the EDT.
Calling invokeLater posts an event: when the event is processed, run is called on the EDT.
Everything in Swing begins with an event.
Event tasks are processed in sequence, in the order they are posted.
The next event can only be processed when the current event task returns. This is why we cannot have an infinite loop on the EDT. actionPerformed (or run, as in your edit) never returns, so the calls to repaint post paint events but they are never processed and the program appears to freeze.
This is what it means to "block" the EDT.
There are basically two ways to do animation in a Swing program:
Use a Thread (or a SwingWorker).
The benefit to using a thread is that the processing is done off the EDT, so if there is intensive processing, the GUI can still update concurrently.
Use a javax.swing.Timer.
The benefit to using a timer is that the processing is done on the EDT, so there is no worry about synchronization, and it is safe to change the state of the GUI components.
Generally speaking, we should only use a thread in a Swing program if there's a reason to not use a timer.
To the user, there is no discernible difference between them.
Your call to revalidate indicates to me that you are modifying the state of the components in the loop (adding, removing, changing locations, etc.). This is not necessarily safe to do off the EDT. If you are modifying the state of the components, it is a compelling reason to use a timer, not a thread. Using a thread without proper synchronization can lead to subtle bugs that are difficult to diagnose. See Memory Consistency Errors.
In some cases, operations on a component are done under a tree lock (Swing makes sure they are thread-safe on their own), but in some cases they are not.
We can turn a loop of the following form:
while ( condition() ) {
body();
Thread.sleep( time );
}
in to a Timer of the following form:
new Timer(( time ), new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
if ( condition() ) {
body();
} else {
Timer self = (Timer) evt.getSource();
self.stop();
}
}
}).start();
Here is a simple example demonstrating animation both with a thread and a timer. The green bar moves cyclically across the black panel.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
class SwingAnimation implements Runnable{
public static void main(String[] args) {
SwingUtilities.invokeLater(new SwingAnimation());
}
JToggleButton play;
AnimationPanel animation;
#Override
public void run() {
JFrame frame = new JFrame("Simple Animation");
JPanel content = new JPanel(new BorderLayout());
play = new JToggleButton("Play");
content.add(play, BorderLayout.NORTH);
animation = new AnimationPanel(500, 50);
content.add(animation, BorderLayout.CENTER);
// 'true' to use a Thread
// 'false' to use a Timer
if (false) {
play.addActionListener(new ThreadAnimator());
} else {
play.addActionListener(new TimerAnimator());
}
frame.setContentPane(content);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
abstract class Animator implements ActionListener {
final int period = ( 1000 / 60 );
#Override
public void actionPerformed(ActionEvent ae) {
if (play.isSelected()) {
start();
} else {
stop();
}
}
abstract void start();
abstract void stop();
void animate() {
int workingPos = animation.barPosition;
++workingPos;
if (workingPos >= animation.getWidth()) {
workingPos = 0;
}
animation.barPosition = workingPos;
animation.repaint();
}
}
class ThreadAnimator extends Animator {
volatile boolean isRunning;
Runnable loop = new Runnable() {
#Override
public void run() {
try {
while (isRunning) {
animate();
Thread.sleep(period);
}
} catch (InterruptedException e) {
throw new AssertionError(e);
}
}
};
#Override
void start() {
isRunning = true;
new Thread(loop).start();
}
#Override
void stop() {
isRunning = false;
}
}
class TimerAnimator extends Animator {
Timer timer = new Timer(period, new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
animate();
}
});
#Override
void start() {
timer.start();
}
#Override
void stop() {
timer.stop();
}
}
static class AnimationPanel extends JPanel {
final int barWidth = 10;
volatile int barPosition;
AnimationPanel(int width, int height) {
setPreferredSize(new Dimension(width, height));
setBackground(Color.BLACK);
barPosition = ( width / 2 ) - ( barWidth / 2 );
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int width = getWidth();
int height = getHeight();
int currentPos = barPosition;
g.setColor(Color.GREEN);
g.fillRect(currentPos, 0, barWidth, height);
if ( (currentPos + barWidth) >= width ) {
g.fillRect(currentPos - width, 0, barWidth, height);
}
}
}
}
What does update do? You probably shouldnt call game.loop() on the EDT. You are running a loop on EDT, your repaint wont ever be invoked since repaint queues an event on EDT and it seems kind busy. Try moving game.loop() to another thread
new Thread(new Runnable() {
#override
public void run() {
game.loop();
}
}).start();
This way you wont block the EDT while the repaint still gets to be executed on the EDT.
Move game.loop() method invocation to something like:
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
game.loop()
}
});
Thanks.

Why my GUI won't update even tho repaint() is being called?

I am having some difficulties using swing workers, timers, and I am actually a little confused.
As far as my understanding goes, I have to put on a timer to set-up recurring tasks that have to be called by the EDT.
I'm trying to make a program that shows graphically a sorting alghoritm (like this : https://www.youtube.com/watch?v=kPRA0W1kECg )
I just don't understand why the GUI won't refresh. I am quite sure the repaint method is being called since I put a sysout showing me the ordered values and it seems to work , but the GUI just... doesn't change.
Here's my code:
public class MainWindow {
private JFrame frame;
JPanel panel;
public final static int JFRAME_WIDTH = 800;
public final static int JFRAME_HEIGHT = 600;
public final static int NELEM = 40;
ArrayList<Double> numbers;
ArrayList<myRectangle> drawables = new ArrayList<myRectangle>();
Lock lock = new ReentrantLock();
Condition waitme = lock.newCondition();
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
MainWindow window = new MainWindow();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public MainWindow() {
initialize();
}
private void initialize() {
frame = new JFrame();
frame.setBounds(100, 100, JFRAME_WIDTH + 20, JFRAME_HEIGHT + 40);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
panel = new myPanel();
frame.getContentPane().add(panel, BorderLayout.CENTER);
Timer timer = new Timer(500, new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
lock.lock();
try{
//Updating the gui
panel.repaint();
panel.revalidate();
//Giving the OK to the sorting alghoritm to proceed.
waitme.signal();
}catch(Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
});
timer.start();
SwingWorker<Integer, String> sw = new SwingWorker<Integer, String>(){
#Override
protected Integer doInBackground() throws Exception {
mapAndCreate();
bubbleSort();
return null;
}
};
sw.execute();
}
private void bubbleSort() throws InterruptedException{
for(int i=0; i < NELEM; i++){
for(int j=1; j < (NELEM-i); j++){
if(drawables.get(j-1).wid > drawables.get(j).wid){
//swap the elements!
myRectangle temp = drawables.get(j-1);
drawables.set(j-1, drawables.get(j));
drawables.set(j, temp);
lock.lock();
try{
//Wait for the GUI to update.
waitme.await();
}catch(Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
}
}
}
/***
* Function that maps values from 0 to 1 into the rectangle width.
*/
private void mapAndCreate() {
double max = 0;
numbers = new ArrayList<Double>(NELEM);
//Finding maximum.
for(int i = 0; i < NELEM; i++){
Double currElem = Math.random();
if(currElem > max) max = currElem;
numbers.add(currElem);
}
//Mapping process
int offset = 0;
for(int j = 0; j < NELEM; j++){
Integer mapped = (int) (( JFRAME_WIDTH * numbers.get(j) ) / max);
myRectangle rect = new myRectangle(offset , mapped);
drawables.add(rect);
offset += JFRAME_HEIGHT / NELEM;
}
}
private class myRectangle{
int myy , wid , colorR,colorG,colorB;
public myRectangle(int y , int wid){
this.myy = y;
this.wid = wid;
Random r = new Random();
colorR = r.nextInt(255);
colorG = r.nextInt(255);
colorB = r.nextInt(255);
}
}
private class myPanel extends JPanel{
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for(myRectangle rectan : drawables){
Graphics2D graphics2D = (Graphics2D)g;
System.out.println(rectan.wid);
Rectangle2D.Double rect = new Rectangle2D.Double(0,rectan.myy,rectan.wid,JFRAME_HEIGHT / NELEM);
graphics2D.setColor(new Color(rectan.colorR,rectan.colorG,rectan.colorB));
graphics2D.fill(rect);
}
System.out.println("====================================================================================================");
}
}
}
Most OSs (or rather the UI frameworks which they use) don't support concurrent access. Simply put, you can't render two strings of text at the same time.
That's why Swing runs all rendering operations in the UI thread. Calling rendering functions (like paint()) outside of the UI thread can cause all kinds of problems. So when you do it, Swing will just remember "I should repaint" and return (instead of doing any actual work). That way, Swing protects you but most people would prefer to get an error with a useful message.
A timer always also means that there is a thread somewhere which executes when the timer runs out. This is not the UI thread of Swing. So any paing operations there must be wrapped with EventQueue.invokeLater() or similar.
Another common bug is to hog the UI thread (so no rendering happens because you do complex calculations there). That's what the SwingWorker is for. Again, in most methods of the SwingWorker, calling methods which would render something is forbidden (-> use invokeLater()).
So my guess is that the UI thread waits for the lock and the lock simply isn't unlocked early or often enough. See this demo how to do a simple animation in Swing.
public class TimerBasedAnimation extends JPanel implements ActionListener {
public void paint(Graphics g) {
// setup
// do some first-run init stuff
// calculate the next frame
// render frame
}
public void actionPerformed(ActionEvent e) {
repaint();
}
public static void main(String[] args) {
JFrame frame = new JFrame("TimerBasedAnimation");
frame.add(new TimerBasedAnimation());
...
}
}
As you can see in the code doesn't lock. Instead, you just send "render now" events from actionPerformed to Swing. Some time later, Swing will call paint(). There is no telling (and no way to make sure or force Swing) when this will happen.
So good animation code will take the current time, calculate the animation state at that time and then render it. So it doesn't blindly step through N phases in M seconds. Instead, it adjusts for every frame to create the illusion that the animation is smooth when it really isn't.
Related:
Java: Safe Animations with Swing
How to Use Swing Timers

When i click on the progress bar close button it should ask the confirmation and stop the progress bar background task

I have added a progress bar in my java swing application. When i click on the progress bar close button it should ask for confirmation and stop the progress bar background task when the user select yes than terminate the progress bar and background task otherwise start the progress bar thread as it is and do the background task.
Can anyone guide me for this issue?
Any comments are appreciated.
Thanks,
Rahul
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.beans.*;
import java.util.Random;
public class ProgressBarDemo extends JPanel implements ActionListener,
PropertyChangeListener, Runnable {
/**
*
*/
private static final long serialVersionUID = 1L;
private JProgressBar progressBar;
private JButton startButton;
private JTextArea taskOutput;
private Task task=new Task();;
// private final Object lock = new Object();
class Task extends SwingWorker<Void, Void> implements Runnable {
/*
* Main task. Executed in background thread.
*/
#Override
public Void doInBackground() {
Random random = new Random();
int progress = 0;
// Initialize progress property.
setProgress(0);
while (progress < 100) {
// Sleep for up to one second.
try {
Thread.sleep(random.nextInt(1000));
} catch (InterruptedException ignore) {
}
// Make random progress.
progress += random.nextInt(10);
setProgress(Math.min(progress, 100));
}
return null;
}
/*
* Executed in event dispatching thread
*/
#Override
public void done() {
Toolkit.getDefaultToolkit().beep();
startButton.setEnabled(true);
setCursor(null); // turn off the wait cursor
taskOutput.append("Done!\n");
}
}
#Override
public void run() {
}
public ProgressBarDemo() {
super(new BorderLayout());
// Create the demo's UI.
startButton = new JButton("Start");
startButton.setActionCommand("start");
startButton.addActionListener(this);
progressBar = new JProgressBar(0, 100);
progressBar.setValue(0);
progressBar.setStringPainted(true);
taskOutput = new JTextArea(5, 20);
taskOutput.setMargin(new Insets(5, 5, 5, 5));
taskOutput.setEditable(false);
JPanel panel = new JPanel();
panel.add(startButton);
panel.add(progressBar);
add(panel, BorderLayout.PAGE_START);
add(new JScrollPane(taskOutput), BorderLayout.CENTER);
setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
}
/**
* Invoked when the user presses the start button.
*/
public void actionPerformed(ActionEvent evt) {
startButton.setEnabled(false);
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
// Instances of javax.swing.SwingWorker are not reusuable, so
// we create new instances as needed.
//task = new Task();
task.addPropertyChangeListener(this);
task.execute();
}
/**
* Invoked when task's progress property changes.
*/
public void propertyChange(PropertyChangeEvent evt) {
if ("progress" == evt.getPropertyName()) {
int progress = (Integer) evt.getNewValue();
progressBar.setValue(progress);
taskOutput.append(String.format("Completed %d%% of task.\n",
task.getProgress()));
}
}
/**
* Create the GUI and show it. As with all GUI code, this must run on the
* event-dispatching thread.
*/
private void createAndShowGUI() {
// Create and set up the window.
JFrame frame = new JFrame("ProgressBarDemo");
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
// Create and set up the content pane.
JComponent newContentPane = new ProgressBarDemo();
newContentPane.setOpaque(true); // content panes must be opaque
frame.setContentPane(newContentPane);
// Display the window.
frame.pack();
frame.setVisible(true);
frame.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent we) {
String ObjButtons[] = { "Yes", "No" };
try {
synchronized (task) {
System.out.println(task.getState());
task.wait();
System.out.println(task.getState());
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
int PromptResult = JOptionPane.showOptionDialog(null,
"Are you sure you want to exit?",
"Confirmation...!!!",
JOptionPane.DEFAULT_OPTION,
JOptionPane.WARNING_MESSAGE, null, ObjButtons,
ObjButtons[1]);
if (PromptResult == 0) {
System.exit(0);
} else {
synchronized (task) {
task.notify();
}
}
}
});
}
public static void main(String[] args) {
// Schedule a job for the event-dispatching thread:
// creating and showing this application's GUI.
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
new ProgressBarDemo().createAndShowGUI();
}
});
}
}
I wants that when i click on the close button of the progress bar it should prompt me for confirmation . When the confirmation dialog opens the swing worker thread which is doing work in background should be in wait(). when user selects the yes than it should close the progress bar or if user selects the no button than start the wait() thread of swing worker and start the execution of the background process again.
Currently when i have tried task.wait(); it all stops the main thread to wait.....!!!
Any suggestions?
I assume that you are using timer with progressbar as you didn't post your code, please post SSCCE.
Anyway, inside actionPerfomed method you must have a condition statement checking the selected option either Yes or No:
Before anything stop the timer till the user chooses it's choice, then:
if(Yes) : stop the timer and set the progress's value to Zero and
reset all the variables you used inside the timer's actionListener.
if(no): start the timer.
NOTE: I will not post the code until you show us if you tried anything.

Java Swing cancelling an Infinite loop

I've hit the infinite loop problem in Swing. Done some research and come across SwingWorker threads but not really sure how to implement them. I've knocked together a simple program that shows the problem. One button starts the infinite loop and I want the other button to stop it but of course due to the Swing single thread problem the other button has frozen. Code below and help appreciated:-
public class Model
{
private int counter;
private boolean go = true;
public void go()
{
counter = 0;
while(go)
{
counter++;
System.out.println(counter);
}
}
public int getCounter()
{
return counter;
}
public void setGo(boolean value)
{
this.go = value;
}
}
public class View extends JFrame
{
private JPanel topPanel, bottomPanel;
private JTextArea messageArea;
private JButton startButton, cancelButton;
private JLabel messageLabel;
private JScrollPane scrollPane;
public View()
{
setSize(250, 220);
setTitle("View");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
topPanel = new JPanel();
bottomPanel = new JPanel();
messageArea = new JTextArea(8, 20);
messageArea.setEditable(false);
scrollPane = new JScrollPane(messageArea);
messageLabel = new JLabel("Message Area");
topPanel.setLayout(new BorderLayout());
topPanel.add(messageLabel, "North");
topPanel.add(scrollPane, "South");
startButton = new JButton("START");
cancelButton = new JButton("CANCEL");
bottomPanel.setLayout(new GridLayout(1, 2));
bottomPanel.add(startButton);
bottomPanel.add(cancelButton);
Container cp = getContentPane();
cp.add(topPanel, BorderLayout.NORTH);
cp.add(bottomPanel, BorderLayout.SOUTH);
}
public JButton getStartButton()
{
return startButton;
}
public JButton getCancelButton()
{
return cancelButton;
}
public void setMessageArea(String message)
{
messageArea.append(message + "\n");
}
}
public class Controller implements ActionListener
{
private Model theModel;
private View theView;
public Controller(Model model, View view)
{
this.theModel = model;
this.theView = view;
view.getStartButton().addActionListener(this);
view.getCancelButton().addActionListener(this);
}
public void actionPerformed(ActionEvent ae)
{
Object buttonClicked = ae.getSource();
if(buttonClicked.equals(theView.getStartButton()))
{
theModel.go();
}
else if(buttonClicked.equals(theView.getCancelButton()))
{
theModel.setGo(false);
}
}
}
public class Main
{
public static void main(String[] args)
{
Model model = new Model();
View view = new View();
Controller controller = new Controller(model, view);
view.setVisible(true);
}
}
You can do it easily without implementing any timer, you just need to add two lines to your actionPerformed method:
public void actionPerformed(ActionEvent ae)
{
Object buttonClicked = ae.getSource();
if(buttonClicked.equals(theView.getStartButton()))
{
theModel.setGo(true); //make it continue if it's just stopped
Thread t = new Thread(new Runnable() { public void run() {theModel.go();}}); //This separate thread will start the new go...
t.start(); //...when you start the thread! go!
}
else if(buttonClicked.equals(theView.getCancelButton()))
{
theModel.setGo(false);
}
}
As your Model.go() is running in a separate thread, the Event Dispatch Thread is free to do its stuff, like drawing the button released again, instead of hanging with the button down.
There's a catch! however, because the thread running Model.go() will run wildly!, it's virtually called as many times per second as your system can.
If you plan to implement some animation or the like, then you will need to:
use a Timer,
or
add some sleep time to the new thread.
Example if you go with threads:
public void go()
{
counter = 0;
while(go)
{
counter++;
System.out.println(counter);
try {
Thread.sleep(1500); //Sleep for 1.5 seconds
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
As you can see I added Thread.sleep(1500) being 1500 the time in milliseconds (1.5 seconds). Thread.sleep can be interrupted for some reasons, so you must catch the InterruptedException.
It's not necessary to go deeper on handling correctly the InterruptedException in this particular case, but if you feel curious about it you can read this nice article.
You are blocking the Event Dispatch Thread (EDT). The thread is responsible to process painting and other UI related requests. Once EDT is blocked the UI will become frozen since it cannot process any events. For more details see The Event Dispatch Thread tutorial.
Consider using timers (How to Use Swing Timers), SwingWorker or an auxiliary background thread. Background thread can communicate with EDT using SwingUtilities.invokeLater(). This mechanism is already implemented in SwingWorker, so it may be easier to go with it. It depends on functionality that is required.
Use a javax.swing.Timer to do the go() work once, (with some optional delay), using start() and stop() in the event handling.
I decided to use a SwingWorker thread and below is the updated Controller class. It does what I need it to do but my question is, is it the correct way and is it clean code? Also, I've tried getting the output of the model.go() method into the view's textarea as per the commented out lines but not been succesful, anyone know how?
public class Controller implements ActionListener
{
private Model theModel;
private View theView;
private SwingWorker<Void, Void> worker;
public Controller(Model model, View view)
{
this.theModel = model;
this.theView = view;
view.getStartButton().addActionListener(this);
view.getCancelButton().addActionListener(this);
}
public void actionPerformed(ActionEvent ae)
{
Object buttonClicked = ae.getSource();
if(buttonClicked.equals(theView.getStartButton()))
{
theModel.setGo(true);
worker = new SwingWorker<Void, Void>()
{
#Override
protected Void doInBackground()
{
// theView.setMessageArea(theModel.getCounterToString());
return theModel.go();
}
#Override
protected void done()
{
// theView.setMessageArea(theModel.getCounterToString());
}
};
worker.execute();
}
else if(buttonClicked.equals(theView.getCancelButton()))
{
theModel.setGo(false);
}
}
}
public class Model
{
public Void go()
{
counter = 0;
while(go)
{
counter++;
System.out.println(counter);
}
return null;
}

Categories

Resources