I am using a SwingWorker to execute something in the background. During the execution I have a condition where I need to ask the user something by throwing up a JoptionPane.showOptionDialog().
I don't want to do this in my model class and dont want to do this when SwingWorker.doInBackground is executing.
I am sure many people have faced this.
So I have to return back from the call to doInBackground and then ask for the user input in done(). I then need to start another SwingWorker and execute a doInBackground() from the done method?
Is there another neater/simpler way of doing this?
Update (for mkorbel's question)
The class design is like this:
public class OptionInSwingWorker {
public static void main(String[] args) {
JFrame frame=new JFrame();
JButton test = new JButton("Test");
frame.add(test);
test.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
new SwingWorker<Void,Void>(){
#Override
protected Void doInBackground() throws Exception {
// check for a value in the database
// if value is something.. throw up an OptionPane
// and ask the user a question..
// then do something...
return null;
}
#Override
protected void done() {
// open some other dialog
}
}.execute();
}
});
}
}
Make a SwingUtilities.invokeLater call that does a prompt and returns the result back to the SwingWorker. If possible have the SwingWorker move on, otherwise just have it loop and wait while it checks for a response.
This will allow you not have to return and start a new SwingWorker later. Although, depending on what you are doing, starting a new SwingerWorker might actually be cleaner and clearer.
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 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.
am back again!. i have a probrem here. am making uneditable jcombobox. i have a list of items in it("crus","davy","shawn") and i want if someone clicks on crus, a thread of images with a Thread.sleep of 2 seconds will appear at a jlabel called picturelabel. when i try to put method run() inside method actionperformed, i get "illegal start of expression". i also get an error "not a statement" when i try to create an array of imageicon.
public class Myjcombobox extends JFrame implements ActionListener,Runnable {
JComboBox job;
String[] items={"crus","shawn","davy","others"};
JLabel picturelabel;
public Myjcombobox(){
super("oh mymy");
setSize(1000,1000);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
BorderLayout border=new BorderLayout();
setLayout(border);
job=new JComboBox(items);
job.addActionListener(this);
}
public void actionPerformed(ActionEvent event){
JComboBox combo=(JComboBox)event.getSource();
String name=(String)combo.getSelectedItem();
if(name=="crus"){
public void run(){//i get an error illegal start of expression//
JImageIcon[] crusimages= new JImageIcon{"crus reading.jpg","crus playing.jpg","crus in class.jpg"}; //i get an error "not a statement","( or[ expected"//
}
}
}
public static void main(String[] args) {
Myjcombobox jcomb=new Myjcombobox();
}
if(name=="crus"){
{//i get an error illegal start of expression//
public void run()
//i get an error "not a statement","( or[ expected"//
JImageIcon[] crusimages= new JImageIcon{"crus reading.jpg","crus playing.jpg","crus in class.jpg"};
}
Every statement in the above code has a problem:
if(name=="crus"){
Don't use "==" to compare strings. Instead you should be using the equals(...) method:
if ("crus".equals(name));
Next, you can't just define a run() method in the middle of your code.
public void run()
The run() method belongs to a Thread, so you need to create a Thread and override the run() method. Something like:
Thread thread = new Thread()
{
#override
public void run()
{
System.out.println("I'm a Thread");
}
};
thread.start();
Finallay you can't create an Array of Image Icons in one step like this:
JImageIcon[] crusimages= new JImageIcon{"crus reading.jpg","crus playing.jpg","crus in class.jpg"};
You need to create an empty Array and then add the Icons one at a time:
ImageIcon[] crusImages = new new ImageIcon[3];
crusImage[0] = new ImageIcon( "crus reading.jpg" );
You can't just run() the thread, if you want to start it for the first time, you must use the method start(), witch starts a thread and runs the run() method. If you use just the run(), you won't be creating a thread, for it will run on the current thread.
Java Concurrency tutorial from Oracle: http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html.
Java Swing Components from Oracle: http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html.
I hop I could help.
Saclyr.
I've been searching near and far for a solution to my question but I am having difficulty even defining my search terms.
I have a method that creates a Swing GUI using invokeLater where the user completes some task. Once the task is completed, the window closes and the initial calling thread (e.g. the method) should resume execution. To be more specific, here is a summary of the method:
public class dfTestCase extends JFrame{
public dfTestCase{
... //GUI code here
}
public String run()
{
CountDownLatch c = new CountDownLatch(1);
Runnable r = new Runnable()
{
public void run()
{
setVisible(true); //make GUI visible
}
};
javax.swing.SwingUtilities.invokeLater(r);
//now wait for the GUI to finish
try
{
testFinished.await();
} catch (InterruptedException e)
{
e.printStackTrace();
}
return "method finished";
}
public static void main(String args[]){
dfTestCase test = new dfTestCase();
System.out.println(test.run());
}
}
Within the GUI, I have actionListeners for buttons that will close and countDown the CountDownLatch.
While the CountDownLatch works, it is not suitable for my purposes because I need to run this GUI several times and there is no way to increment the latch. I'm looking for a more elegant solution - it is my best guess that I would need to make use of threads but am unsure how to go about this.
Any help would be much appreciated!
Update
Some clarification: What is happening is that an external class is calling the dfTestCase.run() function and expects a String to be returned. Essentially, the flow is linear with the external class calling dfTestCase.run()-->the GUI being invoked-->the user makes a decision and clicks a button-->control to the initial calling thread is returned and run() is completed.
For now my dirty solution is to just put a while loop with a flag to continuously poll the status of the GUI. I hope someone else can suggest a more elegant solution eventually.
public class dfTestCase extends JFrame{
public dfTestCase{
... //GUI code here
JButton button = new JButton();
button.addActionListener{
public void actionPerformed(ActionEvent e){
flag = true;
}
}
}
public String run()
{
Runnable r = new Runnable()
{
public void run(){
setVisible(true); //make GUI visible
};
javax.swing.SwingUtilities.invokeLater(r);
//now wait for the GUI to finish
while (!flag){
sleep(1000);
}
return "method finished";
}
public static void main(String args[]){
dfTestCase test = new dfTestCase();
System.out.println(test.run());
}
}
Modal dialogs and SwingUtilities#invokeAndWait iso invokeLater should allow you to capture user input and only continue the calling thread when the UI is disposed
For an example of using model dialogs you can check out the ParamDialog class I wrote. In particular, check out ParamDialog.getProperties(Properties);
http://tus.svn.sourceforge.net/viewvc/tus/tjacobs/ui/dialogs/