Simple SwingWorker example

As promised this is the first release of a SwingWorker example. Source code is available under the LGPL (see INSTALL.txt for installation instructions). This is a simple example of a two-tier application that queries a PointBase database and searchs customers by name.

Note: I can't remember if Pointbase comes bundled with NetBeans 4.1 or if you need the J2EE 1.4 JDK installed instead. If you don't have Pointbase with your NetBeans 4.1 installation then just install J2EE 1.4 JDK available for free here. For details on how to run PointBase use this link. As Geertjan suggests you may consider using MySQL instead although that'll require, of course, some SQL and setup modifications.

Purpose of this demo

On this very first release I have concentrated on all but SwingWorker, I'm afraid. I just wanted to build a sound base that I can improve in the future. Today I'll describe a little bit this simple example and in next releases I'll enhance the SwingWorker in different ways, including timeouts, single executions, cancellations and progress monitors.

The domain package

In the example I've included a "domain" package that contains some weird things, such as a Customer Transfer Object, a Customer DAO and a Customer Business Delegate. Let me explain:

  • CustomerTO is a transfer object I use to hold information about customers. This is a simple Serializable Java Bean (a POJO: Plain Old Java Object).
  • CustomerDAO is a Data Access Object. This concentrates all SQL of the examples. For this example this just has a simple "findByName" query.
  • CustomerBusinessDelegate is a Business Delegate. I use business delegates to delegate operations to DAOs, and to handle different DAOs in a single database transaction. On this simple example I just call CustomerDAO.findByName and I do that in a transaction. Although transactions are not required for query methods I've decided to include transaction handling, so you can see how to handle these.
Many of you will recognize these as J2EE design patterns. I like using these even on my Swing applications. Having all business methods concentrated on the BusinessDelegate allows me to move the examples from a two-tier to a three-tier architecture in the future (using EJBs, for instance) in case of need. That's why I've included this stuff in a standalone package.

TableModels

I've included a quick & dirty implementation of TableModel that uses generics to hold an ArrayList of transfer objects. I've called that AbstractTOTableModel. That allows me to quickly derive specific implementations quite fast, such as CustomerTOTableModel.

AbstractTOTableModel is not very well designed (it fires "tableChanged" events to listeners although a single row is changed) but is good enough for this example. The important part for me is that building CustomerTO table models is very fast with this AbstractTOTableModel background. Furtheremore, I can play with generics (and that's something I still need ;-) )

You'll notice that AbstractTOTableModel<TransferObject> uses internally an ArrayList<TransferObject> to hold transfer objects. And, of course, you know that ArrayList is not thread safe. The fact is that this is not important in the example, because I'll follow this convention:

Convention I All models (in the MVC paradigm) will be modified in the Swing thread.

This is a convention I usually follow when building Swing applications. I consider that handling models with different threads is just asking for trouble. After all there's no need to do that, as we'll see.

FindCustomerByNameSwingWorker

Well, this is the interesting part. I'll describe it in detail below, method by method, (sorry if this is getting too long!).

Class declaration

public class FindCustomerByNameSwingWorker
extends SwingWorker<CustomerTOTableModel,CustomerTO>
{
We're declaring a SwingWorker that will return a CustomerTOTableModel and that will generate intermediate results in the form of CustomerTO's (we'll see that in detail below).

Constructor

public FindCustomerByNameSwingWorker( String aName,
 CustomerTOTableModel aCustomerTOTableModel )
We pass as arguments the name of the customer we want to search (or null if we want to search all customers) and a table model. We'll be incrementally inserting CustomerTO's into the TableModel as the search progresses. And, as stated earlier, we'll do those model modifications in the Swing thread.

doInBackground

@Override
protected CustomerTOTableModel doInBackground()
  throws Exception
{
As you know this is the method that is invoked in the worker thread. This is \*not\* executed in the Swing thread. What we basically do in this method is this:
  • We invoke the business delegate to fetch all customers. Then, for each customer:
    • We update the progress of the computation (automagically generating events into the Swing thread).
    • We publish intermediate results (automagically generating events into the Swing thread).
    • We sleep things a bit, so as to simulate a slow computation.
  • Finally we return the results and perform any post processing.
Invoking the business delegate
CustomerBusinessDelegate cbd = new CustomerBusinessDelegate();
ArrayList customers = cbd.findByName( customerName );
Nothing very interesting here, right? ;-) We just fectch a collection of customers from the database. Then, for each customer in the collection:

We update progress

setProgress( (int) progress );
As you probably know, SwingWorkers generate propertyChangedEvent's to registered PropertyChangeListeners (you can add and remove PropertyChangeListeners to any SwingWorker by using the "addPropertyChangeListener" and "removePropertyChangeListener" methods). In our example we don't use this feature, but we update progress anyway (we'll use that in future enhancements). At the moment just remember that we can update progress. That's good enough at the moment.

We publish intermediate results

For each CustomerTO in the collection we "publish()" it (an intermediate result, after all) to the event dispatch thread. Remember that we declared FindCustomersByNameSwingWorker as extending SwingWorker<CustomerTOTableModel,CustomerTO>? Well, the second argument to that generic declaration states that our intermediate results are CustomerTOs.

publish( customers.get(i) );
Invoking the "publish()" method will in turn invoke the "process" method in the Swing thread. This is, if you "publish" an intermediate result then you may "process" it in the Swing thread. In our case we process each intermediate CustomerTO by updating our table model, like this:
@Override
protected void process(CustomerTO... chunks)
{
  for( CustomerTO customer: chunks )
  {
    customerTableModel.add( customer );
  }
}
This is, all intermediate CustomerTO's are "publish()'ed" in the worker thread and then "process()'ed" in the Swing thread. In our case we just update our table model (so we modify the table model in the Swing thread).

Sleeping

We simulate slow processing by Thread.currentThread().sleep(100L).

Finishing and post processing.

We may process any results in the "done()" method. In our case this is not neccessary because in the example the modifications to CustomerTOTableModel are shown automagically in a JTable (a modification to a TableModel makes the corresponding JTable to be automagically updated).

Executing the SwingWorker

Finally the example includes a simple CustomerFinderPanel that exercises the whole thing by invoking the "execute()" method of the SwingWorker.

Enough for today

Well, I think this is too long for today. I don't want to overflow you on a friday ;-). To summarize these are the important things I'd like to transmit today:

  • SwingWorker<A,B> returns a result of type A and may publish one or more intermediate results of type B.
  • SwingWorkers have a "doInBackground()" method that is processed in a worker thread.
  • SwingWorkers have a "publish()" method that publishes intermediate values in the worker thread, that in turn are "process()" ed on the Swing thread.
  • SwingWorkers have a "setProgress()" method that generates events on the Swing thread.
  • SwingWorkers may be executed by using the "execute()" method.

On future releases about SwingWorkers I'll talk about exception handling, SwingWorker state change events, SwingWorker progress events, cancelling SwingWorkers and allowing a SwingWorker to gracefully die after a timeout period.

As always all questions/suggestions are welcome.

Have a good weekend and keep on Swinging,
Antonio

Comentarios:

Have you ever thought about combining the "scheduler" concept (Quartz) with a Swing GUI? I think a scheduler is a functional "superset" of the "Swingworker" concept! It would be nice to do some jobs in the background (polling mails for example) and some foreground tasks "freeze" the Swing GUI as long as the work is done. There also must be a way to cancel "hanging" jobs. A kind of Swing "taskmanager" like in windows would be nice for that. Sounds interesting?

Enviado por Michael en agosto 03, 2005 a las 05:51 AM CEST #

Hi Michael,

I think that Somnifugi has some very interesting concepts for Swing threading (probably better than Quartz, I think). The fact is that Somnifugi is way too complex to deal with when building GUIs. A combination of that with some sort of Task Manager is the way to go.

And, yes, a TaskManager for SwingWorkers is one of my future ideas. (I don't have enough spare time, tough). I'll do my best.

Enviado por Antonio en agosto 15, 2005 a las 10:36 AM CEST #

Great post Antonio, thanks. About the question of Michael, a task manager (memory only) is already done, it is Java2D demo at jdk1.5.0_04/demo/jfc/Java2D/Java2Demo.jar.

Enviado por Claudio Miranda en agosto 17, 2005 a las 08:50 PM CEST #

Enviar un comentario:
Los comentarios han sido deshabilitados.
About

swinger

Search

Archives
« abril 2014
lunmarmiéjueviesábdom
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    
       
Hoy