In the section about forms we have already seen how
the Form
class provides support for many tasks related to the handling of forms like
reading and writing the contents of input elements or performing validation.
Compared with the amount of code that would be required if all these steps
were implemented by hand this is certainly a major improvement. However, it
is still not the full story
While the Form
class implements a great deal of functionality
needed for the handling of a form's data it leaves some important questions
unanswered:
These issues are closely related to the life cycle of a form. In desktop applications a form is always associated with a (dialog) window. When the window pops up the input fields must be initialized. While the user interacts with the input elements validation can be performed and fields containing invalid data can be highlighted somehow. Typically the window contains buttons which can be used to commit or to cancel the form. If the user presses the OK button, the data entered has to be validated again and - if validation succeeds - stored in the model for further processing. Then the window can be closed. The Cancel button in contrast directly closes the window.
In the JGUIraffe library a form controller is responsible
for managing the life-cycle of a form. It establishes the connection
between the Form
class and the data it maintains and the
window that embeds the form.
It is an unwritten law that all libraries that deal with the processing of user input must emphasize that they implement the model view controller (MVC) pattern in some way. This is a design pattern which has proven to be successful especially in UI development. It focuses on the separation between the graphical component that presents data to the user (the view) and the component that stores the data (the model). A third component, the controller, evaluates user actions in the view and updates the model correspondingly. So the controller mediates between the model and the view.
The JGUIraffe library is no exception from this rule. It also adheres to the separation between model, view, and controller by making use of the following components:
FormController
or a derived class. Together with an instance of
Form
the interaction between the model and the view is managed.
In this section we focus on the FormController
class. The idea
is that every time a window displaying a form is constructed, an instance of
FormController
is created, too, and associated with both the
window and the Form
object managing the user's input. After its
creation the FormController
object is registered as listener
for various events at the window and its input components. Thus it receives
notifications for important status changes and can react accordingly, e.g.
by invoking methods on the Form
object.
For instance, when the window is opened for the first time the controller
object reveives a corresponding event. It can then trigger the
Form
object to fetch initial values from its model and to write
it into the input components.
The form controller is also aware of the actions that can close the form - namely the buttons for Okay and Cancel. If corresponding events are received, it can trigger a validation and decide whether the window can be closed. It is also possible to configure command objects that are to be executed when the form is closed on reaction of an Okay or Cancel action. This allows an application to immediately process the data entered by the user.
Finally, a FormController
object can act itself as an event
source. It can send certain events releated to changes in the validation
status of input fields. Such events are fired for instance after each
validation operation. Interested components can register as listeners for
these events and perform corresponding UI updates. For instance, if a
validation operation detects that a field contains invalid data, an event
listener can highlight this input field.
This was a high-level overview over the functionality provided by the
FormController
class. We will now discuss its API in more
detail.
FormController
is a concrete class and can be used as is. If a
dialog window has specific requirements that go beyond the standard
functionality for input validation and processing provided by the class, a
custom class can be created that extends FormController
.
After the creation of an instance some references to dependent objects must
be set. This way the controller is associated with the Form
object and other components it has to interact with. Typically a developer
does not have to care about the initialization of a form controller; the
framework performs all necessary steps behind the scenes (we will discuss
this in more detail when we describe the builders provided by
JGUIraffe). In this subsection we assume that a
FormController
instance has already been created and
initialized and focus on the methods that can be used to customize this
instance.
The FormController
object must be able to detect when the user
wants to close the dialog window. Depending on the action triggered by the
user (OK or Cancel) the controller has to react accordingly to ensure that
the data entered by the user is processed correctly. In order to detect
clicks on the OK or the Cancel button the controller has to know the names
of the corresponding button components. For this purpose the methods
setBtnOkName()
and setBtnCancelName()
are used.
Both methods expect a string with the name of the corresponding button. The
controller registeres itself as action listener at these button components
so that it is notified when the user clicks on these buttons.
Further properties and methods of the FormController
class are
related to the handling of validation operations. First there is the
validate()
method which performs a validation of the form
associated with the controller and returns the results. The method can be
called at any time; the controller also calls it at certain points in the
life-cycle of the dialog window, e.g. after it is opened or when the user
clicks the OK button. A validation also causes all registered listeners of
type
FormControllerValidationListener
to be notified. Listeners of
this type can be added using the addValidationListener()
method.
The default behavior of the FormController
class when the user
presses the OK button is to perform a validation to ensure that all data
entered so far is valid. If this is not the case, a message box with
validation messages is displayed, and the form cannot be closed. There are a
couple of methods to configure the message box displayed in this situation:
setMessageOutput()
allows setting the
MessageOutput
object that is used for displaying the message box.
If no specific MessageOutput
object is set, the
application-global default object is used - which is probably desired in
most cases. One use case for this method is to suppress the message box with
validation errors at all. This can be done by setting a
NullMessageOutput
object.setValidationMessageBoxCaption()
method the title
of the message box with validation messages can be determined. If no title
has been set, a default title (defined by a default resource ID and obtained
through the current resource manager) is used.
FormValidationMessageFormat
object to the
setValidationMessageFormat()
method.
FormValidationMessageFormat
can translate a list of validation
messages to a plain string which can then be displayed in a message box.
Again, a default object is used if no custom format object is specified.When the user clicks the OK or the Cancel button (or performs a corresponding action, e.g. pressing the Escape key) the dialog window has to be closed. In addition, the controller can be instructed to execute specific code which reacts on the closing of the form. This is a convenient way to immediately process the data entered by the user. For instance, if the current form allows entering the data for a new domain object, code can be invoked in reaction of the OK button which writes the data into the database.
There are two ways to execute code when the dialog window associated with the controller is closed:
setOkCommand()
and setCancelCommand()
respective. When the associated dialog window is closed the controller
checks whether a command is defined for the corresponding closing type
(OK or Cancel). If this is the case, the command is put into the global
command queue and executed by a worker thread - hence the UI does not
block. (Refer to the Commands section for
more information about commands and their execution.) By making use of
command objects complex operations can be performed with the data entered
in the form, e.g. database or file operations.addFormListener()
method expects an object
implementing the
FormControllerFormListener
interface. This interface
defines a single formClosed()
method which is called when the
dialog window associated with the controller is closed. It is passed a
FormControllerFormEvent
object which contains information about
the way the form was closed. So the listener can react accordingly. The
main difference between the event listener approach and command objects is
that event listeners are directly called from within the event dispatch
thread. Therefore they are appropriate for short-running operations only.
If processing of form data takes a little longer, a command object should
be preferred; otherwise the UI might become non-responsive.The two options for processing form data when the form was closed are not mutual exclusive. It is well possible to define both a command and an event listener. This may make sense in certain situations, e.g. the event listener could update the UI to give the user some feedback while a command performs a longer-running operation in the background.
The use of command objects or event listeners for the processing of form data is
one reason why the FormController
class can be used out of the
box: There is no need to override methods that define how the data is to be
processed; just associate the controller with corresponding command objects
or listeners. Note that these associations are fully optional. If there are
no commands nor event listeners, the dialog window is just closed without
performing any further steps - of course, if the OK button is pressed, the
data entered by the user is copied into the model object of the form.
One important task of a FormController
instance is to provide
feedback about the current validation status of the form's input fields. In
most cases the user wants to see directly if fields contain invalid data.
However, there is no default strategy that defines how and when such a
feedback is to be provided. An application may decide to do without
immediate validation feedback. In this scenario a validation is performed
only at the end of data entering, i.e. when the user hits the OK button.
Other applications might want to provide a feedback earlier, e.g. after the
user leaves an input field or even as soon as data is typed in. All of these
strategies have their specific advantages and disadvantages, and a library
supporting forms should be flexible enough to support all of them.
The next question is the way how feedback about invalid fields is to be given. Again there are multiple possibilities thinkable, for instance
Let's tackle the question about when to perform validation first: A form
controller can be passed an object implementing the
FormValidationTrigger
interface to its
setValidationTrigger()
method. This object decides when a
validation is to be performed. The FormValidationTrigger
interface is surprisingly simple: it defines only the single method
initTrigger()
that is passed a reference to the current
FormController
. The background behind this concept is that a
FormValidationTrigger
object is not queried by the controller,
but it becomes active itself when it decides that a validation is required
now. A typical implementation will use the FormController
reference passed to its initTrigger()
method to register itself
as event listener for all necessary events. In the event handler methods it
can then call back to the controller and initiate a validation operation.
JGUIraffe ships with the following concrete implementations of the
FormValidationTrigger
interface:
FormValidationTriggerNone
never triggers a validation. This is
appropriate if no immediate feedback about invalid data is to be provided.
If this trigger is used, a validation is only performed when the user
presses the OK button (this validation is independent from a validation
trigger).
FormValidationTriggerFocus
registers itself as form-global
focus listener. Thus it is notified whenever the user leaves an input
field. Then it triggers a validation operation. This seems to be a good
compromise: the user is not disturbed by changing validation hints while
typing, but the messages are pretty up-to-date.
FormValidationTriggerFocus
is the default trigger used when
no specific trigger was set.
The FormValidationTrigger
interface determines when a validation
of the user input is to be performed. It does not answer the question what
to do when invalid input fields are detected. Because there is a wide range
of possible options to handle invalid input fields there is no specific
interface for this purpose. Rather, the FormController
class
provides a generic event listener mechanism that allows interested components
to be notified when certain events related to the validation status of
input fields occur. The following event listener interfaces are supported:
FormControllerValidationListener
objects are added using the
addValidationListener()
method. They are notified whenever the
controller's validate()
method is invoked. The event object
passed to these listeners also contains an object with the results of the
validation.
FormControllerFieldStatusListener
objects are added using the
addFieldStatusListener()
method. They are notified when the
visited status of an input field changes, i.e. when the user leaves
an input field for the first time. The background is that fields containing
invalid data may be treated differently depending on their visited status.
Consider a form that declares some fields as mandatory. When the form is
opened these fields are not yet filled in and thus are invalid. An application
may not want to present the user a full list with error messages at the
very beginning of data entering. Only if the user enters and leaves a field
its validity should be taken into account. With listeners for the field
status this use case can be implemented. (Note: When the user hits the OK
button all input fields are marked as visited.)These event listener interfaces allow interested components to keep track of the validation status of all input fields in the form. The JGUIraffe library provides a couple of classes implementing these interfaces that can be used out of the box to present hints related to validation results to the user.
The
ColorFieldMarker
class tracks the validation status of input
fields and changes their foreground and background colors correspondingly.
A ColorFieldMarker
can be configured with specific foreground
and background colors for the possible validation states (e.g. colors for
fields that are invalid, for which validation warnings exist, which are
invalid, but have not yet been visited and so on). When an event from the
FormController
is received the ColorFieldMarker
object determines all input fields whose validation status has changed. It
then obtains the colors corresponding to the current status of each field
and sets them. If for a specific validation status no colors are defined,
the original colors of this input field are set (with other words: the color
of such fields is not changed). The following image shows a simple dialog
window with input fields that have been marked according to their validation
status:
In this example the File name input field is invalid because the file name contains characters which are not allowed. For invalid fields that have already been visited the background color is set to red. The yellow background color of the File content input field indicates a validation warning. The warning was issued because the field is empty. The background color of this field is only changed if it has already been visited. So when the dialog window opens the field is displayed with its normal background color (white) even if it is empty.
Another specialized FormControllerValidationListener
implementation is
ToolTipFieldMarker
. As the name implies,
ToolTipFieldMarker
uses the tool tips of input elements to
provide information about validation errors or warnings to the user.
Whenever a validation event is received the ToolTipFieldMarker
object checks whether fields with validation errors or warnings exist. If
this is the case, the messages are extracted and added to the tool tips of
the corresponding elements. The user only has to move the mouse cursor over
an input field to see the messages available for this field.
ToolTipFieldMarker
can be used together with
ColorFieldMarker
. In this case the colors of input fields
indicate whether they contain valid data or not. The actual validation
messages can then be displayed by simply moving the mouse cursor over the
field.
A ToolTipFieldMarker
object is associated with an instance of
FormValidationMessageFormat
. This object determines how the
validation messages are transformed to plain text which is then displayed
in the tool tips. Please refer to the documentation of
FormValidationMessageFormat
for the possible options to customize
this transformation.