Problem: SWT freezes when GUI field is periodically updated.
I would like to have a SWT-based GUI with text field were values are periodically incremented.
Initially I accessed textField from separate Thread what led to throwing exception:
Exception in thread "Thread-0" org.eclipse.swt.SWTException: Invalid
thread access at org.eclipse.swt.SWT.error(SWT.java:4533) at
org.eclipse.swt.SWT.error(SWT.java:4448) at
org.eclipse.swt.SWT.error(SWT.java:4419) at
org.eclipse.swt.widgets.Widget.error(Widget.java:482) at
org.eclipse.swt.widgets.Widget.checkWidget(Widget.java:373) at
org.eclipse.swt.widgets.Text.setText(Text.java:2311) at
regreon.Incrementing.lambda$0(Incrementing.java:62) at
java.lang.Thread.run(Thread.java:745)
After reading SWT documentation (thanks to #marko-topolnik ) - I tried using display.SyncExec(Runnable r) or display.AsyncExec(Runnable r) with runnable that called Thread.sleep in the loop. But this caused the whole thing to freeze.
Here is the code:
package whatever;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.SWT;
public class FreezingGUI {
protected Shell shell;
private Text text;
public static void main(String[] args) {
try {
FreezingGUI window = new FreezingGUI();
window.open();
} catch (Exception e) {
e.printStackTrace();
}
}
public void open() {
Display display = Display.getDefault();
createContents();
shell.open();
shell.layout();
// HOW TO DO THAT???
display.syncExec(() -> {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Integer i = Integer.parseInt(text.getText()) + 1;
text.setText(i.toString());
}
}
}
);
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
}
protected void createContents() {
shell = new Shell();
shell.setSize(450, 300);
shell.setText("SWT Application");
text = new Text(shell, SWT.BORDER);
text.setEditable(false);
text.setText("0");
text.setBounds(30, 32, 78, 26);
}
}
How to avoid freezing and throwing exception?
Any SWT operation which changes a UI object must be run on the SWT User Interface thread.
In your case the text.setText(i.toString()); line is an SWT UI operation and is running in a different thread.
You can use the asyncExec or syncExec methods of Display to run some code in the UI thread. So replace:
text.setText(i.toString());
with
final String newText = i.toString();
Display.getDefault().asyncExec(() -> text.setText(newText));
(this is assuming you are using Java 8).
Using asyncExec will do the UI update asynchronously. Use syncExec instead if you want to pause the thread until the update is done.
If you are using Java 7 or earlier use:
final String newText = i.toString();
Display.getDefault().asyncExec(new Runnable() {
#Override
public void run() {
text.setText(newText);
}
});
Note you should also be checking for the Shell being disposed and stopping your background thread. If you don't do this you will get an error when you close the app. Your code incrementing i is also wrong. This thread works:
new Thread(() -> {
for (int i = 1; true; i++) {
try {
Thread.sleep(1000);
} catch (final InterruptedException e) {
return;
}
if (shell.isDisposed()) // Stop thread when shell is closed
break;
final String newText = Integer.toString(i);
Display.getDefault().asyncExec(() -> text.setText(newText));
}
}).start();
Looking at your code the reason your UI is freezing is because you are executing a runnable on Display.sync that never returns because of the while(true) loop, as a result, other UI threads don't get a chance to execute and the UI freezes. What you need to do is use Displasy.asyncRunnable and instead of having a runnable with while(true) make a scheduler or another thread that sleeps every x seconds that executes the runnable to make the update you want, this way the other UI threads can run preventing your UI from freezing.
Related
Here is an example created from http://www.java2s.com/Code/Java/SWT-JFace-Eclipse/SWTandThread.htm
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
public class MultiThread extends Shell {
Button btnNewButton = new Button(this, SWT.NONE);
/**
* Launch the application.
* #param args
*/
public static void main(String args[]) {
try {
Display display = Display.getDefault();
MultiThread shell = new MultiThread(display);
shell.open();
shell.layout();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Create the shell.
* #param display
*/
public MultiThread(Display display) {
super(display, SWT.SHELL_TRIM);
btnNewButton.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent e) {
applicationThread.start();
}
});
btnNewButton.setBounds(86, 47, 68, 23);
btnNewButton.setText("New Button");
createContents();
}
/**
* Create contents of the shell.
*/
protected void createContents() {
setText("SWT Application");
setSize(450, 300);
}
#Override
protected void checkSubclass() {
// Disable the check that prevents subclassing of SWT components
}
final Runnable print = new Runnable() {
public void run() {
System.out.println("Print from thread: \t" + Thread.currentThread().getName());
}
};
final Thread applicationThread = new Thread("currentThread") {
public void run() {
System.out.println("Hello from thread: \t" + Thread.currentThread().getName());
getDisplay().syncExec(print);
System.out.println("Bye from thread: \t" + Thread.currentThread().getName());
}
};
}
My question is: why IllegalThreadStateException occur on second click on button? Is it because second click creates a thread with same name as previous? How can I avoid that?
Thank you in advance!
Use Below code to set threads Name
Thread.currentThread().setName("Hello");
I hope you are starting a thread again without instantiating an object of thread which will result an java.lang.IllegalThreadStateException..
for e.g if you start an thread by instantiating and call start to start executing a thread. Again if you call thread.start will leads to java.lang.IllegalThreadStateException..
The same thing you are trying to achieve in your code.
once your button is clicked you are executing an thread and starting the thread again by calling thread.start()..
For every button click you should start a new thread() by instantiating object of thread.
From the documentation of Thread.start:
throws
IllegalThreadStateException - if the thread was already started.
You are not creating a new thread with the same name, you are calling start() on the same thread again and get what the documentation promised you.
It seems, whoever made that example did not consider the possibility that a user clicks a button more than onceā¦
The problem is that you can't start same thread twice: check javadoc
It is never legal to start a thread more than once. In particular, a
thread may not be restarted once it has completed execution.
I have a Java program compiled in a .jar, so the end user can't really just ctrl+c it in the console.
They have to end the java process in the task manager.
However, there is a much simpler way, isn't there?
public class Test extends JFrame {
private JPanel contentPane;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
Test frame = new Test();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public Test() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 450, 300);
contentPane = new JPanel();
setContentPane(contentPane);
JButton go = new JButton("Go");
go.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Process p;
Runtime r = Runtime.getRuntime();
while(true) {
try {
p = r.exec("notepad.exe");
p.waitFor();
} catch (IOException | InterruptedException e1) {
e1.printStackTrace();
}
}
}
});
contentPane.add(go);
}
}
As you can see, all it does, once you press the Go button, is spawn a notepad process.
As soon as you close notepad, it spawns another one. I want it to do that.
However, there's no way to stop it halting. For example, pressing the X on the pane doesn't do anything.
How do I make it so that the X effectively closes the Java program, while keeping all the contingencies above?
1) the action performed is running on the EDT thread: the java thread executing all events/event handlers. As p.waitFor does not return immediately it will block all futher event handling
2) one should never run long running actions on the edt thread. In this case I suggest to spwan a new thread that will start the notepad.exe and wait for it in a different thread...
3) another point is: why do you want to wait for the notepad.exe to exit ? There is some subtle inconsistency here, from one perspective you want the application to continue normal processing (clicking on the x box should exit the application) and on the other hand you want your application not to continue normal processing as you wish to wait for the notepad to exit...
explain your contigencies a bit better
I am attempting to make an application that retrieves images and .mp3 files and transitions from one image to the next once the audio has finished. The underlying framework of how I transition between these images is a little convoluted, but I have managed to get an action in SWT that successfully enables me to manually transition from one to the next. However, a problem has arisen when I've tried to automate it; when placed into a loop, my playAudio() method begins before all of the calls I make in my displayShow() method have resolved, which results in a blank window, despite the audio still playing.
Here is the run method for the action that I want to start the show:
Action startAction = new Action("Start") {
public void run() {
//do {
displayShow();
playAudio();
//} while(true);
}
};
Here is playAudio(). I am able to PLAY the audio without incident:
public void playAudio() {
final String audio = "Happy_Birthday.mp3";
audioLatch = new CountDownLatch(1);
audioThread = new Thread() {
public void run() {
try {
Player player = new Player
(new BufferedInputStream
(new FileInputStream(audio)));
player.play();
audioLatch.countDown();
} catch (JavaLayerException e) {
} catch (FileNotFoundException e) {
} catch (Exception e) {
}
}
};
audioThread.start();
try {
audioLatch.await();
} catch (InterruptedException e) {
}
}
And here is displayShow():
private void displayShow() {
new Thread() {
public void run() {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
Control[] children = container.getChildren();
for (int i = 0; i < children.length; i++) {
children[i].dispose();
}
show.showSlide(container);
container.layout();
}
});
}
}.start();
}
show.showSlide returns a composite whose parent is container, which is the immediate child of the highest parent composite. Within the newly created composite, an image is added to a label and the label's parent is assigned to composite. I realize whether displayShow() is in a separate thread or not seems to be immaterial; this was just the last thing I tried.
It is not solely the addition of the loop that causes the refresh to not execute. The only way I can get the manual transition to work is if I remove the CountDownLatch from the playAudio() method. Were I to remove this latch, the only way to encase these two methods in a loop would be embedded while loops, which seem to hog a fair amount of the CPU and still does not solve my problem. Am I missing anything?
The audioLatch.await() is blocking the main program thread, this is the thread that all SWT operations run on so the Display.asyncExec runnables are just being queued until the thread is available.
If you really must wait in the playAudio method you could run the display event loop there until the background thread is finished:
while (! background thread finished)
{
if (!display.readAndDispatch())
display.sleep();
}
I am making a program to check the stock market for a symbol and I got that far, and added a basic gui to it. I am stumped on how to make it check every hour and create a green up arrow if it increased and red down arrow if it decreased.
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
public class QuoteTracker {
JFrame frame;
JPanel mainPanel;
JLabel enterLabel;
JLabel resultLabel;
JTextField text;
JTextField result;
JButton query;
JButton redArrow;
JButton greenArrow;
String url;
public static void main(String[] args) {
new QuoteTracker().buildGui();
}
public class checkingQuote implements Runnable {
#Override
public void run() {
while (true) {
try {
checkQuote(url);
//if increase in value green button
System.out.println("Sleeping");
Thread.sleep(1000 * 60 * 60);
System.out.println("Waking");
} catch (InterruptedException ie) {
ie.printStackTrace();
break;
}
}
}
}
public void checkQuote(String symbol) {
try {
String url = "http://finance.yahoo.com/q?s=" + symbol + "&ql=0";
this.url = url;
Document doc = Jsoup.connect(url).get();
Elements css = doc.select("p > span:first-child > span");
result.setText(css.text());
} catch (IOException e) {
}
}
public void buildGui() {
frame = new JFrame("QuoteTracker");
mainPanel = new JPanel();
enterLabel = new JLabel("enter symbol ");
resultLabel = new JLabel("result ");
text = new JTextField(4);
result = new JTextField(8);
query = new JButton("query");
query.addActionListener(new queryListener());
mainPanel.add(enterLabel);
mainPanel.add(text);
mainPanel.add(query);
mainPanel.add(resultLabel);
mainPanel.add(result);
frame.getContentPane().add(mainPanel);
frame.setSize(300, 400);
frame.setVisible(true);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
class queryListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent ev) {
checkQuote(text.getText());
}
}
}
Do I even need a thread? I've never made one before and tried to add things that made sense. I am thinking I either need a thread or to use java's Timer?
Use SwingWorker to execute long running task in the background while updating the UI based on some results from that long running task. That means, it is actually about two different threads communicating to each other - Worker Threads and Event Dispatch Thread (EDT)
But before that, I want to point some few notes about your code.
Invoke the initialization of your UI in the EDT. That is, instead of just straightly calling new QuoteTracker().buildGui(), call it inside the run method of a Runnable passed to SwingUtilities.invokeLater (like this)
Classes should start in capital letter as per the Java standard.
To apply SwingWorker in you existing code, you can do the following :
First, you must place your checkQuote method in some other class (ideally a service class) then modify your checkQuote method to return the String that is set to the textfield result. Something like this
public Class QuoteService{
public String checkQuote(String symbol) {
try {
String url = "http://finance.yahoo.com/q?s=" + symbol + "&ql=0";
this.url = url;
Document doc = Jsoup.connect(url).get();
Elements css = doc.select("p > span:first-child > span");
return css.text();
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
}
You can then make your QuoteTracker class to focus mainly in the UI part of your application. Just create the service object as instance level field so that you can freely call checkQuote method within your the class.
Invoke SwingWorker when the button is clicked.
public void actionPerformed(ActionEvent ev) {
new SwingWorker<Void, String>() {
#Override // this method is done in the Worker Thread
protected Void doInBackground() throws Exception {
while(true){
String res = checkQuote(text.getText());
publish(res); //will call the process method
Thread.sleep(1000 * 60 * 60); //1 hour
}
}
#Override // this method is done in the EDT
protected void process(List<String> resultList){
String res = resultList.get(0);
if(!"".equals(res)){
result.setText(res);
}
}
#Override // this method is done in the EDT. Executed after executing the doInBackground() method
protected void done() {
//... clean up
}
}.execute();
}
Note that done() will be executed after the execution of doInBackground() is finished, which means, in the code I posted, it will never be executed since the while loop used to periodically call checkQuote is infinite. Just modify it so that you can interrupt that loop according to your need
Further Read : Concurrency in Swing
You can use thread and normal while loop in main thread as well, but at the very first , you need to start you thread and that thread must refer your object.
Add following line in public void buildGui() {
Thread t1 = new Thread(new checkingQuote());
t1.start();
This will start you thread, for testing purpose i have modified checkingQuote class
public class checkingQuote implements Runnable {
int count = 0;
#Override
public void run() {
System.out.println("Inside Runner");
while (true) {
try {
count++;
checkQuote(url);
//if increase in value green button
result.setText(""+count);
System.out.println("Sleeping");
Thread.sleep(1000);
System.out.println("Waking");
} catch (InterruptedException ie) {
ie.printStackTrace();
break;
}
}
}
}
I am seeing number change in the text box.... same way you can alter the logic to get and show the quotes.. but you must keep the value for previous quote to compare with the latest code to show green and red notification...
In gui application it is better to use Timer, also you may use ScheduledThreadPoolExecutor. But in the second case notice, that your scheduled tasks may run in non-GUI thread. As you can't invoke ATW/Swing directly from another thread, you should wrap any call to Swing into SwingUtilities.invokeLater() method.
Also notice, that when you do something durable inside GUI thread, the whole GUI becomes unrepsonsive. So, to achieve a better responsiveness, you would query in a separate thread, and expose results to Swing through invokeLater after quotes have checked. So your checkQuote method may be rewritten this way:
public void checkQuote(String symbol) {
try {
final String url = "http://finance.yahoo.com/q?s=" + symbol + "&ql=0";
Document doc = Jsoup.connect(url).get();
Elements css = doc.select("p > span:first-child > span");
final String text = css.text();
SwingUtilities.invokeLater(new Runnable() {
#Override public void run() {
this.url = url;
result.setText(text);
}
}
} catch (IOException e) {
// Don't swallow exceptions
logger.error("Something gone wrong", e);
}
}
public void checkQuote() {
final String symbol = text.getText();
new Thread(new Runnable() {
#Override public void run() {
checkQuote(symbol);
}
}).start();
}
and call it from Timer and from button click listener.
I am interfacing with a JNI that allows me to use a fingerprint scanner. The code I have written takes the scanned ByteBuffer parsed back to it by the JNI and turns it into a BufferedImage for saving.
What I can't figure out is how to wait for the scan thread to finish before the the jlabel icon on my GUI tries to update. What would be the easiest way to do this?
What else do I need to add?
Edit:
//Scanner class
Thread thread = new Thread() {
public void run() {
// [...] get ByteBuffer and Create Image code
try {
File out = new File("C:\\Users\\Desktop\\print.png");
ImageIO.write(padded, "png", out);
// [???] set flag here
} catch (IOException e) {
e.printStackTrace();
}
}
};
thread.start();
return true;
//Gui class
private void btnScanPrintActionPerformed(java.awt.event.ActionEvent evt) {
Scanner scanPrint = new Scanner();
boolean x = scanPrint.initDevice();
//Wait for the scanning thread to finish the Update the jLabel here to show
//the fingerprint
}
Not sure if you are using Swing or Android for UI but you would want to notify the main event dispatch thread (in swing it is called just that). You would run the scanning thread, then when complete send a 'message' to the EDT with the action that you want to be done to the button.
Thread thread = new Thread(new Runnable(){
public void run(){
//scan
SwingUtiltilies.invokeLater(new Runnable(){
//here you can update the the jlabel icon
public void run(){
jlabel.setText("Completed");
}
});
}
});
In UI development there is no waiting for an action to complete because you always want the EDT to be responsive.
at the end of a the scan thread, use SwingUtilities.invokeLater() to update the gui with the results of the scan.