SwingCommand part 2 – TaskListeners
A common requirement in User Interfaces is to show an animation while a background task runs. How might this be accomplished using SwingCommand?
The following example shows how you can add a TaskListener to a command. The TaskListener will receive events as the command executes, and start and stop an animation.
TaskListeners always receive their events on the Swing Event thread, even if the Task is a BackgroundTask.
LoadMessagesCommand command = new LoadMessagesCommand("My User");
TaskListener animationListener = new TaskListenerAdapter() {
public void pending(Task task) {
startProgressAnimation();
}
public void finished(Task task) {
stopProgressAnimation();
}
};
command.addTaskListener(animationListener);
command.execute();
}
Every time the command is executed, the SwingCommand creates a new Task instance, and the TaskListener will receive events while the Task runs.
It’s also possible to add a TaskListener to receive events for a single SwingCommand invocation, by passing it as a parameter to the execute() method:
//this listener will not receive events on any subsequent executions:
LoadMessagesCommand command = new LoadMessagesCommand("My User");
command.execute(new TaskListenerAdapter() {
public void error(Task task, Throwable error) {
System.err.println("Oops, perhaps our database is offline");
}
});
The ability to add TaskListeners to SwingCommands is a key feature of SwingCommand, and it can be very convenient. For example, you could create several different commands and add a shared listener which tracks the background tasks and updates progress components on your UI. This listener will receive events on the Swing Event thread whenever one of the commands is executed, no matter which thread or which class actually executes it.
Separation of concerns
TaskListeners are a very useful mechanism. They allow you to separate the logic for handling progress animations, and error handling logic, from the main Task processing logic. You could, for example, provide a TaskListener which performs consistent error handling for all of the commands in your application. This is a good separation of concerns. Mixing that logic in with the main command processing logic tends to clutter the code up and make classes less easy to reuse – and there are certainly cases where you might want different error handling depending on the situation in which the SwingCommand is executed (e.g. if it’s executed in response to a button click in a Dialog, you may want to show an error message in the Dialog, rather than the main UI frame). To achive this, you can add some listeners to the SwingCommand, which will get notified whenever it is executed, and others context specific listeners can be passed as parameters to SwingCommand.execute().
The TaskListener interface
There are actually six listener methods in the TaskListener interface. Some of these are always invoked, others are only invoked if a Task enters a specific state (ERROR or SUCCESS) for example. To avoid having to implement all the methods, you can extend TaskListenerAdapter.
The full definition of the TaskListener interface is below:
* Implement this interface to listen to the progress of a task
* Callbacks on this interface are guaranteed to be received on the AWT event thread,
* even if fired by an BackgroundTask
*
* These callbacks provide an easy and safe way to update the UI to show the progress of a task.
*
* <P> - the type of Progress object that this TaskListener will receive
*/
public interface TaskListener<P> {
/**
* The callback to pending is triggered when command.execute() is called
* If the Executor running the task queues the task or blocks waiting for
* a thread, it may be some time before started() is invoked.
*/
void pending(Task task);
/**
* Called when the task starts processing. For BackgroundTask this callback
* takes place just before doInBackground is called. For SimpleTask just before doInEventThread
*/
void started(Task task);
/**
* This callback may take place at any time during task execution, to indicate progress
*/
void progress(Task task, P progress);
/**
* Called when the task reaches the SUCCESS end state
* This callback takes place once a command has successfully completed (was not cancelled,
* and did not throw an Exception resulting in a callback to error())
*/
void success(Task task);
/**
* Called when the task reaches the ERROR end state
* This callback takes place if an exception is raised during task execution, which prevents
* successful completion
*/
void error(Task task, Throwable error);
/**
* Called when the task reaches the CANCELLED end state
* This callback takes place if the task is cancelled.
* @param task
*/
void cancelled(Task task);
/**
* This callback always takes place once the task has finished, whether or not the
* task finishes in ERROR, SUCCESS or CANCELLED states
*/
void finished(Task task);
}
You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.
Leave a Reply