In the previous sections it was discussed that builders that interprete XML-based scripts play an important role in the JGUIraffe framework. The chapter about the dependency injection builder showed how arbitrary Java objects can be defined and created. In this section we focus on the creation of user interface elements that can be used in forms to gather user input. For this purpose the JGUIraffe library provides a specialized tag library: the form builder or component builder tag library.
The tags of the form builder tag library support the definition of typical
UI elements like panels, labels, text fields, or radio buttons. They also
allow the creation of layouts for containers. Last, but not least UI-related
helper objects like validators or transformers can be declared. One big
advantage of the form builder tag library is that it does not only create
the user interface, but automatically generates a
Form
object which is able to process the user input entered in this
UI. Refer to the Working with forms section for
more information about forms.
We will show how this looks like in practice. But first we have to introduce some central classes and interfaces whose knowledge is important when using the form builder tag library.
While processing a builder script with UI definitions the builder actually creates two different structures in memory:
The most important objects for managing input components are
ComponentHandler
objects. A ComponentHandler
wraps
a graphical input element and provides read and write access to its data.
The type of the data depends on the concrete input element. A text field
for instance, has the entered text as its data. For a check box the data
consists of a boolean flag (whether the check box is checked or not). For a
list box it is the index of the selected element (or an array of the selected
indices if multi-selection is allowed). The actual Java data type of a
ComponentHandler
can be queried using its getType()
method.
The methods getData()
and setData()
can be used to
query or set the current content of an input element. Note that the
ComponentHandler
interface has a generics parameter that defines
the return or parameter types of these methods. With the methods
isEnabled()
and setEnabled()
the enabled state of
an input element can be queried or set. By using the setEnabled()
method it is possible for instance to disable an input element if it is
temporarily not available, maybe because of the status of other input
elements.
Component handlers are used under the hood by the Form
class to
transform data between graphical input elements and the form's model or vice
versa. So if only the data entered by a user into a form is of interest, a
developer need not deal with these handlers directly. However, if the form
is more dynamic and should change the status of some components based on
user input, it might be necessary to use component handlers to read the
current data of input elements and to manipulate other dependent elements.
An example is a check box that controls other input elements: only if the
check box is checked, the dependent input elements should be enabled. A way
to achieve this is to add an event listener at the check box for change
events (we will show how this is done in later sections). In this event
handler the ComponentHandler
for the check box is obtained and
queried for the data of the checkbox. This results in a boolean flag which
determines the enabled status of the dependent elements.
The ComponentHandler
interface defines the minimum protocol
required by the framework for the interaction with input components. It does
not cover any specialities of components. This is a shortcoming, especially
when dealing with more complex input elements like tables or trees. Such
components provide special functionality which cannot be accessed by the
ComponentHandler
interface. Therefore the JGUIraffe
library provides a number of specialized interfaces derived from
ComponentHandler
which are tailored for specific component
types. These interfaces live in the
net.sf.jguiraffe.gui.builder.components.model
package. For
instance, there are specialized handler interfaces for tables, trees, or
progress bars. If you work with one of these components, the associated
ComponentHandler
object can be casted to the specialized
interface to obtain extended access to this component.
While the ComponentHandler
interface focuses on data access to
input components, the
WidgetHandler
interface allows the manipulation of graphical
properties of elements, e.g. the colors or the visible state. Widget handlers
are available for all graphical elements, not only for input elements. This
includes labels for example. Again, widget handlers are useful for dynamic
forms which adapt their appearance to current user input.
We have seen that during a builder operation a bunch of objects is created:
a Form
object, component handlers, widget handlers. But how can
these objects be accessed? All objects created during the builder operation
are collected by a central object which represents the results of the
builder. This is an object of type
ComponentBuilderData
. This class defines a bunch of useful methods
for obtaining the objects created:
getComponentHandler()
returns the ComponentHandler
for the input element with the specified name.getComponent()
returns the actual component with the
specified name. This is the platform-specific UI element, e.g. a Swing text
field. Because the type of this element depends on the underlying UI toolkit
getComponent()
has the unspecific type java.lang.Object
as its return type.getWidgetHandler()
returns the WidgetHandler
for the UI element with the specified name. This method can also be called
for non-input elements, provided that a name was specified for them.getForm()
returns a reference to the Form
object created during the builder operation. This form contains fields for
all the input elements defined in the builder script.getBeanContext()
returns the BeanContext
of the
current builder operation. This BeanContext
provides access to
almost all objects created by the builder. Refer to the section
The dependency injection builder for more
information about bean contexts and the definition of objects. There are some
special short cuts for accessing graphical objects produced by the builder
like components or their handlers. The Javadocs for the
ComponentBuilderData
class describe which additional objects can
be retrieved in detail.
So from the ComponentBuilderData
object access to all builder
results is possible. How to obtain the ComponentBuilderData
object itself depends on the target object: A class derived from
FormController
(refer to the section
Form controllers) has direct access to the ComponentBuilderData
object through its getComponentBuilderData()
method. Typically
the controller of a form needs this object if it has to manipulate UI
elements depending on some logic. If some other object needs access to the
ComponentBuilderData
object, the best way to achieve this is to
define this object in the builder script and inject a corresponding
reference. As an example consider the following class definition:
package com.mypackage; import net.sf.jguiraffe.gui.builder.components.ComponentBuilderData; public class MyClass { private final ComponentBuilderData builderData; public MyClass(ComponentBuilderData cd) { builderData = cd; } // remaining part omitted }
Here a simple class is defined which expects a ComponentBuilderData
object to be passed to its constructor. An instance of this class can be
created in a builder script using the following tags:
<di:bean name="myClassInstance" beanClass="com.mypackage.MyClass"> <di:constructor> <di:param refName="COMPONENT_BUILDER_DATA"/> </di:constructor> </di:bean>
Now that we know how to obtain a ComponentBuilderData
object we
can implement the example mentioned above: a form contains a check box which
controls a text field. Only if the check box is checked, the text field
should be enabled. We assume that this rule is to be implemented by a
FormController
object which is registered as change listener
at the checkbox. Then the controller class can contain the following code:
/** Constant for the name of the check box component. */ private static final String COMP_CHECKBOX = "testCheckBox"; /** Constant for the name of the text field component. */ private static final String COMP_TEXT = "dependentTextField"; /** * Notifies this object about a change in the data of an element. * @param e the change event */ public void elementChanged(FormChangeEvent e) { // obtain the handler for the check box (the type-safety warning can be // suppressed because we know that it is a check box handler @SuppressWarnings("unchecked") ComponentHandler<Boolean> checkHandler = (ComponentHandler<Boolean>) getComponentBuilderData().getComponentHandler(COMP_CHECKBOX); // obtain the handler for the text field ComponentHandler<?> textHandler = getComponentBuilderData.getComponentHandler(COMP_TEXT); // Change enabled state based on the check box's data Boolean checked = checkHandler.getData(); textHandler.setEnabled(checked.booleanValue()); }
This example shows how UI-related logic can be implemented with component
handlers and the help of the ComponentBuilderData
object. Event
listeners will be discussed in a later section. Next we talk about builder
scripts that define UI components.
The previous sections already contained examples for builder scripts. Scripts defining UI components do not look differently. It is essential that the namespace declaration on the root element includes the component builder tag library, so that all these tags can be used. The following listing shows the skeleton of a builder script which we use in the examples in this section. It defines a dialog window with its content:
<?xml version="1.0" encoding="ISO-8859-1"?> <j:jelly xmlns:j="jelly:core" xmlns:di="diBuilder" xmlns:f="formBuilder" xmlns:a="actionBuilder" xmlns:w="windowBuilder"> <!-- Definition of the dialog window --> <w:dialog titleres="dialog_title" center="true"> <!-- Define the content of the dialog window here --> </w:dialog> </j:jelly>
Here the component builder tag library is registered with the namespace
prefix "f" (for form). So all XML elements starting with
f:
are looked up in this tag library. The
<j:jelly>
root element also declares namespaces for other
tag libraries:
The skeleton script contains a <w:dialog>
tag which is
part of the window builder tag library. This tag will be introduced when
the window builder tag library is described. For now it is sufficient to
know that this tag - not surprisingly - creates a dialog window. In the
body of the tag the definition of the dialog's content is placed. Here the
tags of the component builder tag library occur; they define the UI elements
that comprise the UI of the dialog window.
The component builder tag library contains a large number of tags for creating various UI components and objects related to them. There are some exceptions, but most of the tags can be grouped into one of these categories:
The most tags contained in the component builder tag library are derived
(either directly or indirectly) from the
FormBaseTag
class. This class does not implement a great deal of
functionality, it mainly provides access to the current
ComponentBuilderData
object which lives in the Jelly context.
But it defines two interesting attributes which are inherited by all sub
classes: ifName
and unlessName
. These attributes
control a conditional execution of the tag.
The builder that processes the builder scripts can be assigned a name. The
ifName
and unlessName
attributes compare the name
of the builder with the value of the attribute and execute the tag only if
the names match (in the case of ifName
) or do not match (for
unlessName
). The background for these attributes lies in the
fact that JGUIraffe tries to be compatible with multiple UI
libraries. It may be possible that some libraries require specific
adaptations in builder scripts. Using these attributes it is pretty easy to
implement such adaptions by letting certain tags only execute for specific
builders. For instance, some parts of the builder script should only be
executed if Swing is used as underlying UI library; other parts may deal with
specialities of SWT, etc. Conditional execution of tags is of course also
possible with tags of the standard Jelly tag libraries. However, the
ifName
and unlessName
attributes provide a
convenient short cut.
The next abstract base class in the hierarchy of the component builder tag
library is
ComponentBaseTag
. It acts as the base class for all graphical
elements and defines a set of common standard attributes. With these
attributes the following properties of graphical components can be set:
ColorHelper
helper class. Have a look at the documentation of
this class to learn about the format of strings describing colors.
From ComponentBaseTag
two other base classes are derived:
SimpleComponentTag
and
InputComponentTag
. SimpleComponentTag
is the base
class for graphical elements that do not support user input. No additional
standard attributes are defined. For instance, the tag for defining a label
is derived from this class. Container tags are also sub classes of
SimpleComponentTag
. Container tags are tags that manage a set
of other components, e.g. a panel.
Many tags of the component builder tag library define input elements and
thus are derived from
InputComponentTag
. This base class not only takes care about the
creation of the corresponding input element, but also ensures that appropriate
handler objects are created and added to the current Form
object. This establishes a connection between the input element and the
model of the form, so that the form can be used to read and write the data
of the input element. For input elements a bunch of standard attributes is
available (in addition to the ones supported for all components):
noField
attribute
can be set to true. This attribute prevents that a field is created
for the input element in the current form.groups
attribute an input element can be added
to one or more logic groups of components. This is just a logic grouping and
not visible. Component groups provide some functionality, e.g. to obtain
ComponentHandler
objects for all elements in the group or to
change the enabled state of all elements.
The last base class we want to discuss here is
UseBeanBaseTag
. The standard tag libraries shipped with Jelly
provide a means for creating and accessing beans: the
<useBean>
tag. Using this tag simple Java objects can be
created by calling their standard constructor, and attributes of the tag are
used to define properties. The UseBeanBaseTag
class of the
component builder tag library extends this functionality to better integrate
with the dependency injection framework. The main purpose of tags
derived from UseBeanBaseTag
is to fetch a bean that was defined
by a <di:bean>
tag and to pass it to another tag handler.
For instance, each input component can be assigned a validator object. The
<f:validator>
tag is responsible for this task. It is
derived from UseBeanBaseTag
. So it can either create a validator
object directly or obtain one that was defined using a
<di:bean>
tag. This object is then passed to the target
input element.
To give you a better impression of how the definition of a user interface in a builder script looks like we present parts of a script that defines a simple dialog box. Using this script we can explain some of the essentials of this approach. The following figure shows the dialog that is to be defined:
This is a simple dialog that allows the user to create a new file. The name of the file and its content can be specified in input fields. The layout is not very complex: the main part of the dialog consists of the two text input fields and their labels; at the bottom there is a bar with the buttons for closing or canceling the dialog. The builder script that defines the dialog is presented below (we only focus on the UI part; some definitions for controller elements are omitted):
<j:jelly xmlns:j="jelly:core" xmlns:di="diBuilder" xmlns:f="formBuilder" xmlns:a="actionBuilder" xmlns:w="windowBuilder"> <!-- A validator for required input fields.--> <di:bean name="requiredValidator" beanClass="net.sf.jguiraffe.transform.RequiredValidator"> </di:bean> <!-- A regular expression validator for validating a file name. Certain characters are not allowed in file names. --> <di:bean name="fileNameValidator" beanClass="net.sf.jguiraffe.transform.RegexValidator"> <di:setProperty property="regex" value="[^\*\\/~\|<>]*"/> </di:bean> <!-- The dialog window --> <w:dialog titleres="newfile_title" center="true" resizable="true"> <f:borderlayout/> <!-- The main panel --> <f:panel> <f:borderconstr name="CENTER"/> <f:percentlayout columns="4dlu start/preferred 3dlu full/preferred(6cm)/100 4dlu" rows="4dlu preferred 3dlu preferred full/preferred(5cm)/100 4dlu"/> <f:label textres="newfile_lab_name"> <f:percentconstr col="1" row="1"/> </f:label> <f:textfield name="fileName" displayNameres="newfile_disp_name" maxlength="200" tooltipres="newfile_txt_name_tip"> <f:percentconstr col="3" row="1"/> <f:validators phase="syntax"> <f:validator beanName="requiredValidator"/> <f:validator beanName="fileNameValidator"> <f:properties> <f:property property="ERR_PATTERN"> <f:localized resid="ERR_FILENAME_PATTERN"/> </f:property> </f:properties> </f:validator> <f:validator class="net.sf.jguiraffe.examples.tutorial.createfile.UniqueFileNameValidator"/> </f:validators> </f:textfield> <f:label textres="newfile_lab_content"> <f:percentconstr col="1" row="3"/> </f:label> <f:textarea name="fileContent" displayNameres="newfile_disp_content"> <f:percentconstr col="1" row="4" spanx="3" targetCol="3"/> <f:font name="Monospaced" size="13"/> <f:validator phase="syntax" beanName="requiredValidator"> </f:validator> </f:textarea> </f:panel> <!-- The button bar --> <f:panel> <f:borderconstr name="SOUTH"/> <f:buttonlayout/> <f:button name="btnOk" textres="newfile_btn_create" mnemonicres="newfile_btn_create_mnemo" default="true"/> <f:button name="btnCancel" textres="newfile_btn_cancel" mnemonicres="newfile_btn_cancel_mnemo" cancel="true"/> </f:panel> </w:dialog> </j:jelly>
The listing is a bit verbose as XML documents tend to become in general. However, it should be understandable which parts are responsible for the creation of which UI elements.
After the typical namespace declaration which includes all of the JGUIraffe tag libraries there is a block that uses the tags of the dependency injection tag library to define some validator objects. These objects are later assigned to the text fields to ensure that they can contain only valid data.
The interesting part starts with the <w:dialog>
tag that
introduces a new dialog window. In the body of this tag the definition of
the dialog's user interface is placed. For the dialog window as a whole a
border layout is defined by the <f:borderlayout>
tag (refer to the Layouts section for more
details about the dialogs supported by JGUIraffe). The UI of the
dialog consists of two panels that are arranged using the border layout: the
main panel in the center, and the buttons panel in the south. Both are
defined by <f:panel>
tags in the body of the
<w:dialog>
tag. The content of the panels is again defined
in the body of these tags. This nesting of tags is a fundamental principle
used within builder scripts. It should not be hard to understand because it
corresponds to the structure of the UI elements created. The layout
constraints that tell the layout manager where to place the panels are
specified by nested tags in the body of the <f:panel>
tags (<f:borderconstr>
in this case). This is the
typical way to specify layout constraints: they are placed in the body of
the tag they refer to, too.
The first panel is the more complex one. It uses a PercentLayout
layout manager to layout the components it contains. The layout is defined
by the <f:percentlayout>
tag in the body of the panel.
The attributes of the tag specify the columns and rows comprising the
tabular layout. To actually place components in this layout,
<f:percentconstr>
tags are needed that define the position
and other properties of components which belong to the panel. Again these
tags can be found in the bodies of the tags defining the components they
refer to.
The main panel consists of two labels and two text fields (a single line
text field and a text area with multiple lines). Labels are pretty easy to
define. For this example the labels are only assigned texts - they support
icons, too, but this is not needed here. The texts of the labels are
specified using the textres
attribute. The suffix "res"
stands for resource; this means that the actual text of the label
is loaded from the application's resources using the value of the attribute
as resource ID. (More information about resources can be found in the
section Resources.) Here we see another common
principle used within the JGUIraffe tag libraries: when texts are
to be defined that are displayed to the end user, there are always the
following possibilities:
<f:label>
tag has the attribute text
that takes the text of the label.textres
attribute in the example script.
As resource group the application's default resource group is used.resgrp
.titleres
attribute of the
<w:dialog>
tag and the displayNameres
attribute of the <f:textarea>
tag.
The text input components are more complex. The corresponding tags have more
attributes than the simple <f:label>
tag, e.g. a tooltip
and a display name. For the text area a special font is set using a nested
<f:font>
tag. Fonts can be set this way for all kinds of
graphical elements. It is also possible to share font objects between
multiple elements. This is achieved by specifying the var
attribute of the <f:font>
tag. Then the font is stored
under this name in the current context. It can be referenced by other tags
for graphical elements using their fontRef
attribute. Refer to
the documentation of the
FontTag
class for more details.
But what makes the tags for the text components really complex are the validator
definitions in their bodies. For the text area a required
validator is specified which checks whether the field contains a value - it
is not permitted to leave the field empty. A nested
<f:validator>
tag is used to assign the validator to the
text area. The other text field contains even multiple validators. Therefore
the <f:validators>
tag occurs in the tag's body which in
turn contains three nested <f:validator>
tags. This means
that on validation of the text field all its validators are invoked. Only if
all of them return a valid result, the text field is considered valid.
The <f:validator>
tag is derived from the
UseBeanBaseTag
base tag handler class. The example script
contains different variants of this tag demonstrating the different ways to
obtain beans:
beanName
attribute is used, there must be a bean
definition with this name in the current script or in the parent bean
context. In this example validator beans are referenced that are defined at
the beginning of the script using <di:bean>
tags.class
attribute rather than
beanName
. This causes an instance of the specified class being
created using its standard constructor - which is convenient, but less
powerful than the variant that uses the full power of the dependency
injection framework.This concludes our discussion of the first example builder script. You should now have an impression of how builder scripts look like and know the basic principles of defining UI elements. In the next section we discuss the tags for creating specific UI elements.
In this sub section we present the tags that comprise the component builder tag library. The single tags are introduced, and their implementing tag handler classes are noted. We give short usage examples and mention points that may not be obvious or may help to avoid pitfalls. Note that this is not a reference of the tags with all their attributes. Reference information can be found in the Javadocs of the tag handler classes (which are linked from the describing text).
Labels belong to the most simple graphical components. They just display a
text and/or an icon which cannot be changed at runtime. Nevertheless, there
is a bunch of attributes available allowing to customize the display of the
label. A description of all available attributes can be found in the Javadocs
of the
LabelTag
class.
As was already described in the subsection A
simple example, the text of a label can be defined either directly by
specifying the text
attribute or via a resource ID using the
textres
attribute. In addition, a label can have an icon. Icons
are defined by nested <f:icon>
tags which are implemented
by the
IconTag
tag handler class. The following fragment shows an
example of a more complicated label declaration:
<f:label text="Hello world!" foreColor="blue" mnemonic="w" alignment="center" componentref="TestComponent"> <f:icon resource="icon.gif"/> </f:label>
Here some more attributes of the <f:label>
tag are used.
A text is directly assigned, and an icon is specified by a nested
<f:icon>
tag. The alignment
attribute
determines the alignment of the label's text. With componentref
another component (which must be defined in the same builder script) can be
specified; this will associate the label with this component, so that
pressing the mnemonic key of the label sets the focus on the associated
component.
The <f:icon>
tag supports various ways to obtain an icon.
Here we use the resource
attribute which looks up the icon in
the class path. It is also possible to provide a URL from which the icon is
to be loaded. The most generic way is to specify a
Locator
object via the locator
attribute. The
Locator
is looked up in the current bean context.
Panels are UI elements whose most important functionality is to group other components together. Allthough they are visible components, usually it is not their appearance what makes them special, but the fact that they operate as a container for components defining their own layout. In Java applications complex layouts are often implemented by nesting multiple panels.
Complex nesting of panels should not be necessary for JGUIraffe applications because the provided layout managers like percent layout allow the arrangement of components in a flexible and powerful way. Nevertheless it is often convenient to group a UI into at least two panels. The example dialog box presented in the A simple example subsection used two panels: one for the main part of the dialog's UI and one for the buttons at the bottom. This is a typical pattern.
Panels are defined using the <f:panel>
tag implemented by the
PanelTag
class. The content of the panel is defined by tags in
the body of the <f:panel>
tag. In the most typical use case
a <f:panel>
tag does not have any attributes, but defines
a layout manager in its body and nests tags for other components. This looks
as in the following example:
<f:panel> <f:borderlayout/> <f:textarea name="mainText"> <f:borderconstr name="CENTER"/> </f:textarea> </f:panel>
Here a panel with a border layout is defined. A text area is placed
in the center part of its UI. (We cover tags for layouts soon.) The panel
merly acts as background for the componets placed on it. The
PanelTag
class supports some attributes which modify the
panel's appearance. Of course, the standard attributes for visible components
are available, e.g. for changing the foreground or background colors. It is
also possible to define a text as a caption for the panel or to instruct the
panel to draw a border. A more complex example is shown below:
<f:panel backColor="black" border="true" textres="PANEL" textColor="YELLOW" resgrp="testformbuilderresources"> ... </f:panel>
The JGUIraffe library ships with several custom layout manager implementations. For each of them the component builder tag library contains corresponding tags for creating layouts and defining layout constraints. The following table displays the associations between layout manager class, tag, and tag handler class.
Layout manager | Tag | Tag handler class |
---|---|---|
PercentLayout
|
<f:percentlayout> |
PercentLayoutTag
|
BorderLayout
|
<f:borderlayout> |
BorderLayoutTag
|
ButtonLayout
|
<f:buttonlayout> |
ButtonLayoutTag
|
PercentLayout
is the most powerful layout manager provided by
JGUIraffe. The corresponding <f:percentlayout>
tag
allows creating such a layout with all possible properties. There are two
variants for creating a percent layout: The first variant is to specify the
column and row constraints as strings as in the following example. This
corresponds to the constructor of the PercentLayout
class
which accepts to strings:
<f:percentlayout columns="4dlu end/preferred 3dlu start/preferred 4dlu" rows="4dlu preferred 1dlu preferred 1dlu preferred 1dlu preferred 4dlu"/>
The strings passed to the attributes must be valid constraints definitions.
The other variant uses nested <f:colconstr>
and
<f:rowconstr>
tags for defining the cell constraints.
This looks as follows:
<f:percentlayout canShrink="true"> <f:colconstr constr="4dlu"/> <f:colconstr constr="0.5cm"/> <f:colconstr constr="start/preferred"/> <f:colconstr constr="3dlu"/> <f:colconstr constr="full/preferred"/> <f:colconstr constr="3dlu"/> <f:colconstr constr="preferred"/> <f:colconstr constr="3dlu"/> <f:colconstr constr="full/preferred"/> <f:colconstr constr="4dlu"/> <f:rowconstr constr="4dlu"/> <f:rowconstr constr="preferred"/> <f:rowconstr constr="1dlu"/> <f:rowconstr constr="start/minimum(2cm)"/> <f:rowconstr constr="3dlu"/> <f:rowconstr constr="preferred"/> <f:rowconstr constr="1dlu"/> <f:rowconstr constr="preferred"/> <f:rowconstr constr="3dlu"/> <f:rowconstr constr="preferred"/> <f:rowconstr constr="1dlu"/> <f:rowconstr constr="preferred"/> <f:rowconstr constr="4dlu"/> <f:colgroup idx1="4" idx2="8"/> </f:percentlayout>
Here each nested tag defines column or row constrains for a single cell.
The corresponding tag handler classes are
PercentColConstraintsTag
and
PercentRowConstraintsTag
respective. As can be seen with the
<f:colgroup>
tag column groups can be defined. Analogously
the <f:rowgroup>
tag defines groups for rows. This also
works if the column and row constraints are defined as attributes. The
corresponding tag handler classes are
PercentColGroupTag
and
PercentRowGroupTag
. Also note the canShrink
attribute added to the <f:percentlayout>
tag. It tells
the tag that is should set the canShrink
property of the
generated layout.
When working with PercentLayout
each component to be added to
the layout must be associated with a
PercentData
constraints object. In a builder script this is
achieved by placing the <f:percentconstr>
tag in the
body of the tag defining the associated component.
<f:percentconstr>
is implemented by the
PercentConstraintsTag
class and supports attributes for setting
all properties of a PercentData
object, e.g. the position in
the grid, the span, the target cell, or additional constraints. The
following fragment shows an example of using this tag:
<f:label textres="viewset_lab_filtertypes"> <f:percentconstr col="1" row="1" spanx="2"/> </f:label>
Another layout manager supported by JGUIraffe is border layout.
Layouts of this type can be created using the <f:borderlayout>
tag implemented by the
BorderLayoutTag
class. This tag provides attributes to set all
the properties supported by a BorderLayout
object, namely the
margins and gaps between the areas of the layout. The constraints used by
BorderLayout
are strings defining one of the five possible
areas (NORTH, EAST, SOUTH, WEST, CENTER) in which a component is to be
placed. They are defined by <f:borderconstr>
tags placed
in the bodies of the tags for the associated components. The following
example demonstrates how this looks in practice:
<f:borderlayout leftMargin="0.5cm" rightMargin="0.5cm" topMargin="0.25cm" bottomMargin="0.25cm" northGap="2dlu" southGap="2dlu"> <f:label text="Label in the north"> <f:borderconstr name="NORTH"/> </f:label> <f:panel> <f:borderconstr name="CENTER"/> <!-- Content of panel in the center --> ... </f:panel> </f:borderlayout>
A ButtonLayout
is specialized on placing buttons in a dialog
box. The corresponding tag is <f:buttonlayout>
,
implemented by the
ButtonLayoutTag
class. Again the properties of the layout are
defined using attributes: it is possible to set the margins of the layout,
the gap between the buttons, and the alignment of the whole button bar
(left, right, or center). For a button layout no
constraints are needed because the buttons are placed in the layout in the
order they are added. The following listing shows an example:
<f:panel> <f:buttonlayout leftMargin="0.25cm" rightMargin="0.25cm" gap="0.5cm" align="right"/> <f:button name="btnOk" textres="viewset_btn_save" mnemonicres="viewset_btn_save_mnemo" default="true"/> <f:button name="btnCancel" textres="viewset_btn_cancel" mnemonicres="viewset_btn_cancel_mnemo" cancel="true"/> </f:panel>
We already discussed labels which are used to output
texts and icons the user cannot manipulate. Labels are immutable; once created
they cannot be changed any more. Sometimes an application wants to display
text and/or icons that can change dynamically. For this purpose the
component builder tag library provides the <statictext>
tag which is implemented by the
StaticTextTag
class.
The definition of a static text is very similar to a regular label definition.
The <statictext>
tag supports the same attributes.
However, under the hood it creates a fully-functional input component for
the text - which means that there is an associated
ComponentHandler
for it. The component handler can be accessed
from the ComponentBuilderData
object using the name specified
in the <statictext>
tag's name
attribute. It
can be cast to the specialized handler interface
StaticTextHandler
which provides convenient methods for
manipulating the text and the icon directly. Note that per default no field
is created for the static text element in the form object created by the
builder. The following fragment of a builder script shows the declaration of
a static text element:
<f:statictext name="staticText" textres="STATIC_TEXT" alignment="right"> <f:icon resource="icon.gif"/> </f:statictext>
As can be seen, a static text can be assigned a text and an icon in the same way as a label. Given this declaration, the element can be manipulated (e.g. by a form controller class) in the following way:
StaticTextHandler handler = (StaticTextHandler) getComponentBuilderData().getComponentHandler("staticText"); handler.setText("new text");
The following rule of thumb can be used to determine whether a plain label or a static text element should be used: If the element is not changed during the life time of the application, use a label. Otherwise take a static text and manipulate it accordingly.
Although there exists a ComponentHandler
for each static text
element, the handlers are not added to the form object constructed for the
UI per default. This is typically not desired because these elements are not
used to gather user input and therefore do not need a corresponding
property in the model object of the form. However, the default behavior can
be changed by setting the noField
attribute of the
<statictext>
tag to false. The tag
should then also be given a name. Then it is possible to write data from
the model object of the form into the UI elements.
There is one problem however:
StaticTextHandler
expects that it is passed data as
StaticTextData
objects; therefore the properties of the form's
model bound to static text elements must be of this type. This is
inconvenient for instance if the elements are used to display mutable text
only. In order to simplify this use case, JGUIraffe provides a
special transformer implementation which can
convert data to StaticTextData
objects:
StaticTextDataTransromer
. Among other things, this class is able
to transform a plain text string into a StaticTextData
object
which can then be used to initialize the static text element. So if this
transformer is applied, properties of the form's model object can be
simple types. We will see in a moment how transformers are assigned to UI
elements.
Often an application has the requirement that a user enters data of specific data types. As an example consider an order entry application which allows the user to enter the quantity of the product to be ordered. Input fields typically do not know about numbers, but only operate on text. So the text data used internally by the input component has to be converted to a number to be compatible with the model of the form. Analoguously, if the input fields are populated from the model of the form, another data type conversion has to be performed from number to string. This is where transformers come into play. They can handle arbitrary conversions of form data. Refer to the section Transformers and Validators for more background information.
Every input component can be assigned up to two transformers:
<f:transformer>
tags
placed in the bodies of input component tags. The tag handler implementation
class is
TransformerTag
.
Have a look at the following example:
<!-- Transformer for converting arbitrary data to string --> <di:bean name="stringTransformer" beanClass="net.sf.jguiraffe.transform.ToStringTransformer"> </di:bean> <!-- A validator/transformer for date input fields.--> <di:bean name="dateTransformer" beanClass="net.sf.jguiraffe.transform.DateTransformer"> </di:bean> ... <f:textfield name="dateField"> <f:transformer type="read" beanName="dateTransformer"/> <f:transformer type="write" beanName="stringTransformer"/> </f:textfield>
Here a field is defined that allows the user to enter a date. This is
implemented by assigning two transformers to the text field: the read
transformer is able to parse a string into a date; the write transformer
can transform arbitrary objects to strings. Both transformers are defined
using <di:bean>
tags and referenced by the
<f:transformer>
tags. Because TransformerTag
is derived from
UseBeanBaseTag
it can also directly create transformer objects.
However, using the <di:bean>
tag for this purpose is more
powerful. This also allows sharing the transformers for multiple components
(which does not cause any problems; because the UI is accessed only by a
single thread there can be no race conditions): just reference the transformer bean from multiple
<f:transformer>
tags. By the way, the transformers used
in this example are standard transformers shipped with the JGUIraffe
library. The standard transformers support many primitive data types so that
it is easy to implement text fields for entering data in these types.
One note: When working with transformers it must be ensured that the data entered by the user can actually be transformed into the target data type; otherwise the transformer throws an exception. The easiest way to do this is to add a corresponding validator to the input component. The validator is invoked first, and only if it signals that the data is valid, the transformer is called.
The handling of validators in builder scripts is pretty similar to the
handling of transformers. Actually, the tag
handler class for validators,
ValidatorTag
, is derived from the same base class as the tag
handler class for transformers. Like transformers, validators are defined by
tags nested in the bodies of input element tags. This time the name of the
tag is <f:validator>
. It supports the same attributes
for creating or referencing the validator object. In addition the
phase
attribute is available which controls when the validator
is invoked. It accepts the following values:
phase
attribute is not provided.<!-- Transformer for converting arbitrary data to string --> <di:bean name="stringTransformer" beanClass="net.sf.jguiraffe.transform.ToStringTransformer"> </di:bean> <!-- A validator/transformer for date input fields.--> <di:bean name="dateTransformer" beanClass="net.sf.jguiraffe.transform.DateTransformer"> </di:bean> ... <f:textfield name="dateField"> <f:transformer type="read" beanName="dateTransformer"/> <f:transformer type="write" beanName="stringTransformer"/> <f:validator phase="syntax" beanName="dateTransformer"/> </f:textfield>
The only change is that a <f:validator>
tag was added.
Note that it references the same bean as the tag for the read transformer.
This is because many of the standard transformers provided by
JGUIraffe are also validators. So they can play both roles.
Transformers and validators specified using the corresponding tags can be configured using additional properties. One use case is to override some properties of the transformer or validator object. As was already pointed out, transformers and validators can be shared between multiple input components. Now it is possible that many components operate with standard settings of the objects, but a single component has some special requirements - for instance, a different format for the values should be used. Instead of defining two transformer beans for the different requirements, a single one can be used. For the input component with special requirements properties can be defined which override the settings to be adapted. As an example consider a validator for numbers. We might have multiple input components that allow the user to enter arbitrary numeric values. Then there is a special component in which the number must be greater than a given value. This can be achieved with the following declarations:
<!-- A validator/transformer for numeric input fields.--> <di:bean name="numberTransformer" beanClass="net.sf.jguiraffe.transform.IntegerTransformer"> </di:bean> ... <f:textfield name="specialField"> <f:validator phase="syntax" beanName="dateTransformer"> <f:properties> <f:property property="minimum" value="25"/> </f:properties> </f:validator> </f:textfield>
Here a generic transformer/validator for numeric input is defined as a
bean. For the special input field this object is referenced, but additional
properties are provided which define a minimum value. Properties are
implemented by the
PropertiesTag
tag handler class. The single properties are
defined by nested <f:property>
tags implemented by
PropertyTag
. Sometimes the values of properties are texts to be
displayed to the user, e.g. special validation error messages. In this case,
the texts may need to be localized so that they are in the language of the
current user. This can be achieved by using the <f:localized>
tag in the body of <f:property>
. This tag is implemented by
the
LocalizedTag
class. It can be specified a resource ID which is
resolved to obtain the property's value. The following example shows how a
validator can be configured to create a customized error message which is
obtained from the application's resources:
<f:textfield name="specialField"> <f:validator phase="syntax" class="net.sf.jguiraffe.transform.IntegerTransformer"> <f:properties> <f:property property="ERR_INVALID_NUMBER"> <f:localized resid="my_error_resource"/> </f:property> </f:properties> </f:validator> </f:textfield>
Using these tags it is easy to adapt the error messages produced by
validators. All validator classes document in their Javadocs the keys of
the error messages they can produce. By setting a property with a specific
key the corresponding error message can be customized. The
<f:localized>
tag can be used to select the message text
in the correct language.
One difference between transformers and validators is that an input element
can have an arbitrary number of validators while there is only a single
read or write transformer. This can make sense if there are multiple
validation rules to be checked. To define multiple validators the
<f:validators>
tag (implemented by the
ValidatorsTag
class) is used. In the body of this tag multiple
<f:validator>
tags can be placed which are collected and
combined to a single
ChainValidator
object. This looks as follows:
<f:textfield name="fileName"> <f:validators phase="syntax"> <f:validator beanName="requiredValidator"/> <f:validator beanName="fileNameValidator"/> <f:validator class="net.sf.jguiraffe.examples.tutorial.createfile.UniqueFileNameValidator"/> </f:validators> </f:textfield>
Input fields for texts belong to the most basic components used within forms. Of course, they are used to enter arbitrary text input from the user, but by assigning appropriate transformers and validators (refer to the corresponding sections), other data types - like number or date - are supported, too.
The component builder tag library provides three tags for creating text input components for different purposes:
<f:textfield>
(implemented by the
TextFieldTag
class) produces plain single-line text input fields.
They can be configured by specifying the number of columns to be displayed
- which gives a hint about the preferred width of the text field - and the
maximum length of the text to be entered.<f:password>
(implemented by the
PasswordFieldTag
class) generates an input field for entering a
password. A password field is pretty similar to a regular text field and
allows the same configuration options. The only difference is that the
characters typed by the user are not directly displayed, but only an echo
character is printed.<f:textarea>
(implemented by the
TextAreaTag
class) is responsible for the creation of multi-line
text input fields. These fields can be used to enter larger amounts of text.
They also have built-in support for scrollbars if the text becomes larger
than the visible area on the screen. A text area can be configured with the
number of columns and rows to be displayed which influence the size of the
component. In addition the maximum length of the text to be entered can be
specified and a flag whether automatic line wrapping is to be performed.<f:textfield name="name"/> <f:textfield name="firstName" columns="20"/> <f:textfield name="street" maxlength="30"/> <f:textfield name="city" columns="20" maxlength="35"/> <f:password name="pwd" columns="8" maxlength="10"/> <f:textarea name="remarks" columns="40" rows="5" maxlength="1000" wrap="true"/>
One pitfall when working with text components is the sizing of the fields. If a percent layout is used, and the cell constraints are set to preferred size, the underlying GUI toolkit tries to determine the size of the text field. At least for Swing the results are not really satisfying. If no specific configuration options are set, Swing assumes very small sizes; you often end up with just a narrow white bar on the screen. To avoid this, you can specify the number of columns to be displayed. This makes the text field wider, but does not allow a precise sizing. With percent layout you can also specify a minimum cell size. This is well suited for working with text fields. If a text field is placed in a cell with a minimum width of - say - 4 cm, the sizing problem is solved. An alternative for determinining the size of the text component is described in the next section which is about scrolling support.
For text components JGUIraffe provides a specialized component
handler interface:
TextHandler
. Through this interface extended functionality
available for text components can be accessed. For instance, it is possible
to query or manipulate the current selection, or to interact with the system
clipboard. The ComponentHandler
returned for a text component
can be cast to TextHandler
.
Some input components allow handling of large amounts of data. For instance, a user might want to type a whole play of Shakespeare into a text area. Of course, this is more text than fits onto the screen. Therefore such components only display a portion of the data they contain and provide scroll bars allowing the user to navigate to other parts of their data. This is a well-known concept of windowing systems every user should be familiar with.
If you have ever worked with Swing, you probably know that Swing controls do
not support scrolling on their own. Rather, components like text areas or
tables have to be added to a javax.swing.JScrollPane
. The
scroll pane implements the whole scrolling logic; it queries the total size
of the wrapped component, determines the preferred size on the screen, and
displays scroll bars as necessary. The JGUIraffe library uses a
different approach: It does not provide a scroll pane component. Instead,
components that may become larger than the screen size are automatically
added to a scroll pane. This is fully transparent to the developer.
There is, however, one aspect related to scrolling the developer should be aware of: the preferred screen size of components that allow scrolling. In most cases, because of the scrolling capabilities, the screen size of such components does not really matter to make them usable: even if a text area is very small, by using the provided scroll bars the user can read the full text it contains. Because of that it is hard to determine a preferred screen size automatically, and the developer might want to give some hints. Text area components for instance can be initialized with the number of columns and rows they should display (as shown in the section Text components). Based on this information the preferred screen size can be calculated. For other components, e.g. tree views or tables, there are no such obvious ways to indicate a preferred size on the screen.
The component builder tag library provides a standard means to set
the preferred screen size of scrolling-enabled components. All tags which
define components of this category support the attributes
scrollWidth
and scrollHeight
. Here the developer
can provide a string value compatible with the
NumberWithUnit
class, e.g. 10 cm
or
100 px
. Both attributes are optional, if one is missing, the
corresponding size is set to a default value based on the component
affected. The following example shows how the preferred height is set for a
list box component (lists are described in the section
Lists and combo boxes):
<f:list name="fileTypes" multi="true" scrollHeight="3cm"> ... </f:list>
Here it is specified that the preferred screen height of the list is 3 centimeters. The layout manager will take this information into account when determining the sizes of the components in the hosting window. (Of course, the list component can later grow or shrink depending on the layout settings and the total space available.) The preferred scroll width has not been specified, so here a default value is used - in this case the value is calculated based on the widths of the list elements to be displayed.
The scrollWidth
and scrollHeight
attributes are
supported by tags for the following UI elements:
Checkboxes are typically used to let the user enter data of type boolean.
They are also part of the set of standard input components. In
JGUIraffe checkboxes are created using the
<f:checkbox>
tag which is implemented by the
CheckboxTag
class.
Using checkboxes is pretty easy. Actually the attributes supported by the
<f:checkbox>
tag are very similar to the ones available
for labels. You can
<f:icon>
tag<f:checkbox name="fine" text="Everything fine?" foreColor="blue" mnemonic="f"/> <f:checkbox name="checkIcon" backColor="black" alignment="center"> <f:icon resource="icon.gif"/> </f:checkbox> <f:checkbox name="cbx1" textres="CHECK1" resgrp="testformbuilderresources"/> <f:checkbox name="cbx2" textres="CHECK2" mnemonicres="CHECK2"/>
Sometimes a number of components logically belongs together. As an example consider a dialog box consisting of several areas where each area is specific to a use case. Depending on the state of the application or the dialog the components in some of the areas may not be available. In this scenario it would be convenient to have a means for grouping components together: groups could be created for the different areas, and then it would be possible to enable or disable all components in a group in a single step.
The component builder tag library supports groups of components. Here groups
are non-visual components. They are represented by the
ComponentGroup
class. A ComponentGroup
is a logic
construct which only stores the names of the elements that are part of the
group. The class provides some methods to obtain the actual components or
their component handlers. A few operations for manipulating the elements are
available, too. To access the components in a group, usually a
ComponentBuilderData
object must be available; this is required
for resolving the component names.
In a builder script the <f:group>
tag is used to create a
component group. As a mandatory attribute the (unique) group name must be
provided. For assigning components to groups there are two options:
group
attribute. Here
a comma-separated list with names of component groups can be specified. The
component is then added to all of these groups (a single component can belong
to multiple groups).<f:group>
tag. They are then automatically added to the
corresponding component group unless they have a different groups
attribute. This is a very compact and readable way to define a component
group because it is pretty obvious which elements are part of the group.
However, depending on layout requirements it may not always be possible to
place all group members into the tag body. In this case the other option is
needed.<f:group name="group1"/> <f:group name="group2"/> <f:textfield name="text1" groups="group1"/> <f:textfield name="text2" groups="group1, group2"/> <f:group name="group3"> <f:textfield name="text3"/> <f:textfield name="text4"/> <f:textfield name="text5" groups="group2"/> </f:group>
In the first block two <f:group>
tags with empty bodies
are used. This creates two empty component groups. The groups are populated
later on with components that have the groups
attribute set
accordingly. Creating the groups first is mandatory because the names passed
to the groups
attribute must reference existing component
groups. For the component group with the name "group3" the
alternative option is used: here all of its elements are declared in the body
of the tag. Note that one element in the body specifies a different group in
its groups
attribute, so it won't be added to this group.
After executing this builder script the component groups can be accessed from
the bean context created during the builder operation. The
ComponentGroup
class defines the static
fromBeanContext()
method for this purpose. In the subsection
Fundamental classes and
interfaces there was an example of how the enabled status of a
component could be changed based on a clicked checkbox. We can now extend
this example to manipulate a whole group of components that all depent on
this checkbox. So there is again a form controller which is registered as
change listener at the checkbox. Whenever the checkbox is changed the
event listener method determines the current state of the checkbox and
enables or disables all elements of the group accordingly. The following
listing shows this use case:
/** Constant for the name of the check box component. */ private static final String COMP_CHECKBOX = "testCheckBox"; /** Constant for the name of the dependent component group. */ private static final String GROUP_NAME = "dependentComponents"; /** * Notifies this object about a change in the data of an element. * @param e the change event */ public void elementChanged(FormChangeEvent e) { // obtain the handler for the check box (the type-safety warning can be // suppressed because we know that it is a check box handler @SuppressWarnings("unchecked") ComponentHandler<Boolean> checkHandler = (ComponentHandler<Boolean>) getComponentBuilderData().getComponentHandler(COMP_CHECKBOX); enableGroup(GROUP_NAME, handler.getData()); } /** * Helper method for enabling a component group. * * @param groupName the name of the group * @param enabled the enabled flag */ private void enableGroup(String groupName, boolean enabled) { ComponentGroup group = ComponentGroup.fromBeanContext( getComponentBuilderData().getBeanContext(), groupName); try { group.enableGroup(getComponentBuilderData(), enabled); } catch (FormBuilderException fex) { // should normally not happen log.warn("Error when enabling group " + groupName, fex); } }
In this example the helper method enableGroup()
enables or
disables all components that are part of the specified component group. When
dealing with component groups, in most cases a reference to the
ComponentBuilderData
object is needed. First the
ComponentGroup
object representing the desired group is obtained
through the static fromBeanContext()
method. The bean context
is queried from the ComponentBuilderData
object.
ComponentGroup
provides the enableGroup()
method
which sets the enabled state of all components that belong to the group.
Again a reference to the ComponentBuilderData
object is
required for this purpose to resolve the components. This method can throw
an exception if a component cannot be resolved.
After the excursus about Component groups in the last subsection we can now describe radio buttons. The understanding of component groups is a prerequisite for the understanding of radio buttons.
In JGUIraffe applications radio buttons are created using the
<f:radio>
tag which is implemented by the
RadioButtonTag
class. From its properties and its underlying
data a radio button is similar to a checkbox. Actually, the
<f:radio>
tag shares many of the attributes supported by
the <f:checkbox>
tag: again a text (either directly or as
resource ID), an icon, the alignment, and a mnemonic can be specified. Like
a checkbox a radio button can either be checked or not, therefore it is
associated with a ComponentHandler
of type boolean.
What makes radio buttons special is the fact that they make only sense in groups: here the special property that only a single radio button can be selected comes into play. This property makes radio buttons surprisingly complex and also influences the data applications wish to store for radio buttons. Typically an application does not want to model a group of radio buttons as a set of boolean flags telling for each radio button whether it is selected or not. Rather, a single information is to be stored for the whole group indicating which of the radio buttons is selected. This could be a numeric index of the selected radio button in the group or maybe a value of an enumeration class. So from a logic point of view radio buttons are not treated as single, independent input components, but they are combined to a radio group which is handled as an input component on its own.
The component builder tag library supports this use case by providing another
tag for creating radio groups: the <f:radioGroup>
tag
implemented by the
RadioGroupTag
class. <f:radioGroup>
can be
used in the same way as the <f:group>
tag: The tag has a
mandatory name
attribute that defines the name of the radio
group, and the tags for the radio buttons that are part of the group can be
placed in the body. Alternatively - if layout requirements do not permit
such a nesting - radio buttons can be added to a radio group by
specifying the group name in the groups
attribute of the
<f:radio>
tag. The following fragment shows an
example of how to define a radio group (layout-related tags have been
omitted):
<f:radioGroup name="sortColumn"> <f:radio name="sortColumnName" textres="viewset_rad_sortcolname"/> <f:radio name="sortColumnDate" textres="viewset_rad_sortcoldate"/> <f:radio name="sortColumnSize" textres="viewset_rad_sortcolsize"/> </f:radioGroup>
The basic idea is now that only for the radio group as a whole a field is
added to the current Form
object, not for the single radio
buttons. Therefore a ComponentHandler
managing the data of the
radio group is created. Per default, this is an instance of the
DefaultRadioButtonHandler
class. DefaultRadioButtonHandler
has the data type java.lang.Integer
; it determines the index of
the selected radio button in the group and returns it as its data. This may
be appropriate for simple use cases, but has the danger that the meaning of
the index changes if the radio buttons are rearranged in the group. So you
might want to provide a different ComponentHandler
implementation.
This can be achieved by nesting a <f:componentHandler>
tag (implemented by the
ComponentHandlerTag
class) in the body of the
<f:radioGroup>
tag. Here another component handler
implementation can be specified. Writing a custom component handler for a
radio group is easy because with
AbstractRadioButtonHandler
there is already a base class providing
the major part of the required functionality. Concrete subclasses only have
to define the mapping between the index of the selected radio button in the
group and the data value to store for this group.
As a simple example consider a radio group with two radio buttons for the
options "Yes" and "No". Because the user has only two
options this can be modeled using a boolean value. (This radio group is
equivalent to a single checkbox, but let's assume that due to layouting
considerations radio buttons should be used.) So for the radio group a
ComponentHandler
implementation is needed that maps the data of
the group to a java.lang.Boolean
value. First we present the
XML definition of the radio group:
<f:radioGroup name="yesOrNo"> <f:componentHandler class="net.sf.jguiraffe.examples.tutorial.viewset.BooleanRadioButtonHandler"/> <f:radio name="radioYes" text="Yes"/> <f:radio name="radioNo" text="No"/> </f:radioGroup>
The group is defined by two <f:radio>
tags in the body of
a <f:radioGroup>
tag. New is the
<f:componentHandler>
tag which installs a custom component
handler for the radio group. The following listing shows the implementation
of the BooleanRadioButtonHandler
class:
package net.sf.jguiraffe.examples.tutorial.viewset; import net.sf.jguiraffe.gui.builder.components.model.AbstractRadioButtonHandler; public class BooleanRadioButtonHandler extends AbstractRadioButtonHandler<Boolean> { /** Constant for the index of the YES button.*/ private static final int IDX_YES = 0; /** Constant for the index of the NO button.*/ private static final int IDX_NO = 1; /** * Creates a new instance of {@code BooleanRadioButtonHandler} */ public BooleanRadioButtonHandler() { super(Boolean.TYPE); } /** * Returns the index of the button with the specified value. * * @param value the data value of the radio group * @return the index of the selected radio button */ @Override protected int getButtonIndex(Boolean value) { return value.booleanValue() ? IDX_YES : IDX_NO; } /** * Returns the value of the radio button with the specified index. * * @param idx the index of the selected radio button * @return the corresponding value for the whole radio group */ @Override protected Boolean getDataForButton(int idx) { return idx == IDX_YES; } }
The two methods getButtonIndex()
and getDataForButton()
perform the mapping between the index of the selected radio button and the
data to be stored for the group. They are called by the base class when
this information is needed. Here the index of the "Yes" button is
mapped to the boolean value true and the index of the "No"
button is mapped to false (and vice versa). More complex mappings
can be done analogously. For instance, the index of the selected button
could be mapped to an enumeration value.
As was stated before, single radio buttons are not added as fields to the
current form per default. If this is desired, the noField
attribute must be set to false explicitly.
One final note: So far validators were only mentioned in the context of text input fields. However, there is no reason why they cannot be applied to other input elements like radio groups as well. One use case would be to add a required validator to a radio group. This would ensure that the user has to select a button.
List boxes and combo boxes allow the user to select items from a given
number of options. They are somewhat similar to a group of radio buttons,
but typically they are used if there are more options available than would
be suitable for a group of radio buttons. Apart from their visual
representation list boxes and combo boxes have many things in common. In
the JGUIraffe library they both use the same underlying data model:
an object implementing the
ListModel
interface.
ListModel
plays a similar role
as the javax.swing.ListModel
interface for Swing applications.
It is not a complicated interface. It provides methods for querying the
number of elements contained in the model and for querying the elements
themselves - as value objects and as display objects. When a list or combo
box is rendered the display objects returned by the model are used. When
the data of such an input element is queried the data object is returned.
Thus the items rendered on the screen can differ from the data that is
actually stored for the input element. Refer to the Javadocs of the
ListModel
interface for a more detailed explanation.
The component builder tag library defines the tags <f:list>
and <f:combo>
for the definition of list boxes and combo
boxes. They are implemented by the classes
ListBoxTag
and
ComboBoxTag
. Both tags require that the list model with the items
to be displayed is set. The following options exist:
modelRef
attribute can be set to the name of a bean
implementing the ListModel
interface. This bean is looked up in
the current BeanContext
; so it may have been created for
instance by a <di:bean>
tag in the same builder script or
passed to the builder in form of a builder property.<f:list>
or <f:combo>
tag can define
the list model. We will see which tags exist for this purpose in a moment.Let's start with an example of the first option where the model is obtained from the current bean context. The example is about a file browser application. This application has a combo box that lists the root file systems available on the local machine (e.g. the existing drive letters on a windows system). The user can select a root file system whose content will then be displayed by the application. The fragment of the builder script that defines the combo box and its model looks as follows:
<di:bean name="fileSystemModel" beanClass="net.sf.jguiraffe.examples.tutorial.mainwnd.FileSystemListModel"> </di:bean> <f:combo name="comboFS" modelRef="fileSystemModel" editable="false"/>
Here first a bean for the model is defined. Later on the model bean is
referenced by the <f:combo>
tag. Because the user can
only select an existing item and cannot enter arbitrary text into the
combo box's text field the editable
attribute is set to
false. If a list was to be used rather than a combo box, the
declaration would be very similar: the <f:combo>
tag would be
replaced by a <f:list>
tag. The editable
attribute would be dropped because lists are per se not editable:
<f:list name="listFS" modelRef="fileSystemModel"/>
The implementation of the FileSystemListModel
class
representing the model of the combo box is shown below:
package net.sf.jguiraffe.examples.tutorial.mainwnd; import java.io.File; import net.sf.jguiraffe.gui.builder.components.model.ListModel; public class FileSystemListModel implements ListModel { /** Stores the available root objects. */ private final File[] roots; public FileSystemListModel() { roots = File.listRoots(); } /** * Returns the display object at the given index. This implementation * returns the name of the {@code java.io.File} representing the file system * root at the given index. * * @param index the index * @return the display object for this model element */ @Override public Object getDisplayObject(int index) { return roots[index].getPath(); } /** * Returns the data type of the model elements. For this model * implementation {@code java.io.File} is the data type. * * @return the data type of this model */ @Override public Class<?> getType() { return File.class; } /** * Returns the value object at the given index. This implementation returns * the {@code java.io.File} object representing the file system root at the * given index. * * @param index the index * @return the value object at this index */ @Override public Object getValueObject(int index) { return roots[index]; } /** * Returns the size of this model. * * @return the size of this model */ @Override public int size() { return roots.length; } }
This implementation is not too complicated. The actual data the model
operates on is already obtained in the constructor by calling
File.listRoots();
. The other methods mainly access the array
returned by this operation. getDisplayObject()
returns a
string, namely the path of the File
object representing a root
file system. getValueObject()
returns the File
object itself. The ComponentHandler
implementation of the
combo box calls getValueObject()
for the selected item and
stores the result in the form's model. Therefore the data type of this
model is java.io.File
.
The other option to define the list model is to use special tags that are
able to create a list model and to assign it to an enclosing
<f:list>
or <f:combo>
tag. Currently
the component builder tag library supports the
<f:textListModel>
tag which is implemented by the
TextListModelTag
class. As the name implies, this tag creates a
simple text-based model. The model's content is defined by nested
<f:listModelItem>
tags. Here the implementing tag handler
class is
ListModelItemTag
. In the body of a <f:textListModel>
tag an arbitrary number of <f:listModelItem>
tags can be
placed. Each tag defines one item of the list model. Both the display text
and the value object of the item can be defined. The Javadocs of the
ListModelItemTag
class describe all options available. The
following fragment shows the definition of such a list model:
<f:list name="fileTypes" multi="true"> <f:textListModel type="java.lang.String"> <f:listModelItem text="*.exe" value="exe"/> <f:listModelItem text="*.txt" value="txt"/> <f:listModelItem text="*.java" value="java"/> <f:listModelItem text="*.scala" value="scala"/> <f:listModelItem text="*.xml" value="xml"/> <f:listModelItem text="*.jelly" value="jelly"/> <f:listModelItem text="*.html" value="html"/> </f:textListModel> </f:list>
In this example a list is defined that allows the user to select multiple
file types. The <f:listModelItem>
tags define the items
to be displayed in the list using the text
attribute. In this
case the values are strings, too. They could have been other objects as well
- their data type must correspond to the type
attribute of the
<f:textListModel>
tag. This approach - the definition of
a list model directly in the builder script - can be used if the number of
list items is fix and known beforehand. Otherwise, it is preferrable to
create a specialized list model class as demonstrated in the previous
examples.
The last example also demonstrates the multi
attribute of the
<f:list>
tag. A value of true means that the user
can select multiple list elements; otherwise the list allows only a single
selection. The multi
attribute influences the data stored in
the form model for the list: if the list supports only single selection,
the form model stores a single object of the data type of the list's model.
In multi-selection mode an array of this data type is stored. So for the
example above with the file types, the form model would have stored an
array of strings for the list.
A progress bar is typically used to give feedback to the user about a longer
running operation. Usually they are not used in forms for entering data.
Nevertheless the component builder tag library supports these components
with the <f:progressbar>
tag which is implemented by the
ProgressBarTag
class.
When a progress bar is defined the following properties can be set:
<f:progressbar name="progress" text="progress" min="1" max="200" allowText="true"/>
A progress bar makes only sense if it can be updated at runtime during a
long-running operation. To do this in a convenient way there is a specific
ComponentHandler
implementation for progress bars:
ProgressBarHandler
. This handler implementation allows setting
the progress bar's current value and text directly. From a
FormController
implementation a progress bar could be
manipulated as follows:
ProgressBarHandler handler = (ProgressBarHandler) getComponentBuilderData().getComponentHandler("progress"); handler.setValue(counter++);
Here we assume that this code snippet is executed repeatedly during a
long-running operation. Each time it is executed another step of the
operation is completed, so a counter is incremented. The value of the
progress bar is set to this counter value. When the operation is finished
the maximum of the progress bar should be reached. Note that for a progress
bar per default no field is added to the current form. If this is desired,
the noField
attribute of the <f:progressbar>
tag must be set to false explicitly. In this case the progress bar's
value is stored in the form's model as an integer number.
A slider allows the user to enter a numeric value in a given range. The user just drags the marker of the slider to the position that corresponds to the desired value. The value is stored in the model of the form as an integer value. There are some similarities between sliders and progress bars; both are initialized with a range of allowed values and display a current value. In contrast to a progress bar a slider allows the user to enter a value; hence it is a full-featured input component.
Sliders are defined using the <f:slider>
tag which is
implemented by the
SliderTag
class. The tag supports attributes for setting the
following properties:
<f:slider name="duration" min="0" max="30" minorTicks="5" majorTicks="10" showTicks="true" showLabels="true" tooltipres="bgtask_duration_tip">
The following figure shows a dialog with a slider that corresponds to this definition:
Sometimes a single dialog should be used to let the user enter data related to several subtopics. An example could be the settings dialog of an application: There may be different areas with specific settings, e.g. general settings, security settings, directory settings, database settings, and so on. Rather than having a single dialog for each settings category, it may be more comfortable for the user to have a central settings dialog which allows navigating to the settings categories available. One way to achieve this is using a tabbed pane: The central settings dialog can define tabs for each category of settings. By clicking one of these tabs the corresponding page with settings is displayed.
The component builder tag library provides the
<f:tabbedpane>
tag to define a dialog with multiple tabs.
<f:tabbedpane>
is implemented by the
TabbedPaneTag
class. This tag defines a frame in which single tabs can
be embedded. Tabs are defined using nested <f:tab>
tags.
Here the implementation class is
TabTag
. The following fragment of a builder script shows an
example:
<f:tabbedpane name="tabs" placement="bottom"> <f:tab titleres="viewset_tab_colors"> <f:panel> ... </f:panel> </f:tab> <f:tab titleres="viewset_tab_sort"> <f:panel> ... </f:panel> </f:tab> <f:tab titleres="viewset_tab_filters"> <f:panel> ... </f:panel> </f:tab> </f:tabbedpane>
The whole tabbed pane is defined by the enclosing
<f:tabbedpane>
tag. As an attribute the placement of the
tabs can be specified - per default they are placed at the top of the pane.
The tabbed pane can also be assigned a name, but this is optional.
(Technically a tabbed pane is a regular input component and thus has a name.
The data of this input component is the integer index of the selected tab.
However, this information is normally not stored in the model of a form.
Hence the noField
attribute is set to true initially.
It can be set to false explicitly to alter this behavior.)
In the body of the <f:tabbedpane>
tag an arbitrary number
of <f:tab>
tags can occur each defining a page of the
tabbed pane. <f:tab>
supports attributes similar to a
label: It is possible to set the text of the tab
(either directly or as a resource ID), to define an icon and a mnemonic.
The content of the tab is defined in the body of the
<f:tab>
tag. Here a single component tag can be placed.
Because a tab represents a whole page of a dialog and is usually more
complex typically a <f:panel>
tag is used here allowing
the definition of an arbitrary complex UI.
At runtime only a single tab is active, and the content of this active tab is displayed. When the user navigates to another tab, the content of the old active tag is made invisible, and the content of the new one appears.
Buttons play an important role in graphical user interfaces. Their main use case is within dialog boxes where they allow the user to close the window - either the data entered by the user is to be saved or editing is canceled. Generally speaking, a button represents an action which is executed when the user presses the button. Thus buttons also appear in tool bars where they provide fast access to central functionality of an application.
In this sub section we focus on the definition of command buttons that can appear in dialog or frame windows. Tool bars will be covered when the action builder tag library is discussed. This tag library is also responsible for the definition of listeners to be invoked when a button is clicked. The component builder tag library only provides tags for defining buttons, but does not deal with the events generated by button clicks.
Buttons are defined using the <f:button>
tag implemented
by the
ButtonTag
class. When used within dialogs they are frequently
arranged by a button layout (see Layouts) as
demonstrated by the following fragment:
<!-- The button bar --> <f:panel> <f:borderconstr name="SOUTH"/> <f:buttonlayout/> <f:button name="btnOk" textres="viewset_btn_save" mnemonicres="viewset_btn_save_mnemo" default="true"/> <f:button name="btnCancel" textres="viewset_btn_cancel" mnemonicres="viewset_btn_cancel_mnemo" cancel="true"/> </f:panel>
Here a panel is defined acting as the button bar of a dialog window. The panel is placed in the south of the dialog window. It uses a button layout with default settings and contains two buttons: an OK and a Cancel button.
The definition of a button is very similar to the definition of a
Label. With the typical attributes the button's text,
its mnemonic, and its alignment can be specified. With a nested
<f:icon>
tag the button can be assigned an icon. The
default
and cancel
attributes are specific to
buttons: Using these boolean attributes, one button of the window can be
marked as the default or cancel button respective. This button can then be
triggered when the user performs a UI-specific action (typically pressing
Enter in case of the default button or Escape for the
cancel button) - even if the button does not currently have the keyboard
focus. Buttons can also be assigned a command. This is optional
and may be of interest for event listeners registered at the button because
the events generated by the button contain this command. But this will be
discussed in more detail later.
Technically, buttons are input components, but they usually do not store
data in the associated form. The ButtonTag
class sets the
noField
attribute to true per default. Actually a button
maintains a boolean value indicating whether the button is pressed or not.
This is used by a special button type: a toggle button. Unlike a
command button a toggle button typically does not trigger an action. Rather,
if pressed by the user, it stays pressed until it is clicked again. This is
semantically equivalent to a checkbox. For the definition of toggle buttons
the component builder tag library provides the
<f:toggleButton>
tag implemented by the
ToggleButtonTag
class. <f:toggleButton>
has
the same attributes as <f:button>
(except for the
default
attribute which is specific to command buttons). For
the produced toggle button input components fields of type boolean are added
to the associated form. The following snippet shows the definition of a
toggle button:
<f:toggleButton name="toggle" text="Toggle" command="tog"/>
As was already pointed out, the reaction of button clicks is subject of a
later section. Here we just want to mention that
Form controllers provide automatic support
for buttons that save or cancel a form: The FormController
class has the properties btnOkName
and
btnCancelName
. Here the names of the OK and the Cancel button
can be provided. The controller then registers itself as event listener at
these buttons and handles their events appropriately. This will be discussed
in more detail in the section about windows.
Sometimes it is necessary to divide the space available in a window on multiple components. Examples include a mail application showing a list of received mails in the top and the text of the currently selected mail in the bottom, or a directory browser application that displays a directory tree in the left and the content of the current directory in the right. Because the application does not how much space the user prefers for the single components it typically provides a bar which can be dragged to adopt the sizing of the components. This is what splitters are for.
In JGUIraffe applications a splitter can be added to a container component, e.g. a window (acting as top-level container) or a panel. It splits the space available either horizontally or vertically and provides it to exactly two subcomponents. The subcomponents can again be containers like panels with an arbitrary number of nested elements. It is even possible to nest splitter components to an arbitrary depth. While up to three nested splitters probably make sense (for instance a tree structure with mail folders on the left, a list with the mails received in the top, and the content of the selected mail in the bottom), the user should not be confused with too many nested splitters. The following code fragment shows a declaration of a user interface that works with splitters:
<f:splitter pos="50" size="10" resizeWeight="0.5"> <f:panel> <!-- Content of the panel in the left --> ... </f:panel> <f:splitter pos="25" orientation="horizontal" resizeWeight="1"> <f:panel> <!-- Content of the panel in the top --> ... </f:panel> <f:panel> <!-- Content of the panel in the bottom --> ... </f:panel> </f:splitter> </f:splitter>
This examples skips the actual content of the panels comprising the UI, but
it demonstrates all configuration options for splitter components. As shown
in the code fragment above, splitters are defined using the
<f:splitter>
tag which is implemented by the
SplitterTag
class. Because splitters
are no input components - no data is stored for them in the model of the
current form - they do not require a name
attribute. In
addition to the standard attributes supported by all graphical components
the following properties of a splitter can be configured:
<f:splitter>
tag will throw an exception.
Splitters use the minimum size of the components they divide to determine
constraints for their sizing. If a component already has its minimum size,
the splitter cannot be dragged any further. This is a caveat, especially
when working with percent layout managers. If not
specified otherwise, the preferred size of a panel using percent layout is
also its minimum size. So if two such panels are to be split, after they
have been initially layouted (and set to their preferred size), the splitter
cannot be moved! Both components have already reached their minimum.
Fortunately, the solution is easy: Just make sure that the
canShrink
property of the percent layouts involved is set to
true. In this case, the minimum size of the panels can be smaller
than the preferred size. It is then possible to drag the splitter, at least
until a "hard" limit is reached, e.g. the minimum size of a label.
Tree views are useful if any kind of hierarchical data is to be presented to
the user, for instance a file system, a package structure, or the table of
contents of a book. The model associated with the tree view must be capable
of storing hierarchical data of these types. Popular UI libraries typically
implement their own hierarchical data structure serving as a tree component's
data model. Swing, for instance, uses the javax.swing.tree.TreeModel
interface to define a hierarchical nodes structure. With the
javax.swing.tree.DefaultTreeModel
class a default implementation
of this interface is available.
The JGUIraffe library takes a slightly different approach: Rather
than defining another proprietary hierarchical data structure, the library
makes use of an already existing one: the HierarchicalConfiguration
class from the Apache
Commons Configuration project. Because Commons Configuration is
used for the implementation of the configuration support anyway, no
additional dependency is required. The HierarchicalConfiguration
class provides a rich API for querying and manipulating its data; all these
methods can be directly used when working with tree views in a
JGUIraffe application.
In Commons Configuration the addProperty()
method can
be used to add data to a HierarchicalConfiguration
object. This
method expects a key string pointing out the location of the new property in
the hierarchical data structure. The string consists of several node names
separated by the node separator character - which is the dot (".")
per default, but can be changed to another character if needed. That way
nodes are added to the configuration object. The tree view produced by the
component builder tag library displays the node structure maintained by the
HierarchicalConfiguration
object. The nodes can also have
values, but these values are not displayed. They can be easily queried,
however. This can be used for instance to associate tree nodes with
application-specific data; an event handler reacting on a change in the
selection of a tree view could then access this data.
To make this clearer let's have an example. Have a look at the following
lines of code that create a HierarchicalConfiguration
object
and populate it with nodes representing some classes of the
JGUIraffe library:
HierarchicalConfiguration config = new HierarchicalConfiguration(); config.addProperty("net.sf.jguiraffe.gui.app.Application", Boolean.TRUE); config.addProperty("net.sf.jguiraffe.gui.app.ApplicationContext", Boolean.TRUE); config.addProperty("net.sf.jguiraffe.gui.builder.Builder", Boolean.TRUE); config.addProperty("net.sf.jguiraffe.gui.builder.BeanBuilder", Boolean.TRUE); config.addProperty("net.sf.jguiraffe.transform.Transformer", Boolean.TRUE); config.addProperty("net.sf.jguiraffe.transform.Validator", Boolean.TRUE);
The fully-qualified class names are used as property names. Because the dot
is the nodes separator character, the configuration creates corresponding
node structures. The addProperty()
method requires a value to
be passed for each property to be added. Here we do not care about the
value, so we just pass Boolean.TRUE
. If we had some data to be
associated with the class names, we would have provided it here. If now the
configuration object initialized this way becomes the model of a tree view,
results will look as follows:
After we have discussed the way the model of a tree view is represented by a
HierarchicalConfiguration
object it is time to explain how such
components are actually created. The component builder tag library uses the
<f:tree>
tag for this purpose which is implemented by the
TreeTag
class. The tag supports a set of attributes for adjusting
the resulting tree's appearance and behavior. Also the model of the tree
must be specified through the model
attribute. The following
fragment from a builder script shows an example:
<f:tree name="myTree" model="treeModel" editable="false" rootVisible="true" multiSelection="false"> </f:tree>
The model
attribute is mandatory. The string provided here is
passed to the dependency injection framework to
be resolved; it must refer to a bean of class
HierarchicalConfiguration
. It is up to a concrete application
how this bean is created. One way would be to use the tags of the dependency
injection tag library for this purpose. You can also create the
configuration programmatically and put the object into the properties of a
BuilderData
object before you invoke the builder. This looks as
follows:
ApplicationBuilderData builderData = application .getApplicationContext().initBuilderData(); HierarchicalConfiguration config = new HierarchicalConfiguration(); // populate the configuration ... // add the object to the properties for the builder builderData.addProperty("treeModel", config); // invoke the builder Window window = builder.buildWindow(locator, builderData);
Because a tree view is a more complex component the simple
ComponentHandler
interface is not sufficient to use all the
features supported. Therefore, with the
TreeHandler
interface a specialized handler interface exists.
TreeHandler
provides methods for accessing specific properties
of a tree and for manipulating its state and appearance. Using these methods
the developer can
A specific node in the model of the tree view is represented by the
TreeNodePath
class. This class collaborates closely with a
HierarchicalConfiguration
object. It stores the configuration
nodes on a path from the root node to a certain target node. It has methods
to query the nodes on the path and supports transforming the whole path to a
corresponding configuration key - this is especially useful if the data
stored in the configuration for this tree node is required.
In many use cases trees are not used in forms to enter user data. Because of
this, the data of a tree view component is per default not stored in the
form's data; i.e. the noField
attribute is initialized with
true. This can be changed by manually setting the attribute to
false. In this case the data stored for the tree view depends on the
multi-selection property: If the tree supports only single selection (this
means that only a single node can be selected at once), its data consists of
a single TreeNodePath
object. Otherwise, an array of
TreeNodePath
is stored.
Often it is required to use special icons for the nodes of a tree view. If
not specified otherwise, the tree view uses a default icon for leaf nodes
and another one for non-leaf nodes. JGUIraffe provides the
TreeIconHandler
interface which allows full control over the icons
to be displayed. The basic idea is that an implementation of
TreeIconHandler
is specified using the iconHandler
attribute of the <f:tree>
tag (a bean with this name is
looked up in the current bean context). The icon handler is passed the
information available for the current node and returns the name of an icon
to be displayed for it. The icons themselves must have been provided to the
tree view using nested <f:treeIcon>
tags (refer to the
TreeIconTag
class). An example of using this tag is shown below:
<f:tree model="myTreeModel" name="myTree"> <f:treeIcon name="LEAF"> <f:icon resource="myLeafIcon.gif"/> </f:treeIcon> <f:treeIcon name="BRANCH_EXPANDED"> <f:icon resource="myExpandedIcon.gif"/> </f:treeIcon> <f:treeIcon name="BRANCH_COLLAPSED"> <f:icon resource="myCollapsedIcon.gif"/> </f:treeIcon> </f:tree>
Here the default TreeIconHandler
implementation is used. This
implementation supports three different icons: for leaf nodes, for collapsed
branch nodes, and for expanded branch nodes. The <f:treeIcon>
tags define corresponding icons for these node types. The name
attributes of the tags specifies the type of node for which an icon is to be
set. Here the default names supported by the default icon handler are used.
If you set a custom icon handler, you are free to use whatever icon names
you want. The only caveat is that the names provided by the
<f:treeIcon>
tags correspond to the names supported by
the handler.
Tables certainly belong to the more complex components that can be added to the UI of an application. They are typically used to display larger amounts of structured data. This makes them a bit similar to list boxes, but in contrast to the more simple list boxes tables can display an arbitrary number of columns. Therefore they require a different model.
List boxes obtain the data to be displayed from an object implementing the
ListModel
interface. This interface allows access to a value and
a display object for each element of the list. This information is
sufficient for a list box control to render itself and to maintain its data.
For a table situation is more complex because the data to be displayed in
the single columns is needed. This is the reason why interfaces representing
models for tables typically have a bunch of methods. For instance, Swing's
javax.swing.table.TableModel
interface defines methods for
querying data of the different cells of the table.
JGUIraffe takes a different approach. In fact, the table models used here are even simpler than list models: they are simple collections with Java beans. The JGUIraffe table implementation borrows some features from the implementation of Forms, especially the mechanism for accessing properties of the form's model- which typically is a Java bean - and binding their values to UI components. In the case of tables the properties of the Java objects stored in the model collection are mapped to the columns of the table. This makes it very easy to display the data an application is processing because in most cases no conversion to specialized model objects is necessary: Typically application data is available in collections of data objects anyway, e.g. as a list with entity objects representing the result of a database query. Such a list can directly be used as the model of a table.
For the definition of tables the component builder tag library provides the
<f:table>
tag which is implemented by the
TableTag
class. The columns of the table are defined by nested
<f:column>
tags (here the implementation class is
TableColumnTag
). The following example fragment from a builder
script shows how a simple (read-only) table can be declared:
<f:table model="tabModel" selectionBackground="blue" selectionForeground="white" name="testTable"> <f:column name="icon" width="20px" columnClass="Icon"/> <f:column name="firstName" percentWidth="35" headerres="TABLE_COL_FIRSTNAME"/> <f:column name="lastName" columnClass="String" percentWidth="40" headerres="TABLE_COL_LASTNAME"/> <f:column name="birthDate" columnClass="DATE" percentWidth="25" headerres="TABLE_COL_BIRTHDATE"/> </f:table>
This script generates a table with four columns: an icon, first name, last name, and birth date. While the reference of all attributes supported by the tags is available in the Javadocs, we want to highlight some points:
<f:table>
tag is the reference to the table's model. A bean with the name specified
here is looked up in the current bean context. It must be a collection.<f:table>
tag provides some specific
attributes for setting further properties of the table, e.g. the colors of
the selection.<f:column>
tag defines a column of the table.
The name
attribute of the tag corresponds to the property of
the beans in the table's model that is read for rendering this column. In
the example above we deal with beans representing persons. Because there is
a column named firstName the objects in the table's model must have
a property named firstName, too - which means there has to be a
public method with the name getFirstName()
and no arguments
that returns the data to be displayed in this column. Note that the actual
class of the objects in the model does not matter; access to the data is
done purely through the names of the properties.columnClass
attribute information about the type of data to
be displayed in that column can be provided. This gives a hint to the
underlying table implementation how to render the data in that column. For
instance, the icon column has set its type to ICON
to
ensure that an image is painted. Defining the column class is optional,
but it is recommended to make sure that the table actually displays the data
as expected. The following options are available:
ColumnClass
enumeration class which defines a number of logic
column types. Using the constants defined by this class is the most
portable way of specifying a column class because JGUIraffe maps
these types to the renderes supported by the underlying UI toolkit.columnClass
attribute is provided, a generic renderer is
used for this column.<f:column>
tag supports both fixed and
relative column widths. A fixed width is specified using the
width
attribute. Here a string compatible with the
NumberWithUnit
class can be provided. Such columns keep their
width when the table is resized. Of course, the user can manually drag the
columns' margins to change their size. With the percentWidth
attribute a relative width can be set. When calculating the widths for the
columns the total width of the table is determined, then the sizes of
columns with a fixed width are subtracted. The remaining size is distributed
to the columns with a relative width according to their percental ratio.
Note that a table with at least one column with relative width always
adjusts the size of its columns to fit into the available space. This is
not the case for tables with only fixed-width columns: here the table can
become wider or smaller than the space available. Columns with no width
specification are assigned a relative width. The example script demonstrates
a typical pattern for setting the column widths: Most data columns are
assigned a relative width according to the size of the information they have
to display. A fixed width is used only for special columns like the icon
column.
Typically tables are used to display - and optionally edit - the data stored
in its model. They do not store data themselves in the model of the current
form. This can be changed by setting the noField
attribute of
the <f:table>
tag explicitly to false. In this
case the data stored by the table in the form's model depends on its
selection model: If single selection is set, the index of the selected row
is stored as an integer. Tables with multiple selection mode enabled (this
can be achieved by setting the multiSelection
attribute to
true) store an array of integers for the indices of all selected
rows.
Tables provide some specific functionality which cannot be accessed through
the ComponentHandler
interface. Therefore with
TableHandler
a specialized handler interface exists. This
interface provides methods for querying and setting the table's selection,
for accessing additional properties, and to notify the table about changes
in its data model. This is important for making changes in the data model
visable. Because the model of a table is a plain collection the table cannot
figure out if an external component manipulates this data. It lies in the
responsibility of the developer to pass notifications to the table by
calling any of the corresponding methods (rowsInserted(),
rowsUpdated(), rowsDeleted(), tableDataChanged()
) provided by the
handler interface. Among those methods tableDataChanged()
is
the most generic one. It causes an update of the whole table. The other
methods are more specific - if you know exactly what has changed, it is more
efficient to use them because then only the relevant parts of the table need
to be updated.
If not specified otherwise, tables are read-only. This can be changed by
setting the editable
attribute of the <f:table>
tag to true. In this case default editors are enabled for all
columns of the table; the editors used depend on the data type of the
associated columns. It is also possible to override the editable
flag for specific columns. The <f:column>
tag also
supports an editable
attribute. The default value of this
attribute is the value of the editable
attribute of the
enclosing <f:table>
tag. By setting this attribute for
single columns, it is easy to turn on editing functionality on a per column
base.
It was already discussed how the columnClass
attribute can be
used to influence the renderer or editor used for a column. However,
sometimes the default renderers available do not fulfill all the requirements
of an application. In this case there are multiple options.
In the introduction for tables we mentioned that the columns of a table
behave in a similar way as the
fields of a form: they can be bound to properties of the form's model. In
fact this similarity goes a bit further because it is also possible to
assign transformers or validators to a
column. These objects work exactly the same for columns as for regular
input components in a form. When a column is rendered the corresponding
data value is obtained from the table model, and then the write transformer
is applied to it. When a cell is edited the read transformers and the
validator associated with the current column are invoked. So all the
validation logic provided for Form
objects is available to
tables, too. In the listing below transformers are defined for some
columns of a table:
<f:table name="table" model="tableModel" multiSelection="true"> <f:column name="icon" width="20px" columnClass="Icon"/> <f:column name="name" percentWidth="50" headerres="main_tcol_name" columnClass="String"/> <f:column name="lastModified" percentWidth="30" headerres="main_tcol_modified"> <f:transformer class="net.sf.jguiraffe.transform.DateTimeTransformer" type="write"/> </f:column> <f:column name="size" percentWidth="20" headerres="main_tcol_size"> <f:transformer type="write" class="net.sf.jguiraffe.examples.tutorial.mainwnd.FileSizeTransformer"/> </f:column> </f:table>
In this example a table is defined that displays information about the files
in a directory. The columns for the icon and the file name use default
renderers. For the date column the default DateTimeTransformer
provided by JGUIraffe is used. The column for the file size is
assigned a custom transformer.
The component builder tag library provides some more tags that allow an even more powerful customization of the renderers and editors used for the cells of a table. These tags basically associate a column with an arbitrary graphical component. This component can also be a panel with multiple nested input components, so that actually multiple properties of the table's model objects can be displayed in a single column. Have a look at the following example:
<f:column name="name" headerres="TABLE_COL_NAME"> <f:colrenderer> <f:panel> <f:label text="First name:"/> <f:statictext name="firstName"/> <f:label text="Last name:"/> <f:statictext name="lastName"/> </f:panel> </f:colrenderer> <f:coleditor> <f:panel> <f:label text="First name:"/> <f:textfield name="firstName" maxlength="25"/> <f:label text="Last name:"/> <f:textfield name="lastName" maxlength="25"/> </f:panel> </f:coleditor> </f:column>
Here a column for the name of a person is defined which actually contains
two sub fields: the first name and the last name. Two new tags,
<f:colrenderer>
(implemented by the
ColumnRendererTag
class) and <f:coleditor>
(implemented by the
ColumnEditorTag
class) are used to define the content of the
column. In the body of these tags an input component tag can be placed, here
a <f:panel>
tag is used which is in turn a container for
other input component tags. The panels have labels and text fields for the
data to be maintained. In the case of the column renderer
static text components are used because they are
read-only per se. (Note that layout-related stuff was removed from the
example to focus on the essentials; in practice a layout definition would be
required.)
The component defined by the <f:colrenderer>
tag is
displayed per default when the table is just shown. When the user clicks
into a cell it is replaced by the component defined by the
<f:coleditor>
tag. Sizing and layout of these components
should be in sync to avoid disturbing effects when switching between these
components.
The tag handler class we discuss in this sub section is a bit special
because it does not create a graphical user interface element. Rather, it
allows the registration of custom
data type converters which can then be used by the dependency
injection framework. The reason why it is part of the component builder
tag library and not of the dependency injection builder tag library is
because the latter is designed to require only a
BeanBuilder
for the execution of scripts. However, extended
functionality for registering converters at the execution time of scripts is
available for
Builder
implementations only. Note: It is possible to use
custom data type converters with a BeanBuilder
by passing in an
InvocationHelper
object which is configured accordingly. But this
requires code to register converters at the corresponding
ConversionHelper
instance. With a Builder
object
converters can be defined in a script in a declarative way.
As is described in the data type converters sub section, converters are called by the dependency injection framework when an object defined in a builder script (e.g. a property or parameter value) has to be converted to a specific target data type. For basic Java types standard converters are available, but when an application operates on beans with special data types, registering custom converters can simplify bean declarations and help to make builder scripts less verbose.
In order to use a custom data type converter, the converter class has to be
implemented first. This means that the Converter
interface of
Commons Beanutils has to be implemented. With the converter class
in place, registering an instance is simply a matter of placing the
<f:converter>
tag (implemented by the
ConverterTag
class) at the beginning of a builder script.
ConverterTag
is derived from the
UseBeanBaseTag
class. Thus it provides multiple ways of defining
the converter object. For instance, if the converter class has a default
constructor, it can be directly created by the tag as shown in the following
example:
<f:converter class="com.mypackage.convert.MyCustomConverter" converterTargetClass="com.mypackage.model.MyBeanClass"/>
Here a converter instance of the class
com.mypackage.convert.MyCustomConverter
is created and
registered for the target class com.mypackage.model.MyBeanClass
.
Alternatively the dependency injection framework can be used to create the
converter instance as a bean which is then referenced from the
<f:converter>
tag. This approach is more powerful. If the
converter has to be created using a different constructor or if properties
have to be set, it is the way to go. The code fragment below demonstrates
this scenario:
<!-- Definition of the converter bean --> <di:bean name="customConverter" beanClass="com.mypackage.convert.MyCustomConverter"> <!-- Further initialization, e.g. constructor calls or properties set --> ... </di:bean> <f:converter beanName="customConverter" converterTargetClassName="com.mypackage.model.MyBeanClass" converterTargetClassLoader="specialClassLoader" isBaseClassConverter="true"/>
This example shows some more options of <f:converter>
tag. No matter which mechanism is used for creating the converter object, a
target class must be specified. This is the class produced by the converter
when converting objects. As can be seen here, the target class can either be
specified directly or by providing its name and the name of the class loader
to load the class (refer to the section
Class loader issues for
more details).
Further, the isBaseClassConverter
attribute can be specified.
If the attribute is missing, the converter will only be called
if the target class of a conversion matches exactly the target class
specified at the tag. If the attribute is present (the exact value does not
matter), the converter will also be called for subclasses of the specified
target class.
The converter is active directly after the execution of the
<f:converter>
tag. So bean declarations succeeding the
tag can already make use of data conversions supported by the converter.
When using the builder in the default way there is also a kind of
inheritance mechanism for data type converters: The
BuilderData
object passed to the builder can contain a reference
to the parent BeanContext
. If this reference is set (which is
the case per default), all data type converters active for the parent
context are also available for the current builder operation. So
depending on the builder script in which a converter is defined, its scope
is determined. If an application uses some special data types for which
custom converters are required, it can register the corresponding converters
in its main builder script (this is the script defining the main window of
the application, i.e. the script executed at application startup). They are
then available to all builder scripts invoked later (provided that the
default parent bean context is passed). On the other hand, converters that
are very specific and are only required in a single builder script can be
defined in this script only.
In the sections about the single components supported by the component builder tag library it was also mentioned, which kind of data is stored in the form's model for this type of component. In this chapter we focus on this topic and show how to create a Java class that can serve as a model bean for a form.
Per default the model of a form is a plain Java bean. It is also possible to
use other types of model objects as discussed in the section
The binding strategy, but for
many applications Java beans will be sufficient. The rules how the input
components are bound to a Java bean are pretty simple: For a component with
the name foo there must be a property in the bean with a
corresponding data type that is also named foo (i.e. there must be
public methods DataType getFoo()
and
void setFoo(DataType obj)
). Instead of the name of the
component the propertyName
attribute of the tag defining the
component can be used to specify the name of the model property. So if a
builder script contains the following declaration:
<f:textfield name="fileName" displayNameres="newfile_disp_name" maxlength="200" propertyName="path"> </f:textfield>
the name of the property in the model bean would have to be Path. It is important that the data type of the property matches the type of data that is expected by the associated component - otherwise there will be exceptions when the data is accessed. The following table shows the mapping between components and data types in the model:
Component | Remark | Data type |
---|---|---|
Text components | Without transformers | java.lang.String |
Checkboxes | boolean or java.lang.Boolean |
|
Radio buttons | Depends on the ComponentHandler |
int or java.lang.Integer
for the default handler |
Lists/Combo boxes | Single selection mode | An object of the type declared by the list model. |
Multi selection mode | An array of the type declared by the list model. | |
Progress bars | Per default not stored in the model | int or java.lang.Integer |
Sliders | int or java.lang.Integer |
|
Buttons | Toggle buttons or command buttons (the latter are not stored in the model per default) | boolean or java.lang.Boolean |
Trees | Single selection mode (per default not added to the model) |
An object of type
TreeNodePath
|
Multi selection mode (per default not added to the model) |
An array of type
TreeNodePath
|
|
Tables | Single selection mode (per default not added to the model) | int or java.lang.Integer |
Multi selection mode (per default not added to the model) | an array of int for the selected indices |
Some remarks about this table:
noField
attribute of the declaring tag has to be set to false explicitly.What was not covered so far is the question how the model is associated with the form. When a builder script is processed a Form object is created and initialized automatically based on the components defined in the script. But the form does not manage its model on its own. This task lies in the responsibility of the Form controller. So in order to pass the bean acting as data model to the form the following steps must be performed:
Form controllers and their declaration will be discussed in later chapters in more detail. For now we just provide a simple example that shows the minimum required steps for associating the form with its model.
In the section A simple example a simple form was generated with two text fields for creating new text files. A compatible model class could look as follows:
public class CreateFileData { /** The file name. */ private String fileName; /** The file content. */ private String fileContent; public String getFileName() { return fileName; } public void setFileName(String fileName) { this.fileName = fileName; } public String getFileContent() { return fileContent; } public void setFileContent(String fileContent) { this.fileContent = fileContent; } }
This is pretty easy because we have only two properties of type String, but you should be able to get the concept behind it. Now in the builder script we use tags of the dependency injection framework to create a bean of this model class:
<di:bean name="createFileModel" beanClass="net.sf.jguiraffe.examples.tutorial.createfile.CreateFileData"/>
Next the form controller is declared. It is associated with the buttons for saving or canceling the form.
<di:bean name="controller" beanClass="net.sf.jguiraffe.gui.builder.window.ctrl.FormController"> <di:setProperty property="btnOkName" value="btnOk"/> <di:setProperty property="btnCancelName" value="btnCancel"/> <di:setProperty property="okCommand" refName="okCommand"/> </di:bean>
The final step is the <w:formcontroller>
tag which has to
be placed inside the enclosing window. It establishes the connection between
the form controller bean and the window to be controlled. It is also assigned
the bean acting as the form's model:
<w:dialog ...> ... <w:formController beanName="controller" formBeanName="createFileModel"> </w:formController> ... </w:dialog>
Note that except for the creation of the model class all other steps are declarative. No further coding is required. JGUIraffe now handles the complete life-cycle of the form and ensures that the model is correctly filled.
This should be enough for now. Later chapters will provide more details about the associations between forms, form controllers, dialog windows, and form models. Also information about event handling is provided.