Java Swing GUI Threads/FoxTrot/SwingWorker/SwingUtilities

16 Oct

Java GUI’s:
(Summarising Ben Galbraith excellent tutorials on javalobby)

Native OS -> AWT Event Queue <- AWT Event Dispatch Thread

All GUI operations have to occur on the Event Dispatch Thread (EDT).
It happens that all EventListeners are also processed on EDT.

This means that if an EventListener has heavy code in it, the EDT slows down
and GUI operations (e.g. redraw screen, processes mouse click) slow down as
they remain in the AWT Event Queue (the EDT is busy processing your heavy code).
This will result in a 'laggy' interface for the user.

(
Generally, all operations modifiying REALIZED Swing components must take place
on the EDT, but there are some exceptions. These methods are Thread-safe:

JComponent.revalidate()
JComponent.repaint()
JComponent.invalidate()
JTextComponent.setText(String)
JTextComponent.replaceSelection(String)

)

Need Threads for a responsive GUI. A few ways to implement this.

0. Non threaded, laggy original:

dirButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
String directory = dirText.getText();
final File dirFile = new File(directory);
if (!dirFile.exists()) return;

DefaultListModel dirListModel = new DefaultListModel();
addFilesToModel(dirFile, dirListModel); // THIS IS THE LAGGY LINE
dirList.setModel(dirListModel); // THIS LINE UPDATES THE GUI
}
});

1. Use SwingUtilities for an asynchronous Thread
If you have an EventListener that you want to update the GUI, you have to
have the EDT do the GUI update (the EDT has to do ALL GUI updates!!!).

dirButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
String directory = dirText.getText();
final File dirFile = new File(directory);
if (!dirFile.exists()) return;

dirButton.setEnabled(false); // DISABLE TO PREVENT USER CLICKING IT AGAIN!

new Thread() {
public void run() {
final DefaultListModel dirListModel = new DefaultListModel(); // HAS TO BE FINAL AS PASSED TO INNER CLASS
addFilesToModel(dirFile, dirListModel); // THIS IS THE LAGGY LINE
SwingUtilities.invokeLater(new Runnable() {
public void run() {
dirList.setModel(dirListModel); // THIS LINE UPDATES THE GUI ON THE EDT
dirButton.setEnabled(true); // RE-ENABLE BUTTON
}
});
}
}.start();
}
});

2. Use SwingWorker for an asynchronous Thread (not part of JDK)
SwingWorker basically tidys up SwingUtilities, also with a good IDE you can just
refactor this out. Converting this anonymous inner class into an inner class which
can be moved around easily.


dirButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
String directory = dirText.getText();
final File dirFile = new File(directory);
if (!dirFile.exists()) return;

dirButton.setEnabled(false); // DISABLE TO PREVENT USER CLICKING IT AGAIN!
SwingWorker swingWorker = new SwingWorker() {
public Object construct() {
// NOTE !!! - THIS DOES NOT HAPPEN ON THE EDT, SO NO GUI OPERATIONS HERE
DefaultListModel dirListModel = new DefaultListModel(); // NO LONGER HAS TO BE FINAL
addFilesToModel(dirFile, dirListModel); // THIS IS THE LAGGY LINE
return dirListModel; // THIS CAN RETURN ANY OBJECT
}

public void finished() {
// NOTE !!! - THIS *DOES* HAPPEN ON THE EDT, SO ALL GUI OPERATIONS HERE -NO LAGGY THINGS
ListModel dirListModel = (ListModel) get(); // GET THE dirListModel RETURNED BY THE 'construct()' METHOD
dirList.setModel(dirListModel); // THIS LINE UPDATES THE GUI ON THE EDT
dirButton.setEnabled(true); // RE-ENABLE BUTTON
}
};
swingWorker.start();
}
});

3. Use FoxTrot for a synchronous Thread
GOOD TO RETROFIT APPLICATIONS WHEN YOU DON’T HAVE PROPER THREADS.

FoxTrot is synchronous, it has access to the Event Queue and can process events in it.
In the following code it looks like the “Worker.post()” call should block and hang waiting
for the “addFilesToModel()” method to return. But in reality FoxTrot is spawning a background
thread to process the commands in its “run()” method. It then monitors the background
thread and waits for it to return, in the meantime it processes anything in the Event
Queue, keeping the GUI responsive. (It can do this because the EDT is the thread that
executes the Listener itself AND FoxTrot’s “post()” method, hence it can access the Event Queue).


dirButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
String directory = dirText.getText();
final File dirFile = new File(directory);
if (!dirFile.exists()) return;

dirButton.setEnabled(false); // DISABLE TO PREVENT USER CLICKING IT AGAIN!

ListModel dirListModel = (ListModel) Worker.post(new Job() {
public Object run() {
DefaultListModel dirListModel = new DefaultListModel();
addFilesToModel(dirFile, dirListModel); // THIS IS THE LAGGY LINE
return dirListModel; // THIS CAN RETURN ANY OBJECT
}
});

dirList.setModel(dirListModel); // THIS LINE UPDATES THE GUI ON THE EDT
}
});

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: