All,

I am working on my first forms app (using NetBeans). I don't like to mix GUI code with app code, so I have my main class, GUI class and core code class nicely seperated. When the user clicks a button on the GUI form it calls a method in the core code which returns a value that the GUI displays. So far, so good, but...

I have a method that processes a file and it takes a long time. I'd like that method to update the GUI as it's working so the user doesn't think the program is hung up. I'd like the user to see something like this in a jTextArea on the GUI:

Opening file xyz.csv.
Reading cross-correlated data points.
Removing spurs.
Calculating offset deviations.
Interpolating target temperatures.
Writing file xyz_clean.csv.
Done processing.

While all of this is going on how does the GUI get updated? Is it possible for the processing method in my core code to write directly to the jTextArea on the GUI?

I'm open to all suggestions and I'd love to know how an experienced Java programmer would keep the user informed. What is the best practice for situations like this?

Thanks,
Bill

Obviously you can pass a reference to the text area into the processing code and thus update it directly, but that would totally undermine your correctly-engineered architecture.
Or you could use the "observer" pattern and enhance your processor code to allow you to add a ChangeListener to it. Then you could update any/all listeners while processing without needing to know anything about the listeners. This is exactly the approach used throughout Swing, and is definitely "good" architecture.
Or you could use the SwingWorker class which was designed for exactly this purpose. It may take a few reads of the API doc to fully understand which methods execute on which thread, and how exactly they are related. but once you got that it's a really clean solution.
http://docs.oracle.com/javase/tutorial/uiswing/concurrency/worker.html
http://docs.oracle.com/javase/7/docs/api/javax/swing/SwingWorker.html

Obviously you can pass a reference to the text area into the processing code and thus update it directly, but that would totally undermine your correctly-engineered architecture.

Exactly!

Or you could use the "observer" pattern and enhance your processor code to allow you to add a ChangeListener to it. Then you could update any/all listeners while processing without needing to know anything about the listeners. This is exactly the approach used throughout Swing, and is definitely "good" architecture.

I'm not sure what that is, but I'll look into it.

Or you could use the SwingWorker class which was designed for exactly this purpose. It may take a few reads of the API doc to fully understand which methods execute on which thread, and how exactly they are related. but once you got that it's a really clean solution.

That sounds like what I'd like to do. The SwingWorker class definitely makes sense to me, but what I still don't understand is how a worker thread in my app code class updates the GUI which is another class.

I have a good feeling for what happening in the example Flipper.java (found here) as far as threading goes, but since all of that code is bundled together in one class I don't understand how to deconstruct Flipper into individual classes and still have it work.

Is there a better example somewhere? BTW, the other example, which is VERY close to what I want to do is ProgressBarDemo.java (here), but it has the same problem as Flipper--all the code is bolted together in one class.

Am I overlooking the obvious?

Thanks,
-Bill

SwingWorker makes your life easier by linking the process to the GUI update in one place then doing all the complex thread-related stuff for you. But that also means it's a fair criticism to say that this one place violates the separation of GUI and model. That makes me uneasy too, but I guess that's a personal preference.

In my code I prefer to use Observer so there is no knowledge of the GUI in the model itself. I'm a bit of a purist like that. Here is some relevant reading (!)
http://www.vogella.com/articles/DesignPatternObserver/article.html

In its very simplest form you add something like this to your process class

private ChangeListener listener = null;

public void addChangeListener(ChangeListener listener) {
   // just supports one listener to keep this really simple
   this.listener = listener;
}

void notifyChangeListener() {
   // call this whenever data is changed
   if (listener != null) 
      listener.stateChanged(new ChangeEvent(this));
   }
}

Then in the GUI somewhere you implement the ChangeListener interface (ie the public stateChanged method) and add the GUI instance to the process instance by calling its addChangeListener(this); Now whenever something interesting happens in the process you call notifyChangeListener, and the GUI's stateChanged method can update the GUI.

James,

Thanks for the great info. I would have never figured this out on my own. I have a bit of experimenting to do now...

Thanks again,

-Bill

You may want to have a look at the standard JavaBean support for change listeners - it gives you all the support for multiple listeners etc for free - see under "bound properties" here and here
In your case you could simply make the currentProcessingStatus String a bound property.
If you are just updating a JLabel with the current status, then you don't need to worry about which thread you are on - JLabel's setText is thread-safe. If you are doing anything more complex in the GUI then you should insert an invokeLater somewhere in the event listener to ensure that GUI updates are done on the Swing thread.
Good luck!
J

Thanks again, James. Tell you what, I love writing console apps because all I have to worry about is the actual code. This GUI stuff takes a lot of work, knowledge, and time, and I feel like, 1) I'm not really sure of what I'm doing and 2) I'm not going to master it easily.

I really miss the DOS prompt :)

-Bill

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.