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:
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:
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.