I come from .NET environment where event listening is pretty easy to implement even for a beginner. But this time I have to do this in Java.
My pseudo code:
MainForm-
public class MainForm extends JFrame {
...
CustomClass current = new CustomClass();
Thread t = new Thread(current);
t.start();
...
}
CustomClass-
public class CustomClass implements Runnable {
#Override
public void run()
{
//...be able to fire an event that access MainForm
}
}
I found this example but here I have to listen for an event like in this other one. I should mix them up and my skill level in Java is too low.
Could you help me elaborating a optimal solution?
I think that what you are looking for is SwingWorker.
public class BackgroundThread extends SwingWorker<Integer, String> {
#Override
protected Integer doInBackground() throws Exception {
// background calculation, will run on background thread
// publish an update
publish("30% calculated so far");
// return the result of background task
return 9;
}
#Override
protected void process(List<String> chunks) { // runs on Event Dispatch Thread
// if updates are published often, you may get a few of them at once
// you usually want to display only the latest one:
System.out.println(chunks.get(chunks.size() - 1));
}
#Override
protected void done() { // runs on Event Dispatch Thread
try {
// always call get() in done()
System.out.println("Answer is: " + get());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Of course when using Swing you want to update some GUI components instead of printing things out. All GUI updates should be done on Event Dispatch Thread.
If you want to only do some updates and the background task doesn't have any result, you should still call get() in done() method. If you don't, any exceptions thrown in doInBackground() will be swallowed - it is very difficult to find out why the application is not working.
Related
It's my first time making GUI on java, and I have a small issue that is pretty annoying.
My code looks something like this.
private void RunButtonActionPerformed(java.awt.event.ActionEvent evt){
richText.append("Starting...");
try{ something happens here }
richText.append("Done...");
}
The problem is that when I click run button, it waits until it finishes the task and print "Starting..." and "Done..." at the same time. How do I make it print "Starting" first before and print "Done" after?
This code is executed in EDT, so any UI changes (richText.append in your case) will be repainted after it. You should execute your heavy task in new thread.
private void RunButtonActionPerformed(java.awt.event.ActionEvent evt){
richText.append("Starting...");
new Thread() {
public void run() {
try{ something happens here }
SwingUtilities.invokeLater(new Runnable() {
richText.append("Done...");
});
}
}.start();
}
Or use SwingWorker to get extra functionality such as reporting progress of task completion
I followed the tutorial for SwingWorker as suggested on the comment, and it worked! It looks something like this.
`private class Worker extends SwingWorker<Void, Void>{
protected Void doInBackground() throws Exception{
try{ things happen here }
return null;
}
#Override
protected void done(){
try{ get (); } catch (){}
}
}
And to call this, RunButtonActionPerformed just needs new Worker().execute().
basically, I have this code which was initially working with console i/o now I have to connect it to UI. It may be completely wrong, I've tried multiple things although it still ends up with freezing the GUI.
I've tried to redirect console I/O to GUI scrollpane, but the GUI freezes anyway. Probably it has to do something with threads, but I have limited knowledge on it so I need the deeper explanation how to implement it in this current situation.
This is the button on GUI class containing the method that needs to change this GUI.
public class GUI {
...
btnNext.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
controller.startTest(index, idUser);
}
});
}
This is the method startTest from another class which contains instance of Question class.
public int startTest() {
for (int i = 0; i < this.numberofQuestions; i++) {
Question qt = this.q[i];
qt.askQuestion(); <--- This needs to change Label in GUI
if(!qt.userAnswer()) <--- This needs to get string from TextField
decreaseScore(1);
}
return actScore();
}
askQuestion method:
public void askQuestion() {
System.out.println(getQuestion());
/* I've tried to change staticaly declared frame in GUI from there */
}
userAnswer method:
public boolean userAnswer() {
#SuppressWarnings("resource")
Scanner scanner = new Scanner(System.in);
if( Objects.equals(getAnswer(),userInput) ) {
System.out.println("Correct");
return true;
}
System.out.println("False");
return false;
}
Thanks for help.
You're correct in thinking that it related to threads.
When you try executing code that will take a long time to process (eg. downloading a large file) in the swing thread, the swing thread will pause to complete execution and cause the GUI to freeze. This is solved by executing the long running code in a separate thread.
As Sergiy Medvynskyy pointed out in his comment, you need to implement the long running code in the SwingWorker class.
A good way to implement it would be this:
public class TestWorker extends SwingWorker<Integer, String> {
#Override
protected Integer doInBackground() throws Exception {
//This is where you execute the long running
//code
controller.startTest(index, idUser);
publish("Finish");
}
#Override
protected void process(List<String> chunks) {
//Called when the task has finished executing.
//This is where you can update your GUI when
//the task is complete or when you want to
//notify the user of a change.
}
}
Use TestWorker.execute() to start the worker.
This website provides a good example on how to use
the SwingWorker class.
As other answers pointed out, doing heavy work on the GUI thread will freeze the GUI. You can use a SwingWorker for that, but in many cases a simple Thread does the job:
Thread t = new Thread(){
#Override
public void run(){
// do stuff
}
};
t.start();
Or if you use Java 8+:
Thread t = new Thread(() -> {
// do stuff
});
t.start();
I am new to java
I have a function runner inside PhotoPoster class (PhotoPoster class is a Jframe with lots of buttons and other GUI elements)
it contains a START button when I click on START it executes the runner function which runs an activity & thats takes a very long time
and other GUI components are not accessible unless the runner functions completes.
I want this to work in separate thread or any other solution to help me
what I currently do is
I have made a another class GuiWorker.java
public class GuiWorker extends SwingWorker<Integer, Integer>{
public GuiWorker() throws IOException {
}
protected Integer doInBackground() throws Exception {
PhotoPoster photoPoster = new PhotoPoster();
photoPoster.ruuner();
return 0;
}
protected void done() {
System.out.println("done");
}
}
PhotoPoster.java
on button click
private void jButton4ActionPerformed(java.awt.event.ActionEvent evt) {
// PhotoPoster photoPoster = new PhotoPoster();
//ruuner();
EventQueue.invokeLater( new Runnable() {
#Override
public void run() {
try {
new GuiWorker().execute();
} catch (IOException ex) {
Logger.getLogger(PhotoPoster.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
}
it gives system out done but not performing runner function activity
Please Help me to sort out this error or any other solution
It looks as though you are queuing your long running thread back onto the Event Dispatch Thread, which means that even though you are doing invokeLater it will still block the thread.
You need to use a SwingWorker.
As I am using gui and I need to create a thread to complete a task. See I want to display a dialog letting the user know the task has been completed I have tried
if(!thread.isAlive()) {
JOptionPane.showMessageDialog(null, "Done");
}
But that doesnt work.
Can anyone help me
Thanks
One option is to do your work using a SwingWorker. Override the done() method and have it notify your GUI that work is complete.
A simple example that nearly matches your use case is shown in the Javadocs at the top of the page:
final JLabel label;
class MeaningOfLifeFinder extends SwingWorker<String, Object> {
#Override
public String doInBackground() {
// Here you do the work of your thread
return findTheMeaningOfLife();
}
#Override
protected void done() {
// Here you notify the GUI
try {
label.setText(get());
} catch (Exception ignore) {
}
}
}
You could just have the thread print a message as it's last line of code in it's run method:
Thread thread = new Thread() {
#Override
public void run() {
//whatever you want this thread to do
//as the last line of code = the thread is going to terminate
JOptionPane.showMessageDialog(null, "Done");
}
}
thread.start();
If you want the main thread to wait for the thread to finish, in the main thread's code you'd use:
thread.join();
create a listener in your main Thread, and then program your Thread to tell the listener that it has completed.
public interface ThreadCompleteListener {
void notifyOfThreadComplete(final Thread thread);
}
then create the following class:
public abstract class NotifyingThread extends Thread {
private final Set<ThreadCompleteListener> listeners
= new CopyOnWriteArraySet<ThreadCompleteListener>();
public final void addListener(final ThreadCompleteListener listener) {
listeners.add(listener);
}
public final void removeListener(final ThreadCompleteListener listener) {
listeners.remove(listener);
}
private final void notifyListeners() {
for (ThreadCompleteListener listener : listeners) {
listener.notifyOfThreadComplete(this);
}
}
#Override
public final void run() {
try {
doRun();
} finally {
notifyListeners();
}
}
public abstract void doRun();
}
NotifyingThread thread1 = new OneOfYourThreads();
thread1.addListener(this); // add ourselves as a listener
thread1.start(); // Start the Thread
then, as each Thread exits, your notifyOfThreadComplete method will be invoked with the Thread instance that just completed. So now you can run any of your code in this method.
Use Callable thread. It will return value,So we can identify that it completed its task.
Java is not my mother tongue and I've been fighting with this problem for a little while.
Basically, I am finding a behavioural difference between calling method switchApplets() directly from init(), and calling it from within a new thread spawned by init().
The consequence of calling it from inside the new thread is that the new applet whitescreens -- until/unless the user resizes or minimizes their browser. If called at the end of init(), the new UI renders immediately without any input from the user. But that's not an option because it doesn't wait for the thread to finish its prep work.
Trimmed-down code:
public class PreLoader extends Applet implements AppletStub {
static JProgressBar pBar = null;
static JLabel message;
public void switchApplets() {
try {
Class main_class = Class.forName("MainClass");
Applet main_applet = (Applet)main_class.newInstance();
removeAll();
setSize(0,0);
setLayout(new GridLayout(1,0));
add(main_applet);
main_applet.init();
main_applet.start();
main_applet.setStub(this);
}
catch (Exception e) {
}
}
public void init() {
pBar = new JProgressBar(0, 100);
pBar.setValue(0);
pBar.setStringPainted(true);
message = new JLabel("Beginning work!");
add(message);
add(pBar);
FlowLayout flow = new FlowLayout();
setLayout(flow);
Thread t = new Thread ( new Runnable () {
public void run ()
{
longRunningFunction1();
longRunningFunction2();
message.setText("Work complete! Stand by..");
switchApplets(); //does NOT work as intended from here
return;
}
} );
t.start();
//switchApplets(); //works as intended if called HERE
}
public void longRunningFunction1() {
//perform some tasks, advance progress bar
}
public void longRunningFunction2() {
//perform some tasks, advance progress bar
}
public void start() {
return;
}
public void appletResize(int width, int height) {
return;
}
}
I tried making init() wait for the thread to finish so that I could call switchApplets() from there, but that only blocked the EDT and prevented the UI from updating. Also tried playing with SwingUtilities' invokeLater/invokeAndWait, but even though switchApplets() gets run on the EDT, it seems that it MUST be called directly from init() (or at least the thread init is running on) to have the desired effect.
Why does calling switchApplets() from within a new thread result in a slightly different (and unwanted) UI behaviour?
The consequence of calling it from inside the new thread is that the new applet whitescreens -- until/unless the user resizes or minimizes their browser.
It's likely a deadlock caused by trying to do UI code on the wrong thread.
I tried making init() wait for the thread to finish so that I could call switchApplets() from there, but that only blocked the EDT and prevented the UI from updating.
You're on the right track. You need to call switchApplets() only from the EDT, and only after the work is done on the other thread.
Are you sure you tried using invokeLater() or invokeAndWait() from within the spawned thread after the long running functions were done? It's been a long while since I did applets but I'm not aware of any applet-specific reason why it wouldn't work, and it would work in any other case. I.e.,
public void run()
{
longRunningFunction1();
longRunningFunction2();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
message.setText("Work complete! Stand by..");
switchApplets();
}
});
}
However, the most proper way to do this is with a SwingWorker rather than a manually created thread. SwingWorker (which is not nearly as well-known as it should be) is designed exactly for the goal of performing background tasks on a separate thread while still being able to update the GUI with progress updates and the results. E.g.,
new SwingWorker<Void,Void>() {
#Override
protected Void doInBackground() { // is called on a background thread
longRunningFunction1();
longRunningFunction2();
return null;
}
#Override
protected void done() { // is called on the Swing thread
message.setText("Work complete! Stand by..");
switchApplets();
}
}.execute();
The Void stuff is because SwingWorker is also capable of returning results and sending intermediate progress updates, but this example doesn't use those features.
You indicated that your long running functions are also updating a progress bar. That's another thing that should happen only on the Swing thread. In practice you can often get away without it, but it's dodgy. Your progress updates can use one of the SwingUtilities.invoke methods, or the mechanisms of SwingWorker; either should work. (SwingWorker itself provides two different ways to do it: Call addPropertyChangeListener (Swing thread) and setProgress (background thread), or call publish (background thread) and override process (Swing thread).)
Also, a small suggestion: if it's inconvenient to deal with a checked exception (or impossible to usefully do so), rather than catching and ignoring it, you should at least catch & rethrow it as an unchecked exception:
catch (Exception e) {
throw new RuntimeException(e);
}
That way, the stacktrace and error message of any exception will not be lost.