Popular UI libraries like Swing or SWT are not thread-safe. They use a specialized thread, the so-called event dispatch thread that is the only instance which is allowed to interact with UI components. This thread also processes all user input. Because the JGUIraffe library is implemented on top of these libraries similar restrictions apply.
Naive UI programming often uses event listeners that are registered at UI components like buttons or menu items and implement the corresponding functionality. This approach is problematic for multiple reasons:
The JGUIraffe library supports a different programming model. Here logic is implemented in so-called command objects.
Command objects are an application of the command pattern
described by Gamma et al. They are implemented by classes that adhere to a
specific interface. When the user interacts with the application command
objects are created and passed to the central
Application
object to be executed. Application
maintains a single worker thread that executes the commands passed to it
one after another. After their execution in the worker thread commands get
the chance to update the UI in order to display the results of their
execution. This is automatically synchronized with the special event
dispatch thread.
So the concept of commands is pretty simple. Nevertheless it has a number of advantages:
The JGUIraffe library defines an interface for command objects:
Command
. This interface defines the following methods:
execute()
is the main execution method of the command. This
method is invoked in a background thread. It contains the main execution
logic of the command, however, from this method no GUI updates are allowed.
execute()
does not expect any parameters, but it can throw an
exception.execute()
method are caught by
the executing task. If an exception is detected, the onException()
method of the command object is invoked (also in the background thread). So
this is the place for error handling logic.onFinally()
is the last method invoked on the background
thread. As the name implies, this method is always invoked - whether an
exception was thrown or not. An implementation may for instance free
resources used by the command.Command
interface defines
the getGUIUpdater()
method. getGUIUpdater()
can
return a Runnable
object, which is then executed in the event
dispatch thread. Therefore it can access all UI components and perform
arbitrary updates.
As can be seen, the Command
interface is slightly more complex.
There is not only a single execute()
method, but the interface
contains also methods for handling exceptions. To simplify the
implementation of custom command classes the library provides an abstract
base class for commands:
CommandBase
. CommandBase
provides an implementation
of onException()
that simply logs the exception and stores it
in a member field. (It can be queried using the getException()
method.) There is also an empty default implementation of the
onFinally()
method. Concrete sub classes mainly have to
define the execute()
method to implement the actual logic.
CommandBase
also supports UI updates. It provides the
protected performGUIUpdate()
method in which code that needs
to access the UI can be placed. The base class ensures that this method is
automatically called in the event dispatch thread after execution of the
command. Whether UI updates should actually be performed can be specified
when a CommandBase
object is created: The constructor can be
passed a boolean flag which determines whether the
performGUIUpdate()
should be called. Commands that do not need
UI updates should call the super constructor with the parameter false,
then this method is skipped.
Now that we have introduced the API of command objects let's implement an
example command. The command should read the content of a directory and
update the model of a table component to display this data. (Because table
components have not been discussed so far, we have to give some notes to
make the example understandable: A table can be accessed through the
TableHandler
interface. This interface provides access to the
table's model which is simply a list with data objects. To update the
table's content we can fill new data objects into this list and then notify
the table that its model has changed. For this example we put the
java.io.File
objects directly into the table model and assume
that the table was configured to display their properties.)
Our command class extends CommandBase
, the abstract base class
for command objects. It needs some parameters to fulfill its task which are
passed to the constructor:
java.io.File
object for the directory to be read.TableHandler
reference representing the table to be
filled.public class ReadDirectoryCommand extends CommandBase { /** The directory to be read. */ private final File directory; /** The table handler. */ private final TableHandler tableHandler; /** The list with the files read. */ private List<File> files; public ReadDirectoryCommand(File dir, TableHandler handler) { directory = dir; tableHandler = handler; }
This code simply stores the arguments passed to the constructor in member
fields. The class also defines a list field for the files found in the
current directory. This field is filled by the execute()
method which is shown in the following fragment:
@Override public void execute() throws Exception { files = new ArrayList<File>(); // add the content of the directory to the list files.addAll(Arrays.asList(directory.listFiles())); // do some further manipulations, e.g. sort the list or apply a filter }
After execute()
terminates the data managed by this command is
stored in the files
list. For this example command we do not
implement any exception handling logic. Thus we can live with the default
implementations of onException()
and onFinally()
.
However, the command needs to update the GUI. This is done in the
performGUIUpdate()
method which is automatically called after
background execution is complete. In our implementation we have to add the
files read by execute()
to the model of the table. This can
look as follows:
@Override protected void performGUIUpdate() { List<Object> model = tableHander.getModel(); // first clear the model model.clear(); // Now add the new files for (File f : files) { model.add(f); } // Notify the table about the change of its model tableHandler.tableDataChanged(); }
So far the complete implementation of the command class. Executing this
command is easy: A new instance has to be created and passed to the
execute()
method of the central
Application
object. If done by hand, this could look as follows:
File dirToRead = ...; TableHandler table = ...; ReadDirectoryCommand cmd = new ReadDirectoryCommand(dirToRead, table); application.execute(cmd);
Note: Typically the developer does not have to care about the creation and execution of command objects. Rather, this is done behind the scenes by the framework in reaction of user actions. We discuss this later in this guide.