how to force java swing gui to update on this snippet - java

What I want from the following minimum working example is to change its radio button's text twice in a small time interval after clicking on it. i.e., when I click on the button, I want to immediately change its text to "1" and after 3 seconds to change its text again to "2".
public class Example extends javax.swing.JFrame {
public Example() {
jRadioButton1 = new javax.swing.JRadioButton();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
jRadioButton1.addItemListener(new java.awt.event.ItemListener() {
public void itemStateChanged(java.awt.event.ItemEvent evt) {
jRadioButton1ItemStateChanged(evt);
}
});
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup().addGap(137, 137, 137)
.addComponent(jRadioButton1).addContainerGap(242, Short.MAX_VALUE))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup().addGap(126, 126, 126)
.addComponent(jRadioButton1).addContainerGap(153, Short.MAX_VALUE))
);
pack();
}
private void jRadioButton1ItemStateChanged(java.awt.event.ItemEvent evt) {
jRadioButton1.setText("1");
//repaint();
try { Thread.sleep(3000); }
catch (InterruptedException e) {}
jRadioButton1.setText("2");
}
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new Example().setVisible(true);
}
});
}
private javax.swing.JRadioButton jRadioButton1;
}
The above obviously doesn't work.
I know that I should use repaint() or SwingUtilities.invokeLater() or maybe a new thread in some way, but I didn't manage yet to achieve the desired result with any combination of the them.
What should I do?
Thanks in advance.

Sleeping in the event dispatch thread blocks it and prevents drawing from happening as you have found out. It also makes the rest of the UI unresponsive for the time, so it's in the list of things you should never do.
The standard way to run delayed, or repeated action in swing applications is using a swing Timer. It runs the action properly in the event dispatch thread, so that you also don't need to pay special attention to thread safety. Using a Timer your button action becomes:
private void jRadioButton1ItemStateChanged(java.awt.event.ItemEvent evt) {
jRadioButton1.setText("1");
Timer timer = new Timer(3000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
jRadioButton1.setText("2");
}
});
timer.setRepeats(false);
timer.start();
}

First of all, change this
private javax.swing.JRadioButton jRadioButton1;
to
public javax.swing.JRadioButton jRadioButton1;
Then. You may write this class
public class Updater extends Thread {
private Example e ;
public Updater(Example e) {
this.e = e;
}
public void run(){
try {
Thread.sleep(3000);
e.jRadioButton1.setText("2");
} catch (InterruptedException ex){
}
}
}
Now here in this method.
private void jRadioButton1ItemStateChanged(java.awt.event.ItemEvent evt) {
jRadioButton1.setText("1");
new Updater(this).start();
}

Related

How do I set a JTextPane out of another Thread?

I am learning about multithreading right now.
I have a "main" class in which I build a JFrame with a Textfield and a Start and a Stop Button in it.
I also have another class/Thread from which I want to print the current time into my Textfield when I click the Start Button of my Frame. Everything works but the text doesn't change in my Textfield as I start the Thread even though Eclipse says my code is alright.
What am I doing wrong?
Class 1:
public class Uhr extends JFrame {
private JPanel contentPane;
public JTextPane tpZeit;
Thread t;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
Uhr frame = new Uhr();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the frame.
*/
public Uhr() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 450, 300);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
contentPane.setLayout(null);
tpZeit = new JTextPane();
tpZeit.setText("test");
tpZeit.setBounds(43, 50, 212, 43);
contentPane.add(tpZeit);
JButton btnstart = new JButton("GO");
btnstart.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
tpZeit.setText("started");
if (t == null) {
t = new Thread(new Uhrsteuerung());
}
if(!t.isAlive()) {
t = new Thread(new Uhrsteuerung());
t.start();
}
}
});
btnstart.setBounds(10, 227, 89, 23);
contentPane.add(btnstart);
JButton btnstop = new JButton("Stop");
btnstop.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
t.interrupt();
}
});
btnstop.setBounds(248, 227, 89, 23);
contentPane.add(btnstop);
}
}
Class with additional Thread:
public class Uhrsteuerung extends Uhr implements Runnable {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
String time;
boolean x ;
#Override
public synchronized void run() {
// TODO Auto-generated method stub
x = true;
while (x) {
try {
time = sdf.format(System.currentTimeMillis());
System.out.println(time);
tpZeit.setText(time);
repaint();
Thread.sleep(1000);
} catch (InterruptedException ex) {
x = false;
}
}
}
}
All code for Java's Swing classes must be executed on the Event Dispatch Thread (EDT). Swing provides some special classes for helping you do this.
To execute code off the EDT, use a SwingWorker class. SwingWorker will execute a task in the background (not using the EDT) and then return a result properly synchronized that executes on the EDT. https://docs.oracle.com/en/java/javase/16/docs/api/java.desktop/javax/swing/SwingWorker.html
Its application is pretty simple. Define your own class that extends SwingWorker.
class PrimeNumbersTask extends SwingWorker<List<Integer>, Integer> {
// ...
If you need to update a component, it's good to pass in the component so that you can refer to it later.
class PrimeNumbersTask extends SwingWorker<List<Integer>, Integer> {
private JTextArea textArea;
public PrimeNumbersTask( JTextArea textArea ) {
this.textArea = textArea;
}
// ...
Do your work in the method doInBackground() by overriding it. Don't touch the Swing classes in this method. Use the publish() method to send smaller chunks of data to the EDT if you want to provide progressive results instead of waiting for the whole task to complete.
#Override
public List<Integer> doInBackground() {
while (!done && ! isCancelled()) {
// do stuff
publish(number);
setProgress(100 * numbers.size() / numbersToFind);
}
}
return numbers;
}
Finally override the method process() to update your Swing classes.
#Override
protected void process(List<Integer> chunks) {
for (int number : chunks) {
textArea.append(number + "\n");
}
}
From the EDT, you can call execute() to start your background task.
JTextArea textArea = new JTextArea();
// manipulate and set up GUI...
PrimeNumbersTask task = new PrimeNumbersTask(textArea);
task.execute();
There's a lot more docs on SwingWorker if you Google for it, that's the basics of how to use it.

Is it okay to use thread.sleep() when coding a bot?

I'm trying to code a primitive spammer. Is it okay to use thread.sleep() when coding a bot?
I'm a novice programmer. If there is any place in my code to fix it, I would appreciate it if you let me know. I may have used JComponents improperly. If it catches your eye, you can specify. Thank you.
Note: "It looks like your post is mostly code; please add some more details." I'm writing this note because I can't find any more details to add. Sorry
public class Spammer extends JFrame implements Runnable{
private boolean running = false;
private JButton jButton1;
private JLabel jLabel1, jLabel2;
private JScrollPane jScrollPane1;
private JSpinner jSpinner1;
private JTextArea jTextArea1;
public Spammer() {
setLayout(null);
jLabel1 = new JLabel("Text: ");
jTextArea1 = new JTextArea(10,28);
jLabel2 = new JLabel("Interval: ");
jSpinner1 = new JSpinner();
jScrollPane1 = new JScrollPane();
jButton1 = new JButton("Spam");
jButton1.setSize(350, 60);
jButton1.setLocation(100, 220);
jLabel1.setSize(50, 150);
jLabel1.setLocation(15, 10);
jLabel1.setFont(new Font("Verdana" , Font.BOLD , 14));
jTextArea1.setSize(350, 150);
jTextArea1.setLocation(100, 10);
jLabel2.setSize(80, 25);
jLabel2.setLocation(15, 180);
jLabel2.setFont(new Font("Verdana" , Font.BOLD , 12));
jSpinner1.setSize(350, 25);
jSpinner1.setLocation(100, 180);
getContentPane().add(jLabel1);
getContentPane().add(jTextArea1);
getContentPane().add(jLabel2);
getContentPane().add(jSpinner1);
getContentPane().add(jScrollPane1);
getContentPane().add(jButton1);
setTitle("Spammer by Me");
setLocationRelativeTo(null);
setResizable(false);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setPreferredSize(new Dimension(500, 340));
pack();
jButton1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
jButton1ActionPerformed();
}
} );
}
private void jButton1ActionPerformed() {
if(!running) {
jTextArea1.setEnabled(false);
jSpinner1.setEnabled(false);
jButton1.setText("Spamming in 3 seconds...");
jButton1.setEnabled(false);
running = true;
new Thread(this).start();
}else {
jTextArea1.setEnabled(true);
jSpinner1.setEnabled(true);
jButton1.setText("Spam");
running = false;
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new Spammer().setVisible(true);
}
});
}
public void run() {
Robot robot = null;
try {
robot = new Robot();
} catch (AWTException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
int[] keys = new int[jTextArea1.getText().length()];
if((int) jSpinner1.getValue() < 0) {
jSpinner1.setValue((int) 0);
}
int interval = (int) jSpinner1.getValue();
for(int i = 0 ; i < keys.length; i++) {
keys[i] = KeyEvent.getExtendedKeyCodeForChar(jTextArea1.getText().charAt(i));
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
jButton1.setEnabled(true);
jButton1.setText("Stop");
while(running) {
for(int i = 0 ; i < keys.length; i++) {
robot.keyPress(keys[i]);
robot.keyRelease(keys[i]);
}
try {
Thread.sleep(interval);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
The key principle at work here is primarily the 'EDT' - the Event Dispatch Thread. This is a thread that does GUI stuff - if for example you drag the titlebar of a completely different app's window across the screen, and this moves that app's window over yours, the EDT is hard at work redrawing everything. The EDT is the thread that sees you press your mouse down on a button, and will render the button in the 'pressed in' view.
The EDT is the only thread from which you can do GUI stuff, whether it is to retrieve info, such as getText(), or whether it is to change things, such as updating the text of a label or whatnot.
The EDT is also the thread you're in when your code runs that you registered as a handler for events, such as the code that responds to a button click or whatnot.
You therefore must not sleep on the EDT (Because then your app looks non-responsive; the thread that responds to button clicks or repaints what needs repainting is not actively running), but you can only fetch GUI data / set GUI stuff from the EDT.
The rules:
Do not interact with any GUI elements unless you are in the EDT
Never sleep in the EDT
Your code is broken, not because you sleep (that's fine - that run() method is not in the EDT), but because you do GUI stuff from this non-EDT thread.
You need to do a careful dance here: You want to sleep (not allowed on the EDT), but interact with GUI elements, such as the interval box, to know how long to sleep, which can only be done on the EDT.
To do this, you can 'send' code to run in the EDT via SwingWorkers, or simply via:
SwingUtilities.invokeAndWait(() -> {
// code that will run in the EDT goes here
});
You can't set any variables from within this code, but you can use AtomicReference and friends to create objects you can change. So, instead of:
int[] keys = new int[jTextArea1.getText().length()];
if (jSpinner1.getValue() < 0) {
jSpinner1.setValue(0);
}
int interval = (int) jSpinner1.getValue();
which is doing GUI stuff, do:
AtomicInteger interval = new AtomicInteger();
SwingUtilities.invokeAndWait(() -> {
int[] keys = new int[jTextArea1.getText().length()];
if (jSpinner1.getValue() < 0) {
jSpinner1.setValue(0);
}
interval.set((int) jSpinner1.getValue());
};

Java Jframe is displaying but the contents (Panel) is not showing properly

I've developed a small application to track my daily work activities, this tool contains two classes:
Executor
UIProgress
My objective was to create a ProgressBar which updates the status of the execution, the logic used is given below,
From executeTask () defined in Executor class, I'have created
UIProgress object.
The UIProgress class extends JFrame. Create a panel which contains
a label with one image and the progress bar. I've defined a
method updateProgress in this class which set the value for
progressbar.
In executeTask () method (in Executor class), created UIProgress
object. In executeTask (), am calling different functions to execute
the tasks and after each function am invoking
UIProgress.updateProgress () method with integer vale to update progressbar.
But while running the program, the JFrame UI is transparent means it didn't show the contents instead contains the background data in the frame, Only progressbar is showing and it updating but the entire JFrame is transparent. We are not seeing the panel (image) and it shows as a transparent mode.
Note: setVisible (true) called after added panel into JFrame.
Executor.java
public void executeTask ()
{
/* Create and display the form */
progress = new UIProgress();
progress.prepareGUI();
progress.updateProgress (10);
getWorkedItems ();
//progress.pack ();
progress.updateProgress (30);
getWorkedTickets ();
progress.updateProgress (50);
getRemainTickets ();
progress.updateProgress (70);
jf.postTriagedTicketDetailsDaily();
...
}
UIProgress.java
public class UIProgress extends javax.swing.JFrame {
public UIProgress() {
try {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
initComponents();
}
private void initComponents() {
panelHeading = new javax.swing.JPanel();
jLabel1 = new javax.swing.JLabel();
progress_cntrl = new javax.swing.JProgressBar();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
panelHeading.setBackground(new java.awt.Color(204, 204, 204));
panelHeading.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0)));
panelHeading.setDebugGraphicsOptions(javax.swing.DebugGraphics.NONE_OPTION);
panelHeading.setOpaque(false);
jLabel1.setBackground(new java.awt.Color(0, 0, 0));
jLabel1.setIcon(new javax.swing.ImageIcon(getClass().getResource("/devtriagerepot_daily/Background-20.jpeg"))); // NOI18N
javax.swing.GroupLayout panelHeadingLayout = new javax.swing.GroupLayout(panelHeading);
panelHeading.setLayout(panelHeadingLayout);
panelHeadingLayout.setHorizontalGroup(
panelHeadingLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, panelHeadingLayout.createSequentialGroup()
.addContainerGap(29, Short.MAX_VALUE)
.addComponent(progress_cntrl, javax.swing.GroupLayout.PREFERRED_SIZE, 651, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(27, 27, 27))
.addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)
);
panelHeadingLayout.setVerticalGroup(
panelHeadingLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(panelHeadingLayout.createSequentialGroup()
.addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 147, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(26, 26, 26)
.addComponent(progress_cntrl, javax.swing.GroupLayout.PREFERRED_SIZE, 40, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(0, 31, Short.MAX_VALUE))
);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(panelHeading, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(0, 4, Short.MAX_VALUE))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(panelHeading, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
);
getAccessibleContext().setAccessibleParent(this);
pack();
}
public void prepareGUI ()
{
progress_cntrl.setMaximum(120);
progress_cntrl.setStringPainted(true);
Dimension dimension = Toolkit.getDefaultToolkit().getScreenSize();
int x = (int) ((dimension.getWidth() - this.getWidth()) / 2);
int y = (int) ((dimension.getHeight() - this.getHeight()) / 2);
this.setLocation(x, y);
pack ();
setVisible(true);
}
public void updateProgress (int val)
{
progress_cntrl.update(progress_cntrl.getGraphics());
progress_cntrl.setValue(val);
}
The key is likely in these methods here:
getWorkedItems ();
getWorkedTickets ();
getRemainTickets ();
If they take any time to perform at all, your calling them on the Swing event thread will block the thread and freeze your GUI completely, rendering it unable to draw itself properly. The solution is to call any long-running methods in a background thread, such as a SwingWorker's doInBackground() method, and make Swing calls only on the Swing event thread. Again a SwingWorker would work well for this, and in fact it has its own "bound" progress property that can be used. Within the worker simply call setProgress(value) where value is your int from 0 to 100. Then attach a PropertyChangeListener to the worker so that the GUI can be notified of these changes when the progress property is updated.
A caveat: be sure to listen for the worker to finish its run so that you can call get() on the worker as this ill allow you to trap and respond to any exceptions that might have been called during its run.
For example, your code could look something like:
public void executeTask() {
progress = new UIProgress();
progress.prepareGUI();
final SwingWorker<Void, Void> myWorker = new SwingWorker<Void, Void>() {
#Override
protected Void doInBackground() throws Exception {
// progress.updateProgress (10);
setProgress(10); // sets the worker's "bound" progress property
getWorkedItems();
setProgress(30);
getWorkedTickets();
setProgress(50);
getRemainTickets();
setProgress(70);
// ... only further background work goes here
// no direct Swing calls
return null;
}
};
myWorker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) {
// if the progress property has been changed
// get its value and use it to update the GUI
progress.updateProgress((int) evt.getNewValue());
} else if (SwingWorker.StateValue.DONE == evt.getNewValue()) {
// worker is done then here notify the GUI
// perhaps call:
// jf.postTriagedTicketDetailsDaily();
// call get() on worker to catch and handle exceptions
try {
myWorker.get();
} catch (InterruptedException | ExecutionException e) {
// TODO handle the excpetions here
e.printStackTrace();
}
}
}
});
myWorker.execute();
}
Note: code not tested.
Please check out Lesson: Concurrency in Swing
Also check out the Swing Tutorials
If this doesn't solve your problem, then you will likely have to create and post a sscce or a minimal example program/mcve where you condense your code into the smallest bit that still compiles and runs, has no outside dependencies (such as need to link to a database or images), has no extra code that's not relevant to your problem, but still demonstrates your problem.
For example, this small program demonstrates the above code in a working GUI:
import java.awt.*;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import javax.swing.*;
public class TestWorker {
private UIProgress progress;
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
new TestWorker().executeTask();
});
}
public void executeTask() {
progress = new UIProgress();
progress.prepareGUI();
final SwingWorker<Void, Void> myWorker = new SwingWorker<Void, Void>() {
#Override
protected Void doInBackground() throws Exception {
// progress.updateProgress (10);
setProgress(10); // sets the worker's "bound" progress property
getWorkedItems();
setProgress(30);
getWorkedTickets();
setProgress(50);
getRemainTickets();
setProgress(70);
TimeUnit.SECONDS.sleep(2);
// ... only further background work goes here
// no direct Swing calls
return null;
}
};
myWorker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) {
// if the progress property has been changed
// get its value and use it to update the GUI
progress.updateProgress((int) evt.getNewValue());
} else if (SwingWorker.StateValue.DONE == evt.getNewValue()) {
// worker is done then here notify the GUI
progress.updateProgress(100);
// perhaps call:
// jf.postTriagedTicketDetailsDaily();
// call get() on worker to catch and handle exceptions
try {
myWorker.get();
} catch (InterruptedException | ExecutionException e) {
// TODO handle the exceptions here
e.printStackTrace();
}
}
}
});
myWorker.execute();
}
// dummy methods just to demonstrate long-running code
private void getRemainTickets() {
mySleep(3); // emulate long-running code
}
private void getWorkedTickets() {
mySleep(4);
}
private void getWorkedItems() {
mySleep(2);
}
private void mySleep(int seconds) {
try {
TimeUnit.SECONDS.sleep(seconds);
} catch (InterruptedException e) {}
}
#SuppressWarnings("serial")
private class UIProgress extends JPanel {
private static final int PREF_W = 400;
private static final int PREF_H = 100;
private JProgressBar progressBar = new JProgressBar(0, 100);
private JLabel statusLabel = new JLabel(" ");
public UIProgress() {
JPanel statusPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0));
statusPanel.add(new JLabel("Status:"));
statusPanel.add(Box.createHorizontalStrut(4));
statusPanel.add(statusLabel);
setLayout(new BorderLayout());
setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
add(statusPanel, BorderLayout.PAGE_START);
add(progressBar, BorderLayout.PAGE_END);
}
public void prepareGUI() {
JFrame frame = new JFrame("UI Progress");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(this);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
public void updateProgress(int prog) {
String text = String.format("Current Progress is %d%%", prog);
statusLabel.setText(text);
progressBar.setValue(prog);
}
}
}

Am I updating Swing component outside of EDT?

I've been reading a lot about Swing, threading, invokeLater(), SwingWorker, etc., but I just can't seem to get my head around it all, so I was trying to create a really simple program to illustrate. I've looked at a lot of examples, but none of them seem to show just what I'm trying to do.
Here's what I'm trying to do in my example. I have a button and a label, and when I click the button, I want the program to pause for 3 seconds before appending a period to the text of the label. During that 3 seconds, I want the GUI to appear as normal and to continue responding to additional clicks. Here's what I wrote:
import javax.swing.SwingWorker;
public class NewJFrame extends javax.swing.JFrame
{
private javax.swing.JButton jButton1;
private javax.swing.JLabel jLabel1;
public NewJFrame()
{
initComponents();
}
private void initComponents()
{
jButton1 = new javax.swing.JButton();
jLabel1 = new javax.swing.JLabel();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
jButton1.setText("Button");
jButton1.addActionListener(new java.awt.event.ActionListener()
{
public void actionPerformed(java.awt.event.ActionEvent evt)
{
jButton1ActionPerformed(evt);
}
});
getContentPane().add(jButton1, java.awt.BorderLayout.CENTER);
jLabel1.setText("Text");
getContentPane().add(jLabel1, java.awt.BorderLayout.PAGE_END);
pack();
}
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt)
{
SwingWorker worker=new SwingWorker()
{
protected Object doInBackground()
{
try{Thread.sleep(3000);}
catch (InterruptedException ex){}
return null;
}
};
worker.execute();
jLabel1.setText(jLabel1.getText()+".");
}
public static void main(String args[])
{
java.awt.EventQueue.invokeLater(new Runnable()
{
public void run(){ new NewJFrame().setVisible(true); }
});
}
}
with this code, if I click the button, the period is immediately appended to the label, which makes sense to me, because I am creating and sleeping a background thread, leaving the EDT available to update the label immediately. So I tried this:
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt)
{
Thread thread = new Thread();
try{ thread.sleep(3000);}
catch (InterruptedException ex){}
jLabel1.setText(jLabel1.getText()+".");
}
This almost works except that it blocks the EDT causing the button to turn blue for three seconds before appending the period to the label's text. I don't want the button to look like it's being pressed for the whole three seconds when it was really just clicked quickly, so I tried this:
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt)
{
SwingWorker worker=new SwingWorker()
{
protected Object doInBackground()
{
try{Thread.sleep(3000);}
catch (InterruptedException ex){}
jLabel1.setText(jLabel1.getText()+".");
return null;
}
};
worker.execute();
}
This appears to work, but aren't I calling jLabel1.setText(...) from the background thread and not the EDT, and therefore breaking the "Swing Single Threading Rule?" If so, is there a better way to achieve the desired effect? If not, can you please explain why?
You're really close...
Try something like this instead.
SwingWorker worker=new SwingWorker()
{
protected Object doInBackground()
{
try{
Thread.sleep(3000);
}catch (InterruptedException ex){}
return null;
}
// This is executed within the context of the EDT AFTER the worker has completed...
public void done() {
jLabel1.setText(jLabel1.getText()+".");
}
};
worker.execute();
You can check to see if you're running in the EDT through the use of EventQueue.isDispatchingThread()
Updated
You could also use a javax.swing.Timer which might be easier...
Timer timer = new Timer(3000, new ActionListener() {
public void actionPerformed(ActionEvent evt) {
jLabel1.setText(jLabel1.getText()+".");
}
});
timer.setRepeats(false);
timer.start();

How to Pause and Resume a Thread in Java from another Thread

I'm writing an application with Java Swing. What i need is a procedure where i can stop the "elaboration" thread using a button in the graphic interface.
Here a simple project focused on what i need
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JTextArea;
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
/**
*
* #author Nikola
*/
public class Main extends javax.swing.JFrame
{
private MyThread THREAD;
public Main()
{
initComponents();
}
#SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">
private void initComponents() {
jButton1 = new javax.swing.JButton();
jScrollPane1 = new javax.swing.JScrollPane();
jTextArea1 = new javax.swing.JTextArea();
jButton2 = new javax.swing.JButton();
jButton3 = new javax.swing.JButton();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
jButton1.setText("Pause Thread");
jButton1.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
jButton1ActionPerformed(evt);
}
});
jTextArea1.setColumns(20);
jTextArea1.setRows(5);
jScrollPane1.setViewportView(jTextArea1);
jButton2.setText("Resume Thread");
jButton2.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
jButton2ActionPerformed(evt);
}
});
jButton3.setText("Start Thread");
jButton3.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
jButton3ActionPerformed(evt);
}
});
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(jButton3)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 63, Short.MAX_VALUE)
.addComponent(jButton2)
.addGap(18, 18, 18)
.addComponent(jButton1))
.addComponent(jScrollPane1))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addContainerGap()
.addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 244, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jButton1)
.addComponent(jButton2)
.addComponent(jButton3))
.addContainerGap())
);
pack();
}// </editor-fold>
private void jButton3ActionPerformed(java.awt.event.ActionEvent evt)
{
THREAD = new MyThread(jTextArea1);
THREAD.start();
}
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt)
{
try
{
THREAD.pauseThread();
}
catch (InterruptedException ex)
{
ex.printStackTrace();
}
}
private void jButton2ActionPerformed(java.awt.event.ActionEvent evt)
{
THREAD.resumeThread();
}
public static void main(String args[])
{
/*
* Set the Nimbus look and feel
*/
//<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
/*
* If Nimbus (introduced in Java SE 6) is not available, stay with the
* default look and feel. For details see
* http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
*/
try
{
for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels())
{
if ("Nimbus".equals(info.getName()))
{
javax.swing.UIManager.setLookAndFeel(info.getClassName());
break;
}
}
}
catch (ClassNotFoundException ex)
{
java.util.logging.Logger.getLogger(Main.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
}
catch (InstantiationException ex)
{
java.util.logging.Logger.getLogger(Main.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
}
catch (IllegalAccessException ex)
{
java.util.logging.Logger.getLogger(Main.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
}
catch (javax.swing.UnsupportedLookAndFeelException ex)
{
java.util.logging.Logger.getLogger(Main.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
}
//</editor-fold>
/*
* Create and display the form
*/
java.awt.EventQueue.invokeLater(new Runnable()
{
public void run()
{
new Main().setVisible(true);
}
});
}
// Variables declaration - do not modify
private javax.swing.JButton jButton1;
private javax.swing.JButton jButton2;
private javax.swing.JButton jButton3;
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JTextArea jTextArea1;
// End of variables declaration
}
class MyThread extends Thread
{
JTextArea area;
private final Object lock = new Object();
public MyThread(JTextArea area)
{
super();
this.area = area;
}
#Override
public void run()
{
for(int i=0 ; ; i++)
area.setText(i+"");
}
public void pauseThread() throws InterruptedException
{
synchronized(lock)
{
lock.wait();
}
}
public void resumeThread()
{
synchronized(lock)
{
lock.notify();
}
}
}
The question is simple:
In the real application, the user set some options and then start the thread which doing the elaboration of the selected data.
I want to provide a "pause" button so the user can stop temporarily the elaboration and make some needed check and after that can resume the operation.
In the way i coded is the graphic thread that stop, not the "elaboration" one.
If you run my sample code and press "Start" the textarea starts to counting. The final result that i need is that when i press the "Pause" button the thread go to "sleep" and the counting stops, when i press the "Resume" button the thread "wakes up" and the counting in the text area starts againt to count.
You can't definitively pause one thread from another in the way you seem to want.
What you need to do instead, is signal that the other thread should stop, by setting some sort of flag. The thread in question must have logic to check this flag and pause its work when that happens.
So in this particular case, perhaps change MyThread as follows:
class MyThread extends Thread {
private volatile boolean running = true; // Run unless told to pause
...
#Override
public void run()
{
for(int i=0 ; ; i++)
{
// Only keep painting while "running" is true
// This is a crude implementation of pausing the thread
while (!running)
yield;
area.setText(i+"");
}
public void pauseThread() throws InterruptedException
{
running = false;
}
public void resumeThread()
{
running = true;
}
}
This is a crude example that for brevity uses a sort of spinlock rather than proper monitor-based sleeping. Hopefully though it communicates the idea of how you use a flag to control the pausing of the thread.
Note that if you were doing some long-running set of steps within the block, instead of just the setText call, it would be good practice to check Thread.currentThread().interrupted() between each of the steps - and exit the loop if the itnerrupt flag is set. This is broadly what the built-in blocking methods (e.g. I/O) do so that they can be interrupted by other threads - since the running flag is only checked one per loop, it doesn't do much good to set it if each loop takes 20 minutes.
Try it like this:
class MyThread extends Thread {
JTextArea area;
private final Object GUI_INITIALIZATION_MONITOR = new Object();
private boolean pauseThreadFlag = false;
public MyThread(JTextArea area) {
super();
this.area = area;
}
#Override
public void run() {
for(int i=0 ; ; i++) {
checkForPaused();
area.setText(i+"");
}
}
private void checkForPaused() {
synchronized (GUI_INITIALIZATION_MONITOR) {
while (pauseThreadFlag) {
try {
GUI_INITIALIZATION_MONITOR.wait();
} catch (Exception e) {}
}
}
}
public void pauseThread() throws InterruptedException {
pauseThreadFlag = true;
}
public void resumeThread() {
synchronized(GUI_INITIALIZATION_MONITOR) {
pauseThreadFlag = false;
GUI_INITIALIZATION_MONITOR.notify();
}
}
}
It is a good idead to use monitors like you did. But you can not force the wait from outside. You have to tell the thread to wait, until you notify him again (over the monitor). In this case, you just have this simple method checkForPaused() that you will have to put in strategic positions so there is not a long delay until the thread is paused.
You can also extend this function so you can ask the thread if he is paused with a flag set in checkForPaused() and a public boolean isPausing().

Categories