Win a copy of Practical SVG this week in the HTML/CSS/JavaScript forum!
  • Post Reply Bookmark Topic Watch Topic
  • New Topic

flush JTextArea immediately

 
B Mayes
Ranch Hand
Posts: 47
Android Eclipse IDE Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Hi folks,

I have a program I am working on that allows for GUI output and/or console output (using System.out.println statements). I have written a simple function that will output data one place, or both, depending on the user's options:



This works just fine, the only problem with it is that it buffers the output for the JTextArea (named text) until all of the calls to the print() method have stopped. It may take up to 10 seconds to parse very large logs (i.e. megabytes of logs) and therefore the JTextArea seems to just sit there hanging, and then poof! it prints everything. Meanwhile if console output is turned on, you will see the println statements executed in real time.

I tried to adjust the method and wrap the call to append in a thread, but it still seems to just buffer the calls and only output once all calls to print have finished:




I am clearly missing something here. Can anyone tell me how to modify my print() method so that it will output each line in the JTextArea in real time, as opposed to buffering everything and then outputting everything at once? Thanks!

 
Rob Spoor
Sheriff
Posts: 20822
68
Chrome Eclipse IDE Java Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
JTextArea.append is one of the few exceptions that you can call on a thread other than the Event Dispatcher Thread (EDT). Still, eventually the visual update will be done on the EDT. Try using EventQueue.invokeAndWait*:
Note that it's a definite no-no to call EventQueue.invokeAndWait from the EDT - you will get an exception. If you're ever not sure use EventQueue.isDispatchThread to check first.

You may want to read Concurrency in Swing for more info.

*SwingUtilities.invokeAndWait and SwingUtilities.invokeLater merely pass the request to EventQueue.invokeAndWait / EventQueue.invokeLater.

 
B Mayes
Ranch Hand
Posts: 47
Android Eclipse IDE Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Cool thanks...that tutorial helps explain a lot. What is happening is that my class is implementing ActionListener (and KeyListener). There is a button on my GUI that has a listener attached to it. Inside of actionPerformed() I check if this the event was the openButton. If so then it should call the parseLog() method, which looks at all kinds of stuff in the log and prints out messages to the JTextArea object. Alternatively, pressing ctrl+o will simply create a new event and pass it actionPerformed() for the log to be parsed in the same manner.

I see now what is going on -- actionPerformed() is being invoked because some event occurred, and the EDT has to wait until parseLog() returns (and therefore actionPerformed() returns) before it actually prints the information to the JTextArea. So that part makes sense.


What still isn't clear to me is how to get this working properly. I tried your code:



but it clearly didn't work because I called invokeAndWait from the EDT -- and I got the exception that you were talking about. The tutorial seems to suggest that I need to a worker thread (javax.swing.SwingWorker) but I'm not exactly sure how to do this. I guess I need to wrap my call to parseLog() in a SwingWorker. Can someone help me do this? Here is the relevant code from actionPerformed():



I tried to update it to this:




But now I don't get any output at all. Not sure why but doInBackground() wouldn't let me return void so I just changed it to return Object and I return the empty string. This is clearly still not working (and the syntax also looks flat out bad). So I guess I still have 2 questions:

1. How can I wrap the call to parseLog() inside of a worker thread so that it kicks off to do it's thing and EDT returns from actionPerformed() to wait for the next action.
2. Assuming I do successfully create a worker thread out of this, do I still need to wrap the call to append() inside of a new Runnable object and then call invokeAndWait()?
 
B Mayes
Ranch Hand
Posts: 47
Android Eclipse IDE Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
GRRRR...Sun did not make this easy to do!!! I still do not have it working properly. I found the following page which I thought gave me more information:

http://java.sun.com/products/jfc/tsc/articles/threads/threads2.html


So I followed their example in ThreadsExample.java and tried this:



It does print output, but of course it waits until the call to parseLog() returns before it prints anything on the GUI. So I checked the API and found this:

http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/SwingUtilities.html

If you look at the javadoc comments for invokeLater() it says (and I quote):


If invokeLater is called from the event dispatching thread -- for example, from a JButton's ActionListener -- the doRun.run() will still be deferred until all pending events have been processed.



So then I went back to the first link above and it says:


The SwingWorker class is a simple utility for computing a value on a new thread. To use it, you create a subclass of SwingWorker that overrides the SwingWorker.construct() method to compute the value. You then instantiate the class and invoke the start() method on the new instance.


Ok fine, so I tried to change my code to do exactly that:



All it does now is complain that:

a) I need to override the inherited abstract method doInBackground(); and
b) that start() is undefined for the type SwingWorker



I defined doInBackground() but clearly that doesn't help since start() is still undefined. Can someone please tell me how to get this working? Usually the API and the examples give me enough information to figure things out but this is quite possibly to most confusing topic I have ever worked on. I just want to spawn a worker thread and then return back to the EDT so it can continue while the worker runs in the background. According to the link from post #2 (http://java.sun.com/docs/books/tutorial/uiswing/concurrency/index.html) this is the appropriate way to write a swing application, but I can't figure out how to do it!!

 
B Mayes
Ranch Hand
Posts: 47
Android Eclipse IDE Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Ok well I figured out why the start method was undefined and why my JRE complained that I needed to define doInBackground(). It looks like that article was written for an old, unofficial version of SwingWorker. Details can be found here:

http://en.wikipedia.org/wiki/SwingWorker

I hate to force users to run with a Java6 JRE but I guess I have to (or download the compatibility package). Let's see if I can get this working on Java6 first...

If anyone has any examples I'm still happy to see them.
 
B Mayes
Ranch Hand
Posts: 47
Android Eclipse IDE Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
FINALLY!!!


I got it working by following the example on wikipedia. Here is the final code, which works like a champ. Now I guess I just have to decide if I want to keep the current implementation, or download the compatibility classes for use with Java 1.5.






Thanks for pointing me in the right direction Rob!
 
Rob Spoor
Sheriff
Posts: 20822
68
Chrome Eclipse IDE Java Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
To improve your GUI update responsiveness, you can use publish - process to send updates to the GUI before the parseLog method finishes. In short, in doInBackground (please, keep it protected...) you call publish to send a newly read line to the process method. Just keep one thing in mind: publish may collect several "chunks" before process is being called.

For this you'll need to merge parseLog into the SwingWorker. It would be something like this:
Now your lines may show up in the text area even though the SwingWorker is still busy.


By the way, I suggest that Joe moves this thread to our GUI forum, as the problem is clearly more Swing related than I/O related.
 
B Mayes
Ranch Hand
Posts: 47
Android Eclipse IDE Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Yeah -- it should probably be moved.


I went ahead and made the doInBackground method protected. I don't think I am going to implement publish/process though (unless you can explain why it is a significant advantage). I have been testing things out quite a bit today and the GUI is now very responsive, and all of the messages are appended to the GUI in real time. When I write to both the console and the GUI I don't notice any lag between the two.

I even added a new worker thread that updates a status message every second while there is still a log file being actively parsed. Neat! Thanks again for all of your help.
 
Rob Spoor
Sheriff
Posts: 20822
68
Chrome Eclipse IDE Java Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
B Mayes wrote:unless you can explain why it is a significant advantage

If you're now using SwingUtilities.invokeLater or something similar then the only advantage would be not having to create the Runnables yourself.
 
  • Post Reply Bookmark Topic Watch Topic
  • New Topic
Boost this thread!