views:

191

answers:

2

I am using an API in a Java library which is called from the event dispatch thread and requires me to return a fully-initialized UI component. It looks like this:

public JDialog createDialog();

But I can only populate the dialog after loading from a database, which can take 10 seconds sometimes. Normally I would do that in a background thread but since this method is called from the EDT and since I have to return the dialog, that won't work. It's a third-party library so I can't change the method, but is there anything I can do to avoid blocking the EDT?

+1  A: 

Construct the dialog without data, and then start a task to populate it.

From a user-experience perspective, anything that takes 10 seconds from initiation to completion is going to be an issue. It's best if you should them something right away, even if it's not in final form. If necessary, you could pop a modal dialog that simply says "Loading."

kdgregory
According to the documentation for this library, it must be fully-populated when I return it (sorry if I was not clear on that point before). I don't know what will happen if I populated it afterwards, but I'd guess it would be bad since they specify that it must be fully populated. Other documentation for that package seems to indicate that I should load the data first, then populate the dialog and return it, but I don't know how to do that without blocking the EDT.
Ed Frommer
*Scott Fines* gave you a good answer: kick off the background process to load the data, then at the end of that process create your dialog (back on the EDT). And so that the user isn't wondering what's happening, display a temporary dialog while you're retrieving the data.
kdgregory
+3  A: 

"Initialized" is not necessarily the same thing as "Populated". "Initialized" usually means that the object has been fully constructed, but may not have any data. "Populated" of course means that the data is present and any data-fetching tasks are complete. So it is possible to give your third-party library a fully initialized JDialog without any data at all.

The way I always like to solve this problem is to create a custom JDialog which displays a busy message or a progress bar or the like, and then request the data in another thread. When the data is returned, I replace the busy message with the data(On the EDT!). As to how you should perform your request in a background thread, I recommend using SwingWorkers. I like to use a private SwingWorker inside my custom JDialog which handles the request in the doInBackground() method, and handles the Display-related tasks in the done() method. Doing it this way will ensure that display-related tasks only occur on the EDT, and database-related tasks only occur OFF the EDT. If you'd like a reasonably good introduction to using SwingWorkers, check out Sun's tutorial on worker threads. A simple example would be:

public class DBDIalog extends JDialog{
     private JLabel busyLabel = new JLabel("Fetching data from DataBase");

     public DBDialog(){
         //do your initialization stuff here
     }

     private class DBFetcher extends SwingWorker<Void,DBInfo>{

        @Override
        protected DBInfo doInBackground() throws Exception{
            return fetchDataFromDB(); //or whatever database call to make
        }

        @Override
        protected void done(){
           try{
               DBInfo info = get();
           //replace your busy label with your DBInfo
           }catch(InterruptedException e){
              //do appropriate thread interrupted stuff
           }catch(ExecutionException e){
              //do appropriate general error handling stuff 
           }

        }
     }
}

A few things to remember, though: the done() method is NOT abstract, so you aren't required to override it. You should, though. If your doInBackground() implementation throws an exception, that exception will be swallowed unless done() has been overridden. Also, don't make changes to your GUI from inside the doInBackground(), unless you use SwingUtilities.invokeLater(Runnable), as doInBackground() is executed from a different thread than the EDT and making GUI changes from a background thread is asking for strange and inexplicable bugs.

When should this be used? Unlike other programming tasks, the point at which something takes too long to respond is a lot shorter in GUIs--The number I've usually seen written down is about 250ms. If your task takes longer than that, it should be in a background thread. In your case, 10 seconds should definitely be in a background thread, but then you already knew that :)

EDIT:

Seeing your comment, I see that most of my post is pretty moot. However, you can still use a SwingWorker:

Have your SwingWorker perform the data-retrieval, and in the done() method, have it construct the JDialog from the data and hand that dialog to your third-party library.

Scott Fines
That last part about "have it construct the JDialog from the data and hand that dialog..." is what's difficult. If my method is called on the EDT, how can I run something in the background and then wait for it to return (so I can construct and return the dialog) without blocking other events posted on the EDT between when I am called and when I return? It seems like maybe SwingWorker does not solve that. Nevertheless, yours was the better of the two answers.
Ed Frommer
You can override `process()` to display intermediate results and/or call `setProgress()` as required. Here's an example: http://sites.google.com/site/drjohnbmatthews/randomdata
trashgod