The tutorial application

In the source code repository, next to the code of the actual library, there is a module with sample code. It is named examples. The major part of this module consists of an example application demonstrating many of the features offered by the JGUIraffe library. This section describes the tutorial application. It is intended to be an extension of the user's guide. While the user's guide provides a whole lot of background information, this document is more focused on practical things. It can also be used to get a quick jump start into development with the JGUIraffe library.

Features of the tutorial application

The tutorial application is not designed to be used as a productive tool. Rather, its goal is to demonstrate typical programming practices working well with the JGUIraffe library. This manifests itself in the fact that many features - especially "dangerous" operations like removing files - have not been implemented. As problem domain a file system browser application was chosen. The following screenshot shows the main window of the application:

Main window of the tutorial application

The application follows typical UI conventions of directory browsing tools. The main panel (below the menu and the tool bar) is split into two areas. On the left hand site there is a combo box allowing the user to select a root file system. On Windows systems it contains the drive letters available. Below the combo box there is a tree view component displaying the directory structure of the file system currently selected. On the right hand site there is a table showing the content (the sub directories and files) of the directory selected in the tree view on the left.

From the tool bar or the main menu some operations can be triggered to do something with the file(s) selected. The availability of these operations depends on the current selection. For instance, a group of actions is active only if a single file is selected. When selecting the View settings... menu item from the Extras menu a dialog window pops up allowing the definition of view-related properties for the current directory:

Dialog window with view settings

Using this dialog window many details of the display of the current directory can be specified, for instance colors, the sorting of the files and sub directories, and filter conditions. The settings entered in this dialog window are saved in a file in the directory so that they can be applied whenever the user navigates to this directory.

An action File new available from both the main menu and the tool bar allows the creation of a new file. It displays a dialog window in which the user can enter a file name and text content of the file. Closing this window with the OK button creates such a file in the current directory. This dialog window and the builder script which defines it is also subject of the chapter A complete builder script of the user's guide.

There is another action which demonstrates dealing with long-running operations: If Extras / Long operation is selected, a dialog window appears which allows the configuration of a dummy background task - mainly the duration of the task in seconds can be entered using a slider control. When the dialog window is closed the task runs in the background (it does nothing useful), and another window is displayed showing the progress of the task.

How dialogs for selecting files are displayed is demonstrated by the Extras / Select and open file action. The action displays a standard file chooser dialog. When the user selects a file, it is opened in the application registered for this file type.

Quick start

A JGUIraffe application requires a minimum set of files in order to successfully launch it and to configure the framework. Of course, these files are present for the tutorial application, too. We provide an overview over these files here. This is especially useful for the impatient readers, so they can start with their own experiments.

Each Java application needs a startup class, i.e. a class containing a main() method. A JGUIraffe application does not strictly require a custom startup class because the Application class comes with a main() method which can be used out of the box. However, providing an application-specific startup class can simplify some things, e.g. the definition of launch configurations in an IDE. If the application supports command line arguments, a custom startup class is also required. For these reasons the tutorial application comes with its own startup class, but with a very simple one. It is derived from Application and just calls the corresponding method to get the framework running:

package net.sf.jguiraffe.examples.tutorial;

import net.sf.jguiraffe.gui.app.Application;
import net.sf.jguiraffe.gui.app.ApplicationException;

public class Main extends Application
{
    /**
     * The main method of the application. Starts the whole application.
     * @param args command line arguments
     */
    public static void main(String[] args) throws ApplicationException
    {
        startup(new Main(), args);
    }
}
    

You may wonder how the framework knows which main window to display. The startup class does not contain any information about this. The answer is that this information is obtained from the application's configuration. JGUIraffe uses Apache Commons Configuration to manage configuration data. So a configuration file compatible with this library has to be created. Actually, two configuration files are needed: One file is a description file listing several configuration sources. This file is named config.xml per default and is searched in the class path. The other file contains the actual configuration of the JGUIraffe framework; it is referenced from the first file. The config.xml file looks as follows for the tutorial application:

<?xml version="1.0" encoding="ISO-8859-1"?>

<configuration>
    <system config-name="system-config"/>
	<xml config-name="userConfig" fileName="${user.home}/.jguiraffe-testappconfig.xml"
      config-optional="true" config-forceCreate="true"/>
	<additional>
		<xml fileName="framework-config.xml"/>
	</additional>
</configuration>
    

Here, in the section wrapped by the <additional> tags the actual configuration file for the framework - in this case named framework-config.xml - is defined. The other elements integrate some other configuration sources (which are useful, but not required for an application):

  • The <system> element makes Java system properties available to the configuration system.
  • This is used by the following <xml> tag which defines an XML configuration file named .jguiraffe-testappconfig.xml located in the home directory of the current user. In this file data specific to a user can be stored; for instance, the framework stores here the position and the size of the main window, so on the next start of the application these properties can be restored.
The configuration of the framework is stored in the framework-config.xml file. Here also a minimum set of properties must be defined so that the framework knows what to do:
<?xml version="1.0" encoding="ISO-8859-1"?>
<config>
  <framework>
    <builder>
      <mainScript>main.jelly</mainScript>
      <beandefinitions>
        <beandefinition>tutorial-beans.jelly</beandefinition>
      </beandefinitions>
    </builder>
    <appctx>
      <defaultResourceGroup>tutorial-resources</defaultResourceGroup>
    </appctx>

    <storeuserconfig>true</storeuserconfig>
    <userconfigname>userConfig</userconfigname>
  </framework>
</config>
    

The most important piece of information is the name of the builder script defining the application's main window. It is defined by the <mainScript> element. The other elements configure some useful but optional functionality of the framework. We give a short overview over these elements:

  • When starting an application an arbitrary number of files with bean definitions can be read. The beans defined in these files can then be queried from the application's global BeanContext. This is useful for instance for central services. For each bean definition file to be read a <beandefinition> element has to be added.
  • If an application uses localized strings, it can define the default resource group using the <defaultResourceGroup> element. The default resource group is used for each resource lookup if no specific group is provided.
  • The remaining elements define the so-called user configuration. This is the already mentioned configuration source in which user-specific data is stored. Here we specify that this mechanism should be enabled (<storeuserconfig>true</storeuserconfig>) and which configuration source to use for this purpose. The name of the configuration source is passed to the <userconfigname> element and must match a configuration source which was defined in the main configuration description file config.xml.

This is basically all what is needed for a working JGUIraffe application. Well, of course, the main builder script referenced by the framework configuration file must also exist; this is highly specific to a concrete application.

Applications that use resources to translate texts will also ship with a set of resource properties files (provided that the default resource manager implementation is used which obtains resource texts from properties files). The tutorial application belongs to this group of applications. Its base resource bundle is named tutorial-resources.properties. (Note that the name matches the default resource group specified in the framework configuration. In the case of the default resource manager a resource group corresponds to a resource properties file. The properties file for the default language does not have an extension; the file with the German translations is named tutorial-resources_de.properties.)

Packaging

Before we start with the actual exploration of the tutorial application we will have to say some words about its package structure. There are multiple reasonable ways of packaging a UI application. For instance, classes could be grouped together in packages by their type. So you would end up with packages for model classes, controller classes, view classes, helper classes, etc. This is a valid packaging scheme, and it can be applied to JGUIraffe applications.

The tutorial application uses a different approach. It groups its classes by functionality. This means that there are packages related to the main UI elements (the main frame and the dialog windows) used by the application. (There is also a package with shared classes used by multiple windows.) This way of packaging is also suitable for JGUIraffe applications. The programming model of this framework typically leads to multiple small classes implementing specific functionality like event handling or task execution. View classes are not needed because views are defined in builder scripts. In many cases you do not even have a controller class because the default implementation of the framework is sufficient. So it makes sense to bundle the classes that implement logic for a specific window in a common package. By the way, these small and focused classes are much easier to unit test than large, monolitic view classes with lots of nested anonymous event listener classes.

The main frame

The classes related to the application's main frame are all located in the net.sf.jguiraffe.examples.tutorial.mainwnd package. In addition to the main controller class, MainWndController, the package contains a bunch of helper classes, e.g. for processing events or for serving as model objects for UI elements. The frame window itself is constructed from the builder script main.jelly which can be found in the src/main/resources directory. (This is the maven default directory for non-Java files which have to be added to the generated jar. Here all builder scripts, configuration files and resource bundles are stored.)

MainWndController is responsible for most of the logic of the main frame window. This is different from typical controller implementations for dialog windows: a dialog window is mainly concerned with gathering data from the user and processing the results. The application's main frame window serves a different purpose. Therefore the controller class is very specific to the logic of the application. It is not associated with the frame in the usual way, and it does not extend the controller base class defined by the JGUIraffe framework (which is focused on dialog windows). The association between the controller object and the UI is done by the dependency injection framework. The controller is passed references to all UI elements it has to interact with. Analogously, all helper objects (e.g. event listeners) which have to call back the controller are passed a reference to it. The helper objects are very simple: they store a reference to the controller and call a corresponding method when they are triggered. Thus the view logic for handling the interaction between the UI elements can be implemented centrally in the MainWndController class.

The main.jelly builder script is a bit more complex than scripts defining dialog windows because it is also responsible for the application's main menu, its tool bar, and the actions representing menu items and/or tool bar buttons. These action declarations make up a large section in the builder script. Another big part defines the UI of the window: the combo box for the file system, the tree view with the directory tree, and the table with the current directory's content. The remaining parts define helper objects and plug them together.

MainWndController is notified when the selection of the combo box with the root file systems changes. This is done by the FileSystemChangeListener class. In response of this event the controller has to update the tree view so that it shows the content of the newly selected file system. As the tree view's model a HierarchicalConfiguration object from the Apache Commons Configuration library is used. A configuration node in this model object represents a node that is displayed by the tree view. The controller stores the configuration nodes associated with root file elements. So it can detect whether it already has loaded data for a file system or whether it has to load this data now. In any case the model of the tree view is manipulated so that it points to the data of the newly selected file system.

The controller also has to be notified about changes in the selection of the tree view. When the user navigates the tree the corresponding directory has to be displayed in the table. Because a whole file system can become pretty huge, the controller does not load it at once. Rather, only a single layer (a directory with its files and subdirectories) is loaded. If a directory is selected whose content has not yet been loaded, the controller executes a command of type ReadDirectoryCommand in the background. The command reads the content of the directory and performs the corresponding sort and filter operations. On completion of the command the model of the tree view is updated. The configuration nodes forming the tree's model are associated with objects of type DirectoryData from the model package. They contain the data required for the display of a directory. Such an object is determined during the processing of the selection changed event of the tree view - either it has already been present in the model or it has just been created by a ReadDirectoryCommand. It is then used to populate the table.

The data model of the table is a plain java.util.ArrayList. It is filled from the data of the DirectoryData object obtained from the tree view's model. Actually, the list contains objects of type net.sf.jguiraffe.examples.tutorial.model.FileData, a simple wrapper around a java.io.File object. The table is configured to display the single properties of such FileData objects. With the FileSizeTransformer class there is a special transformer implementation applied to the column with the file size: it displays the size in kilo bytes.

The view settings dialog

The classes implementing the view settings dialog are located in the net.sf.jguiraffe.examples.tutorial.viewset package; the builder script defining the dialog window is named viewsettings.jelly. While the main frame demonstrates usage of some of the advanced UI elements like trees and tables, the view settings dialog contains various types of simple controls, e.g. radio buttons, checkboxes, or input elements for other data types like numbers or dates.

The view settings dialog is a typical dialog window whose purpose is to enter data. Therefore the base classes provided by the JGUIraffe library for forms and controllers can be used. Because the UI has some dynamic aspects, there is a custom controller implementation named ViewSettingsFormController derived from the base class FormController. This controller class has the task to control the enabled state of some input elements which depent on the state of other elements. This is achieved by registering the controller object as listener for change events at the checkbox elements defined in the UI (registration is performed in a declarative way by tags in the builder script). These checkboxes are connected to input elements; the input elements are only enabled if the checkbox is selected. Therefore, when a change event is received, the controller determines the affected checkbox from the event's properties and obtains its selection state. It then maps the name of the checkbox to a component group and sets the enabled state of this group accordingly. The UI has been defined in a way that there are component groups for the elements which depend on checkboxes. So it is easy to find the elements affected by a change of a checkbox and to manipulate their state.

When the user closes the view settings dialog by clicking the OK button a command of type CreateViewSettingsCommand is executed. It creates a file in the current directory and stores the settings the user has entered in the dialog window in it. To do this the command needs some data:

  • The data model of the form - an object of type ViewSettings lives in the current bean context. It has been stored there by the command which opened the view settings dialog. ViewSettings is a pretty simple data class. It has methods for loading and storing instances using standard Java serialization facilities.
  • The current directory must be known because in this directory the ViewSettings instance has to be stored. It is obtained from the central ApplicationContext object. The application context allows access to so-called typed properties which are intended to be used for managing central data required by multiple parts of the application. The controller of the main window sets this property whenever the user selects a new directory in the tree view.
After the command has written the file with the view settings it executes the Refresh action. This basically simulates a click on the corresponding tool bar icon. It causes another ReadDirectoryCommand to be executed so that the content of the current directory is read again. The ReadDirectoryCommand class is able to interprete the view settings for the current directory and apply filters and sort order accordingly.

A long-running operation

Applications sometimes have the requirement to execute a task which may take a while. In such cases it is good practice to give the user some feedback about what is going on. A good choice for this purpose is a dialog window with a progress indicator. If possible, a cancel button can be provided which allows the user to cancel the operation.

The tutorial application defines an action that simulates such a long-running task. The implementation demonstrates how standard means provided by JGUIraffe can be used to implement a dialog window for visual feedback. The corresponding classes can be found in the net.sf.jguiraffe.examples.tutorial.bgtask package, there is also builder script named bgtask.jelly.

The builder script is a little bit special because it defines two dialog windows at once. The main dialog window is directly opened when the user selects the action for the long-running task. It presents some options for the configuration of the task - mainly the user can specify the duration using a slider control. (Note: There is also a checkbox which controls whether visual feedback is enabled, but this is only for testing purposes. A real application should probably always provide feedback. Actually, disabling visual feedback is used to test the behavior of the application if it is to be terminated and there is still a background task running. You can try what happens then.)

When closing the configuration dialog with OK an instance of the BgTaskCommand class is executed. This object is passed some references to helper and data objects via dependency injection:

  • The GUISynchronizer object is required for accessing UI elements from a different thread than the event dispatch thread. The command object is executed in a background thread, so it has to use the synchronizer when it needs to manipulate the UI.
  • A reference to the second dialog window defined by the builder script. This is the dialog for giving feedback about the progress of the operation.
  • The data model of the first dialog window. This is a bean storing the data entered by the user in the configuration dialog.
  • A reference to the progress bar control contained in the feedback dialog. The command object uses this reference to increase the progress indicator.

At the beginning of its execute() method the command class opens the dialog window for the feedback of the operation. Then it simulates a long-running operation. This is done in a loop which sleeps for one second until the duration specified by the user is reached. In each iteration the current progress rate is calculated, and the progress indicator is updated correspondingly. Because the command runs in a background thread the GUISynchronizer has to be used for this purpose.

After completion of the execute() method the performGUIUpdate() method is called automatically in the event dispatch thread. Here the feedback dialog window is closed.

The command class also implements the FormActionListener interface and is registered as action listener at the Cancel button of the feedback dialog (via an <a:eventListener> tag in the builder script). So it gets notified when the user wishes to cancel the operation. In this case the Cancel button is disabled - to show the user that the click was recognized -, and a flag is set which causes the main loop of the command to terminate.

What is only simulated by the BgTaskCommand class can be transferred to real world use cases. The handling of the dialog window for the feedback of the operation can be done in a similar way. Then the task to be performed has to be broken down into several steps so that the progress indicator can be updated in a meaningful way. Listening to a cancel button and terminating the operation on demand is probably desired in most cases, too.

Note that for this kind of operations the feedback dialog is an essential implementation element. It does not only show the progress of the operation but also blocks the UI (it is a modal dialog) and prevents the user to trigger further actions. This is desired because in many use cases the user should not be allowed to start multiple background tasks in parallel which may conflict with each other. In JGUIraffe applications it is per default not possible to start multiple background tasks at once anyway because the command queue is only associated with a single worker thread. So tasks are executed one by one in isolation. If an application really needs a task that should run in the background independent from all other tasks of the application, it has to start a separate thread manually.