I have a basic Swing UI with a single button marked "Play." When the button is pressed the label changes to "Pause". When the button is pressed now it changes to say "Resume."
On "Play" I am instantiating and executing a SwingWorker. What I would like is to be able to pause this thread (NOT cancel it) and resume it according to the button presses described above. However, I'd prefer not to resort to Thread.sleep() in doInBackground(). That seems a bit hackish. Is there any way for the thread running doInBackground to block?
Pause and Resume SwingWorker.doInBackground()
First of all you have to be sure the background task being performed can be paused, otherwise the question doesn't make sense. So let's say the task can be paused, then you might extend SwingWorker class and make your own pausable worker using a simple flag variable to control the background thread status: paused or not paused.
public abstract class PausableSwingWorker<K, V> extends SwingWorker<K, V> {
private volatile boolean isPaused;
public final void pause() {
if (!isPaused() && !isDone()) {
isPaused = true;
firePropertyChange("paused", false, true);
}
}
public final void resume() {
if (isPaused() && !isDone()) {
isPaused = false;
firePropertyChange("paused", true, false);
}
}
public final boolean isPaused() {
return isPaused;
}
}
Subclasses might check isPaused() status in order to efectively proceed with the task or not. For example:
PausableSwingWorker<Void, String> worker = new PausableSwingWorker<Void, String>() {
#Override
protected Void doInBackground() throws Exception {
while (!isCancelled()) {
if (!isPaused()) {
// proceed with background task
} else {
Thread.sleep(200); // Optional sleep to avoid check status continuously
}
}
return null;
}
};
You can also add a PropertyChangeListener to the worker and listen for paused property changes:
worker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ("paused".equals(evt.getPropertyName())) {
System.out.println("Old status: " + evt.getOldValue());
System.out.println("New status: " + evt.getNewValue());
}
}
});
Example (updated to make use of PropertyChangeListener)
Here is a complete example to play with. Please note that if worker is stopped then it cannot be paused nor resumed anymore.
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class Demo {
private void createAndShowGUI() {
final JTextArea textArea = new JTextArea(20, 50);
final PausableSwingWorker<Void, String> worker = new PausableSwingWorker<Void, String>() {
#Override
protected Void doInBackground() throws Exception {
while (!isCancelled()) {
if (!isPaused()) {
publish("Writing...");
} else {
Thread.sleep(200);
}
}
return null;
}
#Override
protected void process(List<String> chunks) {
String text = String.format("%s%n", chunks.get(chunks.size() - 1));
textArea.append(text);
}
};
worker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ("paused".equals(evt.getPropertyName())) {
String text = (Boolean)evt.getNewValue() ? "Paused..." : "Resumed...";
textArea.append(String.format("%s%n", text));
}
}
});
Action pause = new AbstractAction("Pause") {
#Override
public void actionPerformed(ActionEvent e) {
worker.pause();
}
};
Action resume = new AbstractAction("Resume") {
#Override
public void actionPerformed(ActionEvent e) {
worker.resume();
}
};
Action stop = new AbstractAction("Stop") {
#Override
public void actionPerformed(ActionEvent e) {
worker.cancel(true);
}
};
JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
buttonsPanel.add(new JButton(pause));
buttonsPanel.add(new JButton(resume));
buttonsPanel.add(new JButton(stop));
JPanel content = new JPanel(new BorderLayout(8, 8));
content.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
content.add(new JScrollPane(textArea), BorderLayout.CENTER);
content.add(buttonsPanel, BorderLayout.SOUTH);
JFrame frame = new JFrame("Demo");
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent e) {
if (!worker.isDone()) {
worker.cancel(true);
}
e.getWindow().dispose();
}
});
frame.add(content);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
worker.execute();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Demo().createAndShowGUI();
}
});
}
abstract class PausableSwingWorker<K, V> extends SwingWorker<K, V> {
private volatile boolean isPaused;
public final void pause() {
if (!isPaused() && !isDone()) {
isPaused = true;
firePropertyChange("paused", false, true);
}
}
public final void resume() {
if (isPaused() && !isDone()) {
isPaused = false;
firePropertyChange("paused", true, false);
}
}
public final boolean isPaused() {
return isPaused;
}
}
}
Related
Is it possible to wait for a method (say METHOD1) to finish, but if it is running longer than X secs, call another method until METHOD1 returns?
Some pseudocode:
method1();
startCountdown(1000); // time in millis
while (method1() still running) {
method2(); // shows a popup with spinner (Swing/AWT)
}
I guess, it must be done with concurrency, but I am not used to concurrent programming. So, I have no idea how to start.
The UI framework used is Swing/AWT.
So, the basic idea would be to use a combination of a SwingWorker and a Swing Timer.
The idea is if the Timer triggers before the SwingWorker is DONE, you execute some other workflow, otherwise you stop the Timer, for example...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
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.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
import javax.swing.Timer;
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 {
private JLabel label;
private JButton startButton;
boolean hasCompleted = false;
public TestPane() {
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
label = new JLabel("Waiting for you");
startButton = new JButton("Start");
add(label, gbc);
add(startButton, gbc);
startButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
startButton.setEnabled(false);
startWork();
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
protected void startWork() {
label.setText("Something wicked this way comes");
// You could build an isoloated workflow, which allowed you to pass
// three targets, the thing to be executed, the thing to be
// executed if time run over and the thing to be executed when
// the task completed (all via a single interface),
// but, you get the idea
Timer timer = new Timer(2000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (hasCompleted) {
return;
}
label.setText("Wickedness is a bit slow today");
}
});
timer.setRepeats(false);
SomeLongRunningOperation worker = new SomeLongRunningOperation();
worker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
switch (worker.getState()) {
case DONE:
hasCompleted = true;
timer.stop();
label.setText("All is done");
startButton.setEnabled(true);
break;
}
}
});
worker.execute();
timer.start();
}
}
public class SomeLongRunningOperation extends SwingWorker<Void, Void> {
#Override
protected Void doInBackground() throws Exception {
Thread.sleep(5000);
return null;
}
}
}
Play around with the timings to see what different effects you get.
Why use a SwingWorker? Because it has it's own state callbacks, which makes it easier to deal with
As I said in my comments, you could distill the workflow down into a re-usable concept, something like...
public class TimedTask<V> {
public static interface Task<V> {
public V execute() throws Exception;
}
public static interface TimedTaskListener<V> extends EventListener {
public void taskIsTakingLongThenExepected(TimedTask task);
public void taskDidComplete(TimedTask task, V value);
}
private Task<V> task;
private TimedTaskListener<V> listener;
private V value;
private int timeOut;
private Timer timer;
private SwingWorker<V, Void> worker;
private boolean hasCompleted = false;
public TimedTask(int timeOut, Task<V> task, TimedTaskListener<V> listener) {
this.task = task;
this.listener = listener;
this.timeOut = timeOut;
}
public V getValue() {
return value;
}
public int getTimeOut() {
return timeOut;
}
protected Task<V> getTask() {
return task;
}
protected TimedTaskListener<V> getListener() {
return listener;
}
public void execute() {
if (timer != null || worker != null) {
return;
}
hasCompleted = false;
worker = new SwingWorker<V, Void>() {
#Override
protected V doInBackground() throws Exception {
value = task.execute();
return value;
}
};
worker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
switch (worker.getState()) {
case DONE:
hasCompleted = true;
timer.stop();
getListener().taskDidComplete(TimedTask.this, value);
break;
}
}
});
timer = new Timer(getTimeOut(), new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (hasCompleted) {
return;
}
getListener().taskIsTakingLongThenExepected(TimedTask.this);
}
});
timer.setRepeats(false);
worker.execute();
timer.start();
}
}
And then you could replace the startWork method in the first example with something like...
protected void startWork() {
label.setText("Something wicked this way comes");
TimedTask.Task<Void> task = new TimedTask.Task<Void>() {
#Override
public Void execute() throws Exception {
Thread.sleep(5000);
return null;
}
};
TimedTask<Void> timedTask = new TimedTask(2000, task, new TimedTask.TimedTaskListener<Void>() {
#Override
public void taskIsTakingLongThenExepected(TimedTask task) {
label.setText("Wickedness is taking it's sweet time");
}
#Override
public void taskDidComplete(TimedTask task, Void value) {
label.setText("Wickedness has arrived");
startButton.setEnabled(true);
}
});
timedTask.execute();
}
While SwingWorker is the appropriate tool for the job, for simple tasks you can get away with a Thread for the off-edt long task and a swing Timer to update the GUI:
import java.awt.*;
import javax.swing.*;
import javax.swing.Timer;
public class Main{
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
frame.add(new TestPane());
frame.pack();
frame.setVisible(true);
}
}
class TestPane extends JPanel{
private static Dimension size = new Dimension(250, 100);
private final JLabel label;
private final JButton start;
private int counter;
private Timer timer;
public TestPane() {
setLayout(new BorderLayout(10, 10));
label = new JLabel("Click START to run long process", JLabel.CENTER);
add(label,BorderLayout.NORTH);
start = new JButton("START");
start.addActionListener(e-> start() );
add(start, BorderLayout.SOUTH);
}
private void start() {
start.setEnabled(false);
int processRunTime = 10;
int updateTime = 1; //if this value >= processRunTime update() is not invoked
counter = 1;
simulateLongProcessOf(processRunTime);
timer = new Timer(1000*updateTime, e->update(counter++));
label.setText("Long process started");
timer.start();
}
private void stop() {
label.setText("Long process ended");
timer.stop();
start.setEnabled(true);
}
#Override
public Dimension preferredSize() {
return size;
}
private void simulateLongProcessOf(int seconds){
Thread t1 = new Thread(()->{
try {
Thread.sleep(1000*seconds);
} catch (InterruptedException ex) {
ex.printStackTrace();
}finally {
SwingUtilities.invokeLater(()->stop());
}
});
t1.start();
}
private void update(int count){
label.setText("Update # "+ count+" : long process is running" );
}
}
I've struggled with this question before.
What I ended up doing was, creating a separate class that extends AsyncTask. Added an interface/listener to this class that returned my object. Right before I start my AsyncTask, I'll disable buttons and put up a loading spinner. Once the AsyncTask comes back, I'll do my processing and reenable the buttons and take down the loading spinner. Of coarse I'm doing a rest call in the example, but it can be applied to anything that takes awhile. The reason why this is a better option than a while loop is that it's won't be burning cycles checking conditions.
public class RestCall extends AsyncTask {
private Context mContext;
private static final String TAG = "RestCall";
private AsyncResponse mListener;
public RestCall(Context context, URL url, AsyncResponse listener) {
this.mListener = listener;
this.mContext = context;
this.url = url;
}
public interface AsyncResponse {
void processFinish(JSONArray results);
}
#Override
protected Object doInBackground(Object[] objects) {
Log.d(TAG, "doInBackground: Thread: " + Thread.currentThread().getName());
return getResultsInJSONArray(url);
}
private JSONArray getResultsInJSONArray(URL url) {
//Here is where you will be doing the bulk of the work
//Doing a rest call and
//Processing results to JSONArray
}
#Override
protected void onPostExecute(Object o) {
super.onPostExecute(o);
Log.d(TAG, "onPostExecute: Handing off Object");
mListener.processFinish((JSONArray) o);
}
Now in your original class you'll add the following to your class:
public class myClass
private restCall call;
Than create a listener from that interface you made. Then pass the results to a method.
restCall.AsyncResponse listener = results -> handleResults(results);
With the listener setup you can you can execute your AsyncTask.
//here is were you would throw up the loading bar.
call = new restCall(this, url, listener);
call.execute();
private void handleResults(JSONArray results){
//process what you need to
//take down loading bar
}
I created custom buttons for maximize, minimize and exit. And everything is working, with one exception, program does not remember in which state window was, before minimization. So my question is, is there a way, program to remember window state before minimization, and restore that state, and not to return only on NORMAL state?
My solution for:
maximize:
JButton btnO = new JButton("O");
btnO.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
if (frame.getExtendedState() == JFrame.MAXIMIZED_BOTH) {
frame.setExtendedState(JFrame.NORMAL);
} else {
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
}
}
});
and Minimize:
JButton btnMinimize = new JButton("-");
btnMinimize.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
frame.setExtendedState(JFrame.ICONIFIED);
}
});
You can take the ComponentListener approach and restore its state when the component (in your case, the frame), is resized. Some extra comments inside the code.
Take a look at this example:
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class FrameState extends JFrame {
private static final long serialVersionUID = 1965751967944243251L;
private int state = -1; // Variable to keep the last state.
public FrameState() {
super("Nothing :)");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JButton b = new JButton("-");
b.addActionListener(e -> {
state = getExtendedState(); //Store the state before "-" is pressed
setExtendedState(JFrame.ICONIFIED);
});
JButton o = new JButton("O");
o.addActionListener(e -> {
if (getExtendedState() == JFrame.MAXIMIZED_BOTH) {
setExtendedState(JFrame.NORMAL);
} else {
setExtendedState(JFrame.MAXIMIZED_BOTH);
}
});
getContentPane().setLayout(new FlowLayout());
getContentPane().add(o);
getContentPane().add(b);
setSize(new Dimension(300, 300));
setLocationRelativeTo(null);
addComponentListener(new ComponentListener() {
#Override
public void componentShown(ComponentEvent arg0) {
}
#Override
public void componentResized(ComponentEvent arg0) {
if (state != -1) {
setExtendedState(state); //Restore the state.
state = -1; //If it is not back to -1, window won't be resized properly by OS.
}
}
#Override
public void componentMoved(ComponentEvent arg0) {
}
#Override
public void componentHidden(ComponentEvent arg0) {
}
});
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
FrameState f = new FrameState();
f.setVisible(true);
});
}
}
You can use this code in JButton to maximize and restore JFrame.
In order to perform this function, you have import JFrame even though the JFrame has been extended in your java class.
if(getExtendedState()==NORMAL)
{
setExtendedState(MAXIMIZED_BOTH);
}
else
{
setExtendedState(NORMAL);
}
I need to convert a any image given to a byte array for encryption requirement. I'm using JProgressBar to monitor the conversion progress in case the chosen image is large:
File p= new File("C:\");
BufferedImage oImg = ImageIO.read(p);
ByteArrayOutputStream ba = new ByteArrayOutputStream();
ImageIO.write(oImg, "jpg", ba);
ba.flush();
ProgressBar pb = new ProgressBar();
Thread thread = new Thread(pb);
thread.join();
pb.fireTask(ba.toByteArray());
I defined a ProgressBar class that uses SwingWorker as follows:
public class ProgressBar extends JPanel implements Runnable {
private JProgressBar progressBar;
private Task task;
private byte[] imgByteArray;
public void run() {
createGUI();
}
// Create the GUI
private void createGUI() {
JFrame frame = new JFrame("Converting...");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JComponent newContentPane = new ProgressBar();
newContentPane.setOpaque(true);
frame.setContentPane(newContentPane);
JPanel panel = new JPanel();
progressBar = new JProgressBar(0, 100);
progressBar.setBounds(20, 22, 419, 20);
progressBar.setValue(0);
progressBar.setStringPainted(true);
panel.add(progressBar);
add(panel);
setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
frame.pack();
frame.setVisible(true);
}
/**
* Firing the Task
*/
public void fireTask(byte[] imgArray) {
System.arraycopy(imgArray, 0, imgByteArray, 0, imgByteArray.length);
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
task = new Task();
task.execute();
}
class Task extends SwingWorker<Void, Void> {
#Override
public Void doInBackground() {
for (int i=0; i<=imgByteArray.length; i++){
progressBar.setValue(i);
progressBar.repaint();
try{
Thread.sleep(50);
} catch (InterruptedException err){
}
}
return null;
}
public void done() {
Toolkit.getDefaultToolkit().beep();
setCursor(null); // turn off the wait cursor
}
}
}
Sadly, this error occurs!
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
at ciphers.ProgressBar.fireTask(ProgressBar.java:65)
at ciphers.Images.imageEncryption(Images.java:310)
at ciphers.Images.access$1(Images.java:286)
at ciphers.Images$2.actionPerformed(Images.java:184)
at javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:2018)
at javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2341)
at javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:4
02)
at javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:259)
at javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:252)
I don't know what is wrong with my code! I read SwingWorker and JProgressBar before writing this code but I feel I'm missing something! Can I get a hint that helps.
Thank you :)
You seem to not understand what SwingWorker does.
SwingWorker provides a means by which you can execute long running tasks outside the context of the Event Dispatching Thread. This means that your program won't appear to have become frozen. The benefit of using a SwingWorker (over using a simple Thread) is that it provides a number of easy to use methods to re-sync the updates back to the EDT, which is indented to stop you from breaking the single thread rules of Swing: No UI element should be created or modified on any thread other then the EDT.
Tak a closer look at Worker Threads and SwingWorker and javax.swing.SwingWorker<T,V> and Concurrency in Swing for more details
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutionException;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriter;
import javax.imageio.event.IIOReadProgressListener;
import javax.imageio.event.IIOWriteProgressListener;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class ConvertImage {
public static void main(String[] args) {
new ConvertImage();
}
public ConvertImage() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
TestPane tp = new TestPane();
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(tp);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
tp.convert(new File("/your/image/somewhere"));
}
});
}
public class TestPane extends JPanel implements ImageConverstionListener {
private JLabel label = new JLabel("Waiting...");
private JProgressBar pb = new JProgressBar();
public TestPane() {
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
add(label, gbc);
add(pb, gbc);
}
public void convert(File file) {
ConverterWorker worker = new ConverterWorker(file, this);
worker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(final PropertyChangeEvent evt) {
if ("progress".equalsIgnoreCase(evt.getPropertyName())) {
pb.setValue((int) evt.getNewValue());
}
}
});
worker.execute();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
public void failedToConvertImage(File source, Throwable cause) {
cause.printStackTrace();
JOptionPane.showMessageDialog(this, "<html>Failed to convert " + source + "<br>" + cause.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
}
#Override
public void imageConverted(File source, byte[] bytes) {
JOptionPane.showMessageDialog(this, "Converted image to " + bytes.length + " bytes", "Converted", JOptionPane.INFORMATION_MESSAGE);
}
#Override
public void setMessage(String msg) {
label.setText(msg);
}
}
public interface ImageConverstionListener {
public void failedToConvertImage(File source, Throwable cause);
public void imageConverted(File source, byte[] bytes);
public void setMessage(String msg);
}
public class ConverterWorker extends SwingWorker<ByteArrayOutputStream, String> {
private File source;
private ImageConverstionListener listener;
public ConverterWorker(File source, ImageConverstionListener listener) {
this.source = source;
this.listener = listener;
}
#Override
protected void process(List<String> chunks) {
listener.setMessage(chunks.get(chunks.size() - 1));
}
#Override
protected ByteArrayOutputStream doInBackground() throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
publish("Reading image...");
try (ImageInputStream iis = ImageIO.createImageInputStream(source)) {
Iterator<ImageReader> readers = ImageIO.getImageReaders(iis);
if (readers.hasNext()) {
ImageReader reader = readers.next();
reader.addIIOReadProgressListener(new IIOReadProgressListener() {
#Override
public void sequenceStarted(ImageReader source, int minIndex) {
}
#Override
public void sequenceComplete(ImageReader source) {
}
#Override
public void imageStarted(ImageReader source, int imageIndex) {
}
#Override
public void imageProgress(ImageReader source, float percentageDone) {
setProgress(Math.round(percentageDone));
}
#Override
public void imageComplete(ImageReader source) {
}
#Override
public void thumbnailStarted(ImageReader source, int imageIndex, int thumbnailIndex) {
}
#Override
public void thumbnailProgress(ImageReader source, float percentageDone) {
}
#Override
public void thumbnailComplete(ImageReader source) {
}
#Override
public void readAborted(ImageReader source) {
}
});
reader.setInput(iis);
try {
BufferedImage img = reader.read(0);
publish("Converting image...");
try (ImageOutputStream ios = ImageIO.createImageOutputStream(baos)) {
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("png");
if (writers.hasNext()) {
ImageWriter writer = writers.next();
writer.addIIOWriteProgressListener(new IIOWriteProgressListener() {
#Override
public void imageStarted(ImageWriter source, int imageIndex) {
}
#Override
public void imageProgress(ImageWriter source, float percentageDone) {
setProgress(Math.round(percentageDone));
}
#Override
public void imageComplete(ImageWriter source) {
}
#Override
public void thumbnailStarted(ImageWriter source, int imageIndex, int thumbnailIndex) {
}
#Override
public void thumbnailProgress(ImageWriter source, float percentageDone) {
}
#Override
public void thumbnailComplete(ImageWriter source) {
}
#Override
public void writeAborted(ImageWriter source) {
}
});
writer.setOutput(ios);
try {
writer.write(img);
} finally {
writer.removeAllIIOWriteProgressListeners();
}
}
}
} finally {
reader.removeAllIIOReadProgressListeners();
}
}
}
return baos;
}
#Override
protected void done() {
try {
ByteArrayOutputStream baos = get();
listener.imageConverted(source, baos.toByteArray());
} catch (InterruptedException | ExecutionException ex) {
listener.failedToConvertImage(source, ex);
}
}
}
}
Ok, so I made a simple program that adds the value to counter each time a button is clicked.
Now, I would like to add "Auto" button feature to increase the value of the counter when the "Auto" button is clicked. I'm having problems with it because it won't render each counter value on the screen, instead the value updates when the loop is done.. Here is my code:
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.concurrent.TimeUnit;
import javax.swing.JButton;
import javax.swing.JFrame;
public class Gui extends JFrame{
private static final long serialVersionUID = 1L;
private JButton uselesButton;
private JButton autoButton;
private FlowLayout layout;
private long counter = 0;
public Gui() {
super("Button");
layout = new FlowLayout(FlowLayout.CENTER);
this.setLayout(layout);
uselesButton = new JButton(String.format("Pressed %d times", counter));
add(uselesButton);
uselesButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
counter++;
uselesButton.setText(String.format("Pressed %d times", counter));
}
});
autoButton = new JButton("Auto");
add(autoButton);
autoButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for(long i =0; i < 99999999;i++) {
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e1) {
System.out.println("ERROR");
}
counter = i;
uselesButton.setText(String.format("Pressed %d times", counter));
}
}
});
}
}
Keep in mind that I'm a beginner... All help appreciated :)
Take a look at the tutorial about How to Use Swing Timer and then look at my solution:
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
public class Gui extends JFrame {
private static final long serialVersionUID = 1L;
private JButton uselesButton;
private JButton autoButton;
private FlowLayout layout;
private long counter = 0;
private javax.swing.Timer timer;
public Gui() {
super("Button");
layout = new FlowLayout(FlowLayout.CENTER);
setLayout(layout);
setDefaultCloseOperation(3);
setSize(300, 300);
setLocationRelativeTo(null);
//initialing swing timer
timer = new javax.swing.Timer(100, getButtonAction());
autoButton = new JButton("Auto");
add(autoButton);
autoButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (!timer.isRunning()) {
timer.start();
} else {
timer.stop();
}
}
});
}
private ActionListener getButtonAction() {
ActionListener action = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
autoButton.setText(String.format("Pressed %d times", ++counter));
if (counter > 1000) {
timer.stop();
}
}
};
return action;
}
public static void main(String... args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Gui().setVisible(true);
}
});
}
}
your code block the GUI thread (EDT) when enter inside this loop (GUI will hang, the button will not update until you finish), so you should add your code inside another worker thread:
autoButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
new Thread(new Runnable() {
#Override
public void run() {
for(long i =0; i < 99999999;i++) {
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e1) {
System.out.println("ERROR");
}
counter = i;
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
uselesButton.setText(String.format("Pressed %d times", counter));
}
});
}
}
}).start();
}
});
the problem here is that the system is in the loop, so it can't paint the changes.
in order to do that you need to open a new thread. the new thread will do the loop, and the main thread will repaint the form.
one more thing, you shouldn't do sleep on the main thread. you can use a timer that will tick every 10 millisecond instead of sleep(10)
here is an example
I want to have the user press a button to kick off a background thread.
While the thread is processing, I want two things to happen:
1) A WAIT_CURSOR should be displayed.
2) The application should not respond to mouse events.
As per the setCursor documentation "This cursor image is displayed when the contains method for this component returns true for the current cursor location, and this Component is visible, displayable, and enabled. ".
I want my application to be disabled while this background thread is processing.
Any ideas how to get the functionality I want?
import java.awt.Component;
import java.awt.Cursor;
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.SwingUtilities;
public class WaitCursor extends JFrame
{
private static final long serialVersionUID = 1L;
public WaitCursor()
{
setResizable(false);
setName(getClass().getSimpleName());
setTitle("My Frame");
setSize(300, 300);
getContentPane().add(new MyButtonPanel());
}
private class MyButtonPanel extends JPanel
{
private static final long serialVersionUID = 1L;
public MyButtonPanel()
{
JButton btnStart = new JButton("Start");
btnStart.addActionListener(new BtnStartActionListener());
add(btnStart);
}
private class BtnStartActionListener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
// Change to WAIT_CURSOR
Component root = SwingUtilities.getRoot((JButton) e.getSource());
JOptionPane.showMessageDialog(root, "Wait 10 seconds");
root.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
// TODO: Disabling the root component prevents the WAIT_CURSOR from being displayed
root.setEnabled(false);
new Thread(new TimeKiller(root)).start();
}
}
}
private class TimeKiller implements Runnable
{
Component _root;
public TimeKiller(Component root)
{
_root = root;
}
public void run()
{
try
{
Thread.sleep(10 * 1000);
}
catch (InterruptedException e)
{
// Ignore it
}
// Change back to DEFAULT CURSOR
JOptionPane.showMessageDialog(_root, "Done waiting");
_root.setCursor(Cursor.getDefaultCursor());
_root.setEnabled(true);
}
}
private static void createAndShowGUI()
{
// Create and set up the window.
WaitCursor frame = new WaitCursor();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
try
{
createAndShowGUI();
}
catch (Exception e)
{
e.printStackTrace();
System.exit(0);
}
}
});
}
}
One way to disable it is to use the glass pane to block mouse input.
For example:
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import javax.swing.*;
#SuppressWarnings("serial")
public class WaitCursor2 extends JPanel {
private static final int PREF_W = 400;
private static final int PREF_H = PREF_W;
private JComponent glassPane;
private JButton runBackgroundProcBtn;
private JTextArea textarea = new JTextArea(15, 30);
public WaitCursor2(JComponent glassPane) {
this.glassPane = glassPane;
glassPane.setFocusable(true);
glassPane.addMouseListener(new MouseAdapter() {
}); // so it will trap mouse events.
add(new JTextField(10));
add(runBackgroundProcBtn = new JButton(new AbstractAction(
"Run Background Process") {
#Override
public void actionPerformed(ActionEvent arg0) {
runBackgroundProcessAction();
}
}));
add(new JScrollPane(textarea));
}
private void runBackgroundProcessAction() {
disableSystem(true);
glassPane.setVisible(true);
new SwingWorker<Void, Void>() {
#Override
protected Void doInBackground() throws Exception {
long sleepTime = 5000;
Thread.sleep(sleepTime);
return null;
}
#Override
protected void done() {
disableSystem(false);
}
}.execute();
}
public void disableSystem(boolean disable) {
glassPane.setVisible(disable);
runBackgroundProcBtn.setEnabled(!disable);
if (disable) {
System.out.println("started");
glassPane.requestFocusInWindow(); // so can't add text to text components
glassPane.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
} else {
System.out.println("done");
glassPane.setCursor(Cursor.getDefaultCursor());
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
private static void createAndShowGui() {
JFrame frame = new JFrame("WaitCursor2");
WaitCursor2 mainPanel = new WaitCursor2((JComponent) frame.getGlassPane());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
The glass pane will trap mouse events if it set visible and given a MouseListener. It will lose t his ability if it is set invisible. Likewise it will pull the caret from text components if you make it focusable and give it focus.
added a field current_active and at method actionPerformed, do a simple check. Albeit it is not perfect but for simple app, i think this do the trick. A crude way of solving your two requirement. :-) Hope it works for you too.
import java.awt.Component;
import java.awt.Cursor;
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.SwingUtilities;
public class WaitCursor extends JFrame
{
private static boolean current_active = false;
public WaitCursor()
{
setResizable(false);
setName(getClass().getSimpleName());
setTitle("My Frame");
setSize(300, 300);
getContentPane().add(new MyButtonPanel());
}
private class MyButtonPanel extends JPanel
{
public MyButtonPanel()
{
JButton btnStart = new JButton("Start");
btnStart.addActionListener(new BtnStartActionListener());
add(btnStart);
}
private class BtnStartActionListener implements ActionListener
{
// change to wait_cursor
public void actionPerformed(ActionEvent e)
{
if (!current_active)
{
Component root = SwingUtilities.getRoot((JButton) e.getSource());
JOptionPane.showMessageDialog(root, "Wait 10 seconds");
root.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
// TODO: Disabling the root component prevents the WAIT_CURSOR from being displayed
//root.setEnabled(false);
current_active = true;
new Thread(new TimeKiller(root)).start();
}
}
}
}
private class TimeKiller implements Runnable
{
Component m_root;
public TimeKiller(Component p_root)
{
m_root = p_root;
}
#Override
public void run()
{
try
{
Thread.sleep(10 * 1000);
}
catch (InterruptedException e)
{
//Ignore it
}
// Change back to DEFAULT CURSOR
JOptionPane.showMessageDialog(m_root, "Done waiting");
m_root.setCursor(Cursor.getDefaultCursor());
current_active = false;
}
}
// create and setup the window.
public static void createAndShowGUI()
{
WaitCursor frame = new WaitCursor();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
try
{
createAndShowGUI();
}
catch (Exception e)
{
e.printStackTrace();
System.exit(0);
}
}
});
}
}