Tutorial

Writing a ULC application does not differ very much from writing a Swing application. However, there are a few constraints one has to keep in mind in order to be successful. To get acquainted with the basic concepts of ULC, we have designed a step-by-step guide showing the complete process of developing a ULC application.

In this first introduction to ULC programming, we will implement a simple list application that lists tasks. The application will allow users to add and remove entries from a list, as shown below.

The application displays an entry field to enter a task and a list box to display tasks. The bottom of the dialog box shows two buttons, one button for adding tasks to and one for removing tasks from the list.

In the following, we will assume the program is being created using a simple text editor with an appropriate version of the standard Java Development Kit (JDK). Implementing this example using more sophisticated tools should be very easy for a developer with some experience.


The To Do List Application

The example we would like to use in this tutorial is a task list allowing users to add and remove entries presented as a simple list. The desired application window is shown below in Figure 1.

Figure 1

Starting point is a generic ULC application skeleton (the code appearing for the first time in this tutorial is emphasized):


package todolist;

import com.ulcjava.base.development.DevelopmentRunner;

public class MyToDoList extends AbstractApplication {
    public static void main(String args[]) {
        DevelopmentRunner.setApplicationClass(MyToDoList.class);
        DevelopmentRunner.run();
    }

    public void start() {
    }
}

This code defines an empty ULC application class, which does nothing at the moment. Note that the class must be defined as public and it should provide the main() method. This is no different from basic Java principles. A closer look at the code reveals that the MyToDoList class extends AbstractApplication, a class of the ULC framework. More unusual is the call to the static run() method of the DevelopmentRunner class within the application's main() method; there is no explicit creation of an application instance. See ULC Reference Guide for more information of the DevelopmentRunner. Another difference is the presence of a start() method. Unlike Swing programming, the standard initialization, which usually takes place in the constructor, is done within this method. The ULC framework guarantees that it is called for by each connecting client.

All classes required by the MyToDoList application can be found in the com.ulcjava.base.application package, which is imported in the first line.

Creating a Window

The next logical step is to create a window. The basic class representing a separate window in the ULC framework is ULCFrame.

Let's add the new code for an application containing a window:

package todolist;

import com.ulcjava.base.application.*;
import com.ulcjava.base.application.event.ActionEvent;
import com.ulcjava.base.application.event.IActionListener;
import com.ulcjava.base.application.util.BorderedComponentUtilities;
import com.ulcjava.base.development.DevelopmentRunner;

public class MyToDoList extends AbstractApplication {
    public static void main(String args[]) {
        DevelopmentRunner.setApplicationClass(MyToDoList.class);
        DevelopmentRunner.run();
    }

    public void start() {
        ULCFrame frame = new ULCFrame("ULC ToDoList Sample");
        frame.setDefaultCloseOperation(ULCFrame.TERMINATE_ON_CLOSE);
        frame.setVisible(true);
    }

The only changes take place in the start() method. Note: Since this is the only part of our class that will be undergoing modifications, we will only show this method in the subsequent listings. Calling the setVisible() method on the ULCFrame instance displays this window. If we run this example, closing the window will make the application terminate automatically.

Designing the Layout

Now it is time to design the layout of our window. We would like to use a simple vertical box (one column) for the root layout into which we will put a decorative bordered text field and a bordered list. Below these components we create another layout box (this time horizontal) for two buttons. A schematic diagram of our window, with the layout structure emphasized, is shown in Figure 2.

Figure 2

The following code creates the necessary widgets for this layout:

public void start() {
    ULCTextField editField = new ULCTextField();
    ULCComponent borderedEditField =
        BorderedComponentUtilities.createBorderedComponent(
        editField, "To Do Item");

    ULCList list = new ULCList();
    ULCComponent borderedList =
        BorderedComponentUtilities.createBorderedComponent(
        list, "To Do List");

    ULCButton addButton = new ULCButton("Add");
    ULCButton removeButton = new ULCButton("Remove");

    ULCBoxPane buttonBox = new ULCBoxPane(false);

    ULCBoxPane rootBox = new ULCBoxPane(true);

    ULCFrame frame = new ULCFrame("ULC ToDoList Sample");
    frame.setDefaultCloseOperation(ULCFrame.TERMINATE_ON_CLOSE);
    frame.setVisible(true);
}

To edit new entries for the To Do List we have created an instance of ULCTextField. To display the entries we created a ULCList instance. Buttons for adding and removing entries from the list have been added.

For the bordered components a utility class is used to decorate the components(ULCTextField, ULCList) with a ULCCompoundBorder. This compound border has a title border as outer border and an empty border as an inner border. If no alignment information is provided, the bordered components fill the entire space available.

The ULCBoxPane constructor used here has the form of ULCBoxPane(boolean vertical) and allows creation of one column (when called with trueas for rootBox) or one row (when called with false as for buttonBox) boxes.

Adding the Widgets to the Containers

After having prepared the content of our window, we can now add the widgets to their container.

public void start() {
    ULCTextField editField = new ULCTextField();
    ULCComponent borderedEditField =
        BorderedComponentUtilities.createBorderedComponent(
        editField, "To Do Item");

    ULCList list = new ULCList();
    ULCComponent borderedList =
        BorderedComponentUtilities.createBorderedComponent(
        list, "To Do List");

    ULCButton addButton = new ULCButton("Add");
    ULCButton removeButton = new ULCButton("Remove");

    ULCBoxPane buttonBox = new ULCBoxPane(false);
    buttonBox.add(ULCBoxPane.BOX_CENTER_CENTER, addButton);
    buttonBox.add(ULCBoxPane.BOX_CENTER_CENTER, removeButton);

    ULCBoxPane rootBox = new ULCBoxPane(true);
    rootBox.add(ULCBoxPane.BOX_EXPAND_TOP, borderedEditField);
    rootBox.add(ULCBoxPane.BOX_EXPAND_EXPAND, borderedList);
    rootBox.add(ULCBoxPane.BOX_CENTER_CENTER, buttonBox);

    ULCFrame frame = new ULCFrame("ULC ToDoList Sample");
    frame.setDefaultCloseOperation(ULCFrame.TERMINATE_ON_CLOSE);
    frame.add(rootBox);
    frame.setVisible(true);
}

The alignment attribute in ULCBoxPane.add() call is used to describe the desired placement of a widget within another widget. It follows the naming convention BOX_<horizontalAlignment>_<verticalAlignment>. Possible values for <horizontalAlignment> are EXPAND, CENTER, LEFT, RIGHT. The valuse for the <verticalAlignment>: EXPAND, CENTER, TOP, BOTTOM.

A Word on Layouts

Figure 3

Figure 3 shows another screenshot of the running application, after resizing its window in both directions. Since both bordered components were added with the horizontal alignment EXPAND, they will always take up all available width. The button box always stays centered, since it was added with horizontal alignment CENTER.

The only component added to the vertical box with vertical alignment set to EXPAND was the list border. Therefore this cell will take all the remaining vertical space, while the other cells will keep their original vertical sizes and positions.

More generic comments on how to use layouts can be found in the ULC Essentials Guide.

We now have all the necessary widgets arranged. What we still need is the code performing the desired modifications on the list which is executed when one of the buttons is clicked.

Model-based widgets

Before we go any further, a short explanation of the ULCList class is necessary. This widget is used to present a data structure which in many cases can be a really huge and complex structure managed by user-independent code. Very often it would be very inefficient to duplicate the data from this structure inside the widget class, first and foremost because the memory usage would double, but also because every operation would have to be replicated. For this reason, this class (as well as some other ULC classes) requires another object, representing the actual model of the data displayed within the widget. In other words, the model objects are the real data structures and widgets can be regarded as their 'views', as in the Model-View-Controller (MVC) paradigm. In particular, this implies that it is possible that many widgets can present data from one model. The general scheme of using model-based widgets is:

  • Create a model object, which must implement a model interface appropriate for a particular widget.
  • Create a widget, passing the model instance as a constructor parameter.
  • Whenever you want to modify the data shown by the widget, perform this modification on the model. The model will notify the associated widget and update its state.

For the application developer's convenience, the ULC framework provides default model classes, which are satisfactory for most simple applications. We are going to use one of these, namely DefaultListModel, in our program. Therefore, we need to replace the original definition of ULCList instance with these two lines:

DefaultListModel listModel = new DefaultListModel();
ULCList list = new ULCList(listModel);

For more information about using models see ULC Reference Guide.

Defining Actions

Now that we have a structure we can manipulate, we can add code to perform the modifications and associate it with buttons. The common way to do this in both Swing and ULC, is to define classes implementing the IActionListener interface, which contains just one method, namely actionPerformed(). One of the main advantages of this approach is the possibility to use just one IActionListener object connected to many widgets, so the same action can be triggered by a button, selected from the main menu, or pop-up menu. Here is the code for the new classes:

    static class AddEntryAction implements IActionListener {
        DefaultListModel fListModel;
        ULCTextField fEditField;

        public AddEntryAction(DefaultListModel listModel,
                              ULCTextField editField) {
            fListModel = listModel;
            fEditField = editField;
        }

        public void actionPerformed(ActionEvent actionEvent) {
            fListModel.add(fEditField.getText());
        }
    }

    static class RemoveEntryAction implements IActionListener {
        DefaultListModel fListModel;
        ULCList fList;

        public RemoveEntryAction(DefaultListModel listModel,
                                 ULCList list) {
            fListModel = listModel;
            fList = list;
        }

        public void actionPerformed(ActionEvent actionEvent) {
            if (fList.getSelectedIndex() != -1) {
                fListModel.remove(fList.getSelectedIndex());
            }
        }
    }

Since these are separate classes, we need to pass on the information on which objects they will perform the operations. Both will need a reference to the list model. AddEntryAction will also need the text field reference from where to copy the text and RemoveEntryAction requires a reference to the list widget to be able to find out which row is selected, so it can be removed. We assign the listeners to their appropriate buttons by means of the ULCButton.addActionListener() method:

addButton.addActionListener(new AddEntryAction(listModel, editField));
removeButton.addActionListener(new RemoveEntryAction(listModel, list));
We do not store references to created listeners, since we do not intend to make these actions accessible from anywhere else but from these two buttons.

Adding Enablers

For the final touch on our application, we could use a nice feature of the ULC framework called Enablers. The general idea is to provide the developer with simple shortcuts for changing the enabled/disabled state of widgets depending on the state of other widgets. Widgets implementing the IEnabler interface can be registered as enablers for any other ULCComponent. When the appropriate "enabling" event is generated depends on the IEnabler widget. In case of ULCList it happens when a row is selected and in case of ULCTextField when is a non-empty string is typed. This is exactly what we would need for enabling the Remove and Add buttons respectively. In the list widget, the IEnabler interface is not implemented by the ULCList itself, but by the ULCListSelectionModel class. This model can be retrieved by calling the getSelectionModel() method on the ULCList object. With this final step, we've completed our first ULC application.

package todolist;

import com.ulcjava.base.application.*;
import com.ulcjava.base.application.event.ActionEvent;
import com.ulcjava.base.application.event.IActionListener;
import com.ulcjava.base.application.util.BorderedComponentUtilities;
import com.ulcjava.base.development.DevelopmentRunner;

public class MyToDoList extends AbstractApplication {
    public static void main(String args[]) {
        DevelopmentRunner.setApplicationClass(MyToDoList.class);
        DevelopmentRunner.run();
    }

    public void start() {
        ULCTextField editField = new ULCTextField();
        ULCComponent borderedEditField =
            BorderedComponentUtilities.createBorderedComponent(
            editField, "To Do Item");
        
        DefaultListModel listModel = new DefaultListModel();
        ULCList list = new ULCList(listModel);
        ULCComponent borderedList =
            BorderedComponentUtilities.createBorderedComponent(
            list, "To Do List");
        
        ULCButton addButton = new ULCButton("Add");
        addButton.addActionListener(
            new AddEntryAction(listModel, editField));
        addButton.setEnabler(editField);
        
        ULCButton removeButton = new ULCButton("Remove");
        removeButton.addActionListener(
            new RemoveEntryAction(listModel, list));
        removeButton.setEnabler(list.getSelectionModel());

        ULCBoxPane buttonBox = new ULCBoxPane(false);
        buttonBox.add(ULCBoxPane.BOX_CENTER_CENTER, addButton);
        buttonBox.add(ULCBoxPane.BOX_CENTER_CENTER, removeButton);

        ULCBoxPane rootBox = new ULCBoxPane(true);
        rootBox.add(ULCBoxPane.BOX_EXPAND_TOP, borderedEditField);
        rootBox.add(ULCBoxPane.BOX_EXPAND_EXPAND, borderedList);
        rootBox.add(ULCBoxPane.BOX_CENTER_CENTER, buttonBox);

        ULCFrame frame = new ULCFrame("ULC ToDoList Sample");
        frame.setDefaultCloseOperation(ULCFrame.TERMINATE_ON_CLOSE);
        frame.add(rootBox);
        frame.setVisible(true);
    }

    static class AddEntryAction implements IActionListener {
        DefaultListModel fListModel;
        ULCTextField fEditField;

        public AddEntryAction(DefaultListModel listModel,
                              ULCTextField editField) {
            fListModel = listModel;
            fEditField = editField;
        }

        public void actionPerformed(ActionEvent actionEvent) {
            fListModel.add(fEditField.getText());
        }
    }

    static class RemoveEntryAction implements IActionListener {
        DefaultListModel fListModel;
        ULCList fList;

        public RemoveEntryAction(DefaultListModel listModel,
                                 ULCList list) {
            fListModel = listModel;
            fList = list;
        }

        public void actionPerformed(ActionEvent actionEvent) {
            if (fList.getSelectedIndex() != -1) {
                fListModel.remove(fList.getSelectedIndex());
            }
        }
    }
}

The full program code is also available as a  separate file.

Testing the Application

To run the To Do List application simply start it with the java todolist.MyToDoList command (or similar, depending on the development environment used). Make sure your classpath includes all the necessary ULC framework classes. Now, add and remove entries, resize the application window and verify that it behaves exactly as expected. Also check that the buttons enable and disable correctly.

Congratulations! Your ULC To Do List application is complete.