In the previous chapter about the component builder tag library we explained how different types of UI controls can be constructed and combined to graphical user interfaces. Now it is time to fill these user interfaces with functionality. Many applications require that manipulations on controls can be tracked so that they can react accordingly. Examples of such manipulations are a button that is pressed, a change in the selection of a list box, or text being entered into a text field. The UI logic of an application often demands that such manipulations affect other parts of the UI. Maybe the enabled state of some controls depends on the data entered into other controls, or detail views have to change so that they display the data of a selected table element.
In this chapter we discuss how these use cases are addressed by the JGUIraffe library. One key component in this area is another Jelly tag library that provides tags for registering listeners at components and for defining actions to be executed in reaction of changes on controls: the action builder tag library. Using this tag library it is also possible to define menus and tool bars that can be associated with windows. We will discuss these topics in the following sub sections.
Most UI toolkits for Java use the classic observer pattern for dealing with manipulations on UI components: In order to be notified about such manipluations, client code has to implement specific listener interfaces and to register itself at the components of interest. Whenever a certain change in the status of a monitored component occurs, all registered listeners are called. Typically the methods of the listener interface expect a specific parameter object that contains information about the manipulation. These parameter objects are often called events, and the listeners registered at UI components are called event listeners.
Toolkits like Swing and SWT follow this approach. They define a multitude of event listener interfaces and corresponding event objects to allow an application to keep track of the several UI components it is comprised of. JGUIraffe also uses the concept of event listeners, but in contrast to the toolkits mentioned above, only a very limited number of events is supported. The philosophy is to abstract from low-level events; rather, only a few logic event types are defined. To make this clearer have a look at the many different event types Swing uses to indicate that a component has been changed in some way: a change of a text component is treated differently than a change of the selection of a list, a tree, or a table. For all these kinds of changes there are different event listener interfaces and event objects. JGUIraffe uses a single logic change event for all these components. This simplifies programming and should be sufficient in most cases. Typically the many different event listener interfaces do not provide any additional value because it is still required to query the affected component to find out the details about the change.
Here is an overview over the fundamental event types supported by the JGUIraffe library for graphical input elements (there are a couple of other event types related to special controls and windows which will be covered later).
Listener interface | Event object | Description |
---|---|---|
FormActionListener
|
FormActionEvent
|
An action event typically means that the user wants the application to do something. Events of this type are issued for instance by buttons or menu items. In reaction of the event the listener has to initiate the operation that corresponds to the UI element that fired the event. |
FormChangeListener
|
FormChangeEvent
|
Change events have already been mentioned above. They are fired for all kinds of changes on different component types. Typically an application only has to be notified that something has changed. The exact nature of the change can then be determined by inspecting the affected component. One use case for change events is the implementation of dynamic UIs that change according to user actions. As an example consider a directory browser application: When the user selects another directory, the table showing the content of the current directory has to be refreshed. |
FormFocusListener
|
FormFocusEvent
|
Events of this type are fired when the user changes the keyboard focus from one component to another. This means that the user's interaction with one component has finished (and maybe a new interaction with another component starts). Focus events provide a good opportunity for validating the data the user has entered. |
FormMouseListener
|
FormMouseEvent
|
With this event type an application can react on mouse clicks or several other mouse gestures. |
While action and change events are more abstract and high-level events, focus and mouse events are more on a technical layer. They are needed to implement specific functionality around UI components. For instance, it is not necessary to register a mouse listener at a button component to find out whether the button is pressed by the user. The action event is used for this purpose. However, mouse listeners are required if an application wants to react on a double-click.
All event classes shown in the table above are derived from the base class
FormEvent
. From this base class they inherit some properties which
are very useful during event processing:
ComponentHandler
of this component. Through this object access to
the component is possible. For instance, its current data can be inspected.
So in order to retrieve events from UI components an application has to
implement the corresponding event listener interfaces. But how does it
register event listeners at the desired components? For this purpose
JGUIraffe provides the
FormEventManager
class. The current instance of
this class can be obtained from the
ComponentBuilderData
object which holds central information about
the builder operation currently in progress. (Refer to the section
Fundamental
classes and interfaces for more information about this class and its
role.) A reference to the ComponentBuilderData
object is
available to all form controllers automatically; other components can be injected a
reference by means of the dependency injection
builder.
FormEventManager
provides methods that allow registering and
unregistering event listeners of all supported types for specific or all
components of the current form. For instance, there are two overloaded
variants of the addActionListener()
method: one expects the
name of a component and a FormActionListener
object, the
other one only takes a FormActionListener
parameter. The former
method registers the given action listener only at the specified component.
The latter one registers the listener at all components that support action
listeners. This is an extension to the functionality provided by most of the
typical UI frameworks where only the registration at single components is
supported. With this feature it is very easy to register "global"
event listeners which allow a very generic event processing.
JGUIraffe itself uses this feature for instance to implement a
generic validation listener that is triggered whenever a component loses
focus.
As an example we show how a change listener can be added to a checkbox
component named cbxFilter. We assume that the
ComponentBuilderData
is already available in the variable
builderData
. Then the code for looking up the event manager and
registering the listener would look as follows:
FormEventManager eventManager = builderData.getEventManager(); eventManager.addChangeListener("cbxFilter", new FormChangeListener() { ... });
The major part of the components supported by JGUIraffe only
deals with standard events (action, change, focus, and mouse). However,
there are some exceptions. For instance, the
TreeHandler
interface allows adding further tree-specific event
listeners. To deal with such components, in addition to the methods
operating on standard event handlers, FormEventManager
provides
a generic method for adding event handlers of an arbitrary type:
addEventHandler()
. This method expects the type of the event
listener to be added as a string. This can be the name of a constant of the
FormListenerType
enumeration class which defines all standard
event listener types. In this case the addEventListener()
methods behaves exactly as the corresponding specific
addXXXListener()
methods.
However, if the passed in listener type does not match one of the standard
event types, the method tries to find a corresponding add method using
reflection. This is based on a simple naming convention: if the string
Foo is passed as event listener type, the corresponding method
for registering event listeners must be called addFooListener().
(This corresponds to the naming conventions used within the JDK.) Using
this mechanism it is possible to add arbitrary event listeners to components.
The following example shows how a
TreeExpansionListener
object is registered at a
TreeHandler
:
FormEventManager eventManager = builderData.getEventManager(); TreeExpansionListener listener = new TreeExpansionListener() { ... }; eventManager.addEventListener("tree", "Expansion", listener);
Here tree is the name of the tree component.
addEventListener()
searches for a ComponentHandler
object with the specified name. If it finds one, it calls the method for
adding the event listener according to the described naming convention. In
this example it would call the addExpansionListener()
method.
Analogously to the other methods for adding specific event listeners it is
possible to pass in null for the component name. Then
addEventListener()
iterates over all component handler objects
available and tries to find a corresponding method for adding the event
listener. All handlers supporting such a method are called - the others are
ignored. Using this mechanism it is possible for instance to add a
TreeExpansionListener
at all existing tree components in a
single method call.
Working directly with FormEventManager
is a programmatic
approach to the registration of event listeners. The corresponding code
can be placed for instance in initialization methods of
Form controllers. Later in this document
we will see, that event listeners can also be registered using specialized
tags in builder scripts. This is then a declarative approach. But before we
come to this topic, we first have to discuss actions - the
components that gave the action builder tag library its name.
Actions are a well-established concept in many UI libraries. For instance, they are supported by Swing and SWT. An action connects a graphical element in the user interface of an application to the operation the application should execute when this element is triggered by the user. Often the same could be achieved by event listeners registered directly at the corresponding UI elements. However, actions are concepts on a higher abstraction level and provide some advantages over plain event listeners. The main advantage is probably that actions are independent on specific UI elements; actually they can be associated with multiple UI elements at once. This is especially useful when an application allows its users to execute a command in various ways. This pattern is used frequently, for instance, a certain operation can be accessed as a menu item or as a tool bar button. If this operation is realized as an action, the logic for executing this operation can be implemented in a central place. So actions provide a sophisticated programming model that supports the separation between UI and business logic.
Another advantage of actions is that they also influence the UI elements associated with them. For instance, if an action is temporarily not available (e.g. due to restrictions in the business logic), it can be disabled. Then all associated UI elements are automatically disabled - the developer does not have to remember all components affected by the action and disable them manually.
Because of their mediator role between the UI and application logic actions are twofold: They have a set of properties related to their visual representation and also refer to the logic to be executed when they are triggered. The visual properties come into play when an action is associated with a UI element like a tool bar button; then the UI element initializes some of its properties (e.g. label, icon, accelerator, etc.) from the data stored by the action. The action is also automatically registered as event listener at the associated UI element. When the element fires an action event, the action is notified and ensures that the logic is executed.
The actions provided by the JGUIraffe library comply to this
generic description in all points. Programmatically they are represented by
the
FormAction
interface. This interface is a bit minimalistic; it
only provides access to properties related to the execution of an action.
No visual properties can be queried here. This is due to the fact that for
the action framework such visual properties are of less importance. They are
evaluated by code that is specific to the underlying UI platform (e.g. Swing
or SWT) when the action is associated with a specific UI element. After that
the framework only has to ensure that the action is correctly executed when
it is triggered. This also conforms to the declarative approach taken by
JGUIraffe: Actions are defined in builder scripts (as we will see
soon). When client code obtains an action object it has already been fully
initialized and can now be used to control its execution.
FormAction
provides access to the following properties:
enabled
flag: Using this property actions can be
disabled and enabled. A disabled action cannot be executed. The enabled
status is also reflected by UI elements associated with the action; for
instance, a button is grayed out if its action is disabled.checked
flag: Some actions do not execute code when they
are triggered, but only changed their checked
status. Such
actions behave similar to checkboxes. They are often used to represent a
global status of an application. Graphically they are represented by checkable
menu items or toggle buttons in the tool bar. Using the checked
property the action's checked
status can be queried and set.FormAction
objects are associated with a task that is
executed when the action is triggered. This is a difference to the action
implementations in typical UI frameworks like Swing or SWT where actions
directly implement the logic they represent. The possibility to dynamically
change the task of an action allows for very flexible applications. As task
of an action an object can be set that implements one of the following
interfaces:
java.lang.Runnable
: This is the standard interface used
by the JDK to represent an executable task. Its simple run()
method does not support any parameters or exceptions.
ActionTask
is a slightly enhanced version of the
Runnable
interface. It also defines a run()
method, but this variant expects some more parameters which can be
evaluated by an implementation. For instance, a reference to the action
that is currently executed is passed.FormAction
defines an
execute()
method which triggers the action.
In many cases client code does not have to deal with FormAction
objects directly. They are declared and initialized in a builder script and
then work automatically, i.e. they are executed when the user interacts
with the associated UI elements without intervention of the application. So
how are actions created? Take a look at the following fragment of a builder
script for some example action declarations:
<!-- File new action --> <a:action name="fileNewAction" textres="act_filenew_text" tooltipres="act_filenew_tip" mnemonicres="act_filenew_mnemo" acceleratorDef="CONTROL F1" taskBean="openNewFileDialogTask"> <f:icon resource="new.gif"/> </a:action> <!-- File open action --> <a:action name="fileOpenAction" textres="act_fileopen_text" tooltipres="act_fileopen_tip" mnemonicres="act_fileopen_mnemo" group="SINGLE_FILE" taskBean="dummyTask"> <f:icon resource="open.gif"/> </a:action> <!-- Edit cut action --> <a:action name="editCutAction" textres="act_editcut_text" tooltipres="act_editcut_tip" mnemonicres="act_editcut_mnemo" group="SELECTION" acceleratorDef="control x" taskBean="dummyTask"/>
This script defines three different actions. As can be seen, actions are
created using the <a:action>
tag which is implemented by
the
ActionTag
class. (We assume that the action builder tag
library is associated with the namespace prefix "a". This means
that the Jelly script must contain the attribute declaration
xmlns:a="actionBuilder"
in its root element.) Using this tag
the following properties of an action can be set:
Accelerator
class for a detailed description of accelerators and
the syntax used to define them.<f:icon>
tag.taskBean
attribute is looked up in the current bean
context. It may have been created using the dependency injection
framework.
After these declarations fully initialized FormAction
objects
exist and are available in the current context. In the next section we will
see how we can reuse these objects in order to create corresponding UI
elements.
The action builder tag library provides a set of tags for the creation of menus and tool bars. Because menu items and tool bar buttons are the default controls to be associated with actions it is reasonable to let these components be handled by the same part of the framework. Menus and tool bars should be familiar to most developers so there is not much to say about them. Let's dive into an example of a menu definition directly:
<a:menubar name="mainMenu"> <a:menu textres="menu_file_text" mnemonicres="menu_file_mnemo"> <a:menuitem actionName="fileNewAction"/> <a:menuitem actionName="fileOpenAction"/> <a:menuitem actionName="fileEditAction"/> <a:menuitem actionName="filePrintAction"/> <a:separator/> <a:menuitem actionName="exitAction"/> </a:menu> <a:menu textres="menu_edit_text" mnemonicres="menu_edit_mnemo"> <a:menuitem actionName="editCutAction"/> <a:menuitem actionName="editCopyAction"/> <a:menuitem actionName="editPasteAction"/> <a:separator/> <a:menuitem actionName="editRefreshAction"/> <a:separator/> <a:menuitem actionName="editDeleteAction"/> </a:menu> <a:menu textres="menu_extras_text" mnemonicres="menu_extras_text"> <a:menuitem actionName="extrasViewDefAction"/> <a:separator/> <a:menuitem actionName="extrasSettingsAction"/> <a:menuitem actionName="extrasLongOpAction"/> </a:menu> </a:menubar>
This fragment of a builder script defines the main menu of an application: a
menu bar with three menus. The items of the menus are defined through
actions (which are specified using the actionName
). Here the
actions defined in the last example (and some more) are referenced. Of
course, the names passed to the actionName
attribute must point
to valid actions that have been declared before in this builder script or
are available in the parent bean context. We give a short overview over the
tags involved:
Tag | Implementation class | Description |
---|---|---|
<a:menubar> |
MenuBarTag
|
Defines a menu bar. This is the top-level element of a menu definition. It
has a mandatory name attribute. The whole menu bar created by
this tag is stored in the current bean context under this name. The name
can then be used to reference the menu from a window.
|
<a:menu> |
MenuTag
|
This tag adds a menu to a menu bar or another menu. It can be placed in
the body of a <a:menubar> tag or another
<a:menu> tag. The menu is defined by its text (which
can either be set directly or through a resource ID) and an optional icon.
A mnemonic can also be defined. Menus are no active elements themselves;
they merely serve as containers in which other active elements - mainly
menu items - are placed.
|
<a:menuitem> |
MenuItemTag
|
The <a:menuitem> tag defines the content of a menu.
Each tag creates an entry in the menu that can be selected by the user
to initiate an operation. We will discuss the different options supported
by this tag below.
|
<a:separator> |
SeparatorTag
|
This tag can be used to group menu items. It creates a thin line in a menu to separate groups of menu items. It has no further functionality. |
This example script fragment shows that a declarative approach for defining menus works pretty well. The hierarchical structure of XML fits well to the organization of a menu bar with menus and sub menus. The same menu setup in Java code as a series of constructor calls is by far less readable.
All menu items defined by the script are associated with an action whose
name is passed to the actionName
attribute. Under the hood, this
causes a menu item to be created whose properties are initialized from the
properties set for the associated action. Constructing menu items from
actions is the preferred option. It allows using all of the advantages of
actions. Also, there is no need to manually register an event listener
because the action is automatically connected with the menu item and gets
triggered when the user selects the item.
Alternatively the <a:menuitem>
tag supports a similar set
of attributes as used by the <a:action>
tag for setting
the properties of an action (text, mnemonic, accelerator and so on). With
these attributes the appearance of the menu item can be defined manually.
If this approach is used, the developer has to add an event listener
explicitly. In order to do so, the menu item must be assigned a name through
the name
attribute. In this case a ComponentHandler
is created for the menu item and stored in the current context. The
FormEventManager
class can then be used to register an action
listener as discussed in the section
Basics of the event system. It is also possible to use the
declarative approach for registering an event listener that will be discussed
below.
So much for menus, we now move on to tool bars. The declarations in a builder script that define a tool bar look similar to menu definitions as demonstrated by the following snippet:
<w:frame titleres="main_title" menu="mainMenu" autoClose="false"> <f:panel> <f:borderlayout/> <!-- A tool bar at the top of the window's content --> <a:toolbar> <f:borderconstr name="NORTH"/> <a:toolbutton actionName="fileNewAction"/> <a:separator/> <a:toolbutton actionName="fileOpenAction"/> <a:toolbutton actionName="fileEditAction"/> <a:toolbutton actionName="filePrintAction"/> <a:separator/> <a:toolbutton actionName="editRefreshAction"/> </a:toolbar> ... </f:panel> </w:frame>
There is one obvious difference between menu bars and tool bars: Menus are declared as stand-alone components while tool bars are really integrated in the UI of the window they belong to. This example defines a frame window (the tags for constructing windows are subject of the next chapter). The content of the window consists of a panel with a BorderLayout. The tool bar is added to the north of the frame's content panel. This is the typical pattern for defining tool bars. All tags involved in the definition of a tool bar are listed in the following table:
Tag | Implementation class | Description |
---|---|---|
<a:toolbar> |
ToolbarTag
|
The top-level tag for defining a tool bar. The tag does not have any special attributes other than the ones inherited by the super class. |
<a:toolbutton> |
ToolButtonTag
|
With this tag a single tool bar button can be added. So a sequence of these tags populates the tool bar. |
<a:separator> |
SeparatorTag
|
The <a:separator> tag has already been introduced in the
discussion about menus. It can be used for tool bars, too, to logically
group single buttons. This time it produces a gap between buttons.
|
Most comments we made for menu items also apply to tool bar buttons. Again a button can either be defined by associating it with an action or by manually setting the corresponding properties. Note that partly the same actions were used for menu items as for tool bar buttons. In this case both elements trigger the same action.
The menus discussed in the previous section provide commands available for the whole application - they are application-global. Popup menus in constrast are associated with a single component of the application and allow access to functionality specific for this component. The user clicks on a specific location on the screen and is displayed a menu with options related to this position. Therefore menus of this type are also refered to as context menus.
While global menus are pretty static, popup menus are often more dynamic in nature. Their content may vary depending on the status of the component they are associated with. Consider a table listing the files and sub directories of a specific directory. A popup menu for this table can show different items depending on the current selection: if a file is selected, other options may be available than for a sub directory; other options may be always visible.
The action builder tag library supports assigning popup menus to
specific components. It pays special attention to the dynamic nature of such
menus because they are not specified directly, but a class has to be
provided which is able to create the menu on demand. For this purpose the
PopupMenuHandler
interface exists. Internally, JGUIraffe
installs the appropriate event listeners listening for a gesture that should
bring up a popup menu - for most platforms this is a click of the right
mouse button. If a fitting event is detected, the PopupMenuHandler
is invoked. It can then create a menu based on arbitrary criteria.
PopupMenuHandler
is a simple interface defining only a single
method: constructPopup()
. This method is passed a
PopupMenuBuilder
object which can be used to define the content
of the menu. The PopupMenuBuilder
interface defines several
addXXX()
methods for adding different types of objects to the
popup menu under construction: actions, sub menus, or separators. There is
also a create()
method that has to be called after the menu's
content has been defined. It causes the menu to be created and displayed.
Actually, the PopupMenuHandler
can decide not to call the
create()
method - in this case no menu will be shown.
Let's come to an example: We stay with the table showing the content of a
directory. To this table a context menu is to be added. The menu should
offer operations on the files or directories currently selected. However,
some operations are only available if the selection contains a single file
object only. The corresponding actions are grouped together in an action
group called SINGLE_FILE. The implementation of the
PopupMenuHandler
interface obtains all actions belonging to
this group and adds them to the menu only if they are enabled. After that it
adds some actions that are always active.
package net.sf.jguiraffe.examples.tutorial.mainwnd; import net.sf.jguiraffe.gui.builder.action.ActionBuilder; import net.sf.jguiraffe.gui.builder.action.ActionStore; import net.sf.jguiraffe.gui.builder.action.FormAction; import net.sf.jguiraffe.gui.builder.action.FormActionException; import net.sf.jguiraffe.gui.builder.action.PopupMenuBuilder; import net.sf.jguiraffe.gui.builder.action.PopupMenuHandler; import net.sf.jguiraffe.gui.builder.components.ComponentBuilderData; public class TablePopupHandler implements PopupMenuHandler { /** An array with the names of actions that are always present. */ private static final String[] ACTIVE_ACTIONS = { "editDeleteAction", "editRefreshAction" }; @Override public void constructPopup(PopupMenuBuilder builder, ComponentBuilderData compData) throws FormActionException { ActionStore as = (ActionStore) compData.getBeanContext().getBean( ActionBuilder.KEY_ACTION_STORE); // Handle group with actions for a single file selection boolean enabled = false; for (String name : as.getActionNamesForGroup("SINGLE_FILE")) { FormAction action = as.getAction(name); if (action.isEnabled()) { builder.addAction(action); enabled = true; } } if (enabled) { builder.addSeparator(); } // Actions that are always contained in the menu for (String name : ACTIVE_ACTIONS) { builder.addAction(as.getAction(name)); } // Display the menu builder.create(); } }
This listing also demonstrates the usage of some of the helper classes
related to action management. The
ActionStore
class manages all action objects and action groups
defined in the current builder script and allows access to them by name.
The current instance of this class can be queried from the bean context as
shown in the example code. Using the ActionStore
object the
names of the actions in the SINGLE_FILE action group can be
obtained. The code iterates over this set and calls the popup builder's
addAction()
method only if the action is enabled. This assumes
that there are other components in place which control the enabled status
of the actions in the group based on the table's current selection. This is
probably handled by a change event listener registered at the table. If at
least one action of the group was enabled, a separator is added to the popup
menu, so the actions of the group are visually separated from the other
ones. Finally the actions that should always be visible are added to the
menu. As the very last operation the popup handler calls the builder's
create()
method to actually display the menu.
Now that the logic for creating the menu is available, how can it be
connected to the table component? For this purpose the action builder
tag library provides the <a:popup>
tag which is
implemented by the
PopupHandlerTag
class. The tag has to be placed in the body of
the input component tag for the element the menu is to be added to:
<f:table name="table" model="tableModel" multiSelection="true"> ... <!-- Add a context menu to the table --> <a:popup class="net.sf.jguiraffe.examples.tutorial.mainwnd.TablePopupHandler"/> </f:table>
The example showed how the content of the popup menu can vary depending on
certain criteria: some actions were only added to the builder if they were
enabled. If you have a popup menu that should always display the same menu
items, you probably do not want to implement a separate
PopupMenuHandler
class for it - especially as it would be
pretty trivial. JGUIraffe provides a default implementation of the
interface that can be used in such cases:
SimplePopupMenuHandler
. When an instance of this class is created
it is passed a collection with the content of the popup menu to be constructed.
This collection can contain objects of the following types:
FormAction
objects are added to the builder using its
addAction()
method. They become menu items in the generated
menu.SimplePopupMenuHandler
objects are used to create
sub menus. In this case the popup menu has a sub menu whose content is
defined by the SimplePopupMenuHandler
object. This can be
nested to an arbitrary depth.
Using features of the dependency injection framework it is possible
to define SimplePopupMenuHandler
instances completely in
builder scripts. That way static popup menus can be created and assigned to
components without programming. We present an example that is about a text
area control. The text area allows the user to insert several pre-defined
text fragments and to clear its content. These operations should be made
available through a popup menu. The popup menu consists of a sub menu with
the several insert operations and a menu item for clearing the text. So two
SimplePopupMenuHandler
definitions are needed: one for the sub
menu and one for the main popup menu. The definitions can look as follows
(the actions referred to have been dropped):
<!-- Action data definition for the append sub menu in the popup menu --> <a:actionData textres="newfile_men_append_text" mnemonicres="newfile_men_append_mnemo" var="menuAppend"/> <!-- Popup menu handler for the append sub menu --> <di:bean name="appendMenu" beanClass="net.sf.jguiraffe.gui.builder.action.SimplePopupMenuHandler"> <di:constructor> <di:param> <di:list> <di:element refName="action:addContentLoremIpsumAction"/> <di:element refName="action:addContentXMLAction"/> <di:element refName="action:addContentHTMLAction"/> </di:list> </di:param> </di:constructor> <di:setProperty property="actionData" refName="menuAppend"/> </di:bean> <!-- Popup menu handler for the text area's content menu --> <di:bean name="fileContentPopupHandler" beanClass="net.sf.jguiraffe.gui.builder.action.SimplePopupMenuHandler"> <di:constructor> <di:param> <di:list> <di:element refName="appendMenu"/> <di:element refName="action:clearContentAction"/> </di:list> </di:param> </di:constructor> </di:bean>
This example uses the <di:list>
tag of the dependency
injection framework to create the lists that have to be passed to the
SimplePopupMenuHandler
objects. In most cases the list elements
are actions obtained from the current bean context. Note how the reserved
prefix action: is used to reference the desired actions. The first
SimplePopupMenuHandler
declaration represents the sub menu. It
is referenced by the second one. The visual properties of the sub menu - e.g.
its text, mnemonic, or icon - are obtained from the
SimplePopupMenuHandler
instance. In order to initialize them,
the definition contains a <di:setProperty>
tag for the
ActionData property. Using this property all visual properties
related to actions can be set at once. With the
<a:actionData>
tag it is possible to create a
corresponding data object for these properties. The example fragment
demonstrates how this tag is used. The data object created by the tag is
stored under the name determined by the var
attribute and is later
referenced by the <di:setProperty>
tag. More information
can be found at the
ActionDataTag
class. The following fragment shows how the main
SimplePopupMenuHandler
bean is associated with the text area
control:
<f:textarea name="fileContent" displayNameres="newfile_disp_content"> ... <a:popup beanName="fileContentPopupHandler"/> </f:textarea>
Result is a popup menu with a nested sub menu:
In the section Basics of the event
system we have used the FormEventManager
class to register
event listeners at input elements. While this programmatic registration
surely makes sense in some situations it is not really compatible with the
declarative approach of declaring UIs: after the builder script was run you
still have to do some tasks related to the initialization of the UI. It
would be nice if event listeners could be directly registered in the script,
too.
Well, the action builder tag library provides such functionality.
It offers some tags that allow the registration of event listeners at
components defined in the builder script. There are even two different
approaches: a simple one that simulates the direct usage of the
FormEventManager
class as described in the section
Basics of the event system, and
a more advanced one that is based on actions. We start with the simple
approach.
Normal registrations of event listeners can be done using the
<a:eventListener>
tag which is implemented by the
EventRegistrationTag
class. Basically, this tag operates like the
generic addEventHandler()
method of
FormEventManager
: it locates the component of interest, evaluates
the listener type (which can be either a standard type like action,
change, or focus or a custom type), and registers the
specified listener. We present an example in which a change listener is
registered at a checkbox:
<!-- The event listener bean --> <di:bean name="cbxListener" beanClass="..."> ... </di:bean> <a:eventListener component="cbx" eventType="CHANGE" beanName="cbxListener"/>
The eventType
attribute defines the type of listener to
register. In this case, with the type change (case does not matter)
a default listener type is used. For non-standard types the same naming
conventions apply as discussed for the addEventHandler()
method of FormEventManager
. For instance, to register an
expansion listener for a tree, the event type would be Expansion.
In this example the event listener to register is specified through the
beanName
attribute which references a bean defined by tags of
the dependency injection builder library. It is also possible to
specify the class
attribute and set it to the fully qualified
name of the listener class. Then a new instance of this class would be
created. However, the approach with the beanName
attribute is
more powerful because the whole feature set of bean declarations can be used.
Finally, the component to which the listener should be added has to be provided. For this purpose there are multiple options:
component
attribute the name of an input component
can be specified. The tag searches for a ComponentHandler
with
this name in the current context and tries to add the listener to this
handler. This is probably the most basic use case when dealing with event
listeners.<a:eventListener>
tag in the body of another input
component tag. Then the listener will be registered at this component.targetBean
attribute an arbitrary object can be
defined as target of the registration. The name specified here is looked up
in the current bean context. The resulting bean need not be a component
handler; registration is performed through reflection, so the only
requirement is that a method exists whose name corresponds to the listener
type. This also allows adding event listeners to native UI elements.multiple
attribute is set to
true, the tag searches for all ComponentHandler
objects
in the current form that are compatible with the specified listener type.
The specified event listener is registered at all matching handlers. This
corresponds to the functionality of FormEventManager
to
register "global" event listeners. When using the
multiple
attribute there must not be a component name nor a
target bean name.As another example we show how a menu item can be declared without the reference to an action and then be connected to an event listener. As stated in the section Main menus and tool bars, it is recommended to use actions for the definition of menu items and tool bar buttons because this approach is more flexible. However, if a menu item is more or less static, and the functionality it represents is only located in the menu and not in other places, it is easier to declare it directly. The following fragment from a builder script shows an excerpt from a menu definition followed by a tag which assigns an event listener to the Cut item from the Edit menu (the declaration of the event listener bean has been omitted):
... <a:menu text="Edit" mnemonic="E"> <a:menuitem name="editCut" text="Cut" mnemonic="C" acceleratorDef="CONTROL X"/> ... </a:menu> ... <a:eventListener component="editCut" eventType="ACTION" beanName="cutListener"/>
Note the following points:
<eventListener>
tag uses the same name in its
component attribute.
FormActionListener
interface.
So using the <a:eventListener>
tag the same functionality
is available as when using FormEventManager
directly. Before
we end this discussion, we want to add some words about the creation of
event listeners:
In typical UI programming it is often the case that anonymous inner classes are used as event listeners. They are declared in the code that constructs the UI and directly added to the components they should monitor. This is convenient and has the advantage that the listener can access all members of the enclosing class - typically an event listener needs access to other components, e.g. to manipulate the state of input elements or to change properties of beans. However, the big disadvantage of this approach is that logic is cluttered and mixed with code for constructing the UI.
Using the declarative approach for registering event listeners together with the features of the dependency injection framework does not have this drawback. Event listener classes can be declared as regular top-level classes, maybe in a separate package for classes that deal with view logic. If a listener class needs references to other objects (graphical components or other beans like controllers), these dependencies can be automatically injected when the listener bean is created. To make this clearer we provide an example: An event listener is to be registered at a checkbox. When the checkbox is clicked by the user, the listener has to update the enabled state of an action and of a text field, depending on the checked state of the checkbox. The event listener class could be implemented as follows:
package com.mypackage; public class CbxChangeListener implements ChangeListener { /** The action to be enabled/disabled. */ private final FormAction actionToModify; /** The component handler to enable/disable. */ private final ComponentHandler<?> handlerToModify; /** * Creates an instance of CbxChangeListener and initializes it with the * components to be manipulated. * * @param action the action to enable/disable * @param handler the component handler to enable/disable */ public CbxChangeListener(FormAction action, ComponentHandler<?> handler) { actionToModify = action; handlerToModify = handler; } /** * The value of the monitored checkbox has changed. * * @param e the change event */ public void elementChanged(FormChangeEvent e) { // obtain the current value of the checkbox // (we can suppress the warning because we know that it is a checkbox) @SuppressWarning("unchecked") ComponentHandler<Boolean> cbxHandler = (ComponentHandler<Boolean>) e.getHandler(); Boolean value = cbxHandler.getData(); // now modify the components actionToModify.setEnabled(value); handlerToModify.setEnabled(value); } }
This is pretty straightforward. The class has two member fields for the
objects (an action and a component handler) it should manipulate. They are
initialized by the constructor. Provided that an instance is properly
registered at the checkbox component, the elementChanged()
method is called every time the value of the checkbox changes. The event
passed to the method also contains the ComponentHandler
of the
input element which caused the event - in our case this is the checkbox. So
we just have to query the current value of the checkbox through its
ComponentHandler
and can then set the enabled state of the
watched components accordingly. This was the Java part. The following
fragment shows how an instance of this class is created in a builder script
and associated with the checkbox:
<!-- The event listener bean --> <di:bean name="cbxListener" beanClass="com.mypackage.CbxChangeListener"> <di:constructor> <di:param refName="action:myaction"/> <di:param refName="comp:txtData"/> </di:constructor> </di:bean> <a:eventListener component="cbx" eventType="CHANGE" beanName="cbxListener"/>
The use of the <a:eventListener>
tag should be clear. The
interesting part is the creation of the listener object. Here standard
functionality of the dependency injection builder
is used to invoke the constructor of the listener class and pass the expected
dependencies. The prefixes passed to the refName
attributes of
the <di:param>
tags indicate that special elements should be
looked up: an action and a component handler. These special prefixes are
supported by the BeanContext
that is available in the builder
script. You can find a documentation about them in the Javadocs of the
classes
ComponentBuilderData
,
ActionBuilder
, and
WindowBuilderData
. Hopefully, this example demonstrates that it
is easy to use the declarative approach for registering event listeners. The
combination of the dependency injection framework and the
<a:eventListener>
tag provides a powerful means to
separate logic from UI initialization.
The <a:eventListener>
tag provides all functionality
needed for dealing with event listener registrations. So what else has to be
said about event listeners? JGUIraffe provides another feature
related to the handling of event listeners: events fired by UI input
elements can be mapped to actions.
One advantage of actions is that they can be shared between multiple components, e.g. a menu item and a tool bar button. The logic behind the action has to be implemented once and can then be directly reused by all components that are associated with the action. Many input components however, cannot be associated with actions; they only support event listeners. Now there are some use cases where an event generated by an input component should have the same effect as an action. One example is a table listing the files of a directory. Through a menu item and a tool bar button it is possible to open the file currently selected in the table. The logic for opening the file is implemented centrally by an action. To improve usability it would be desirable if a double-click on a file would also cause the file to be opened.
The problem could be solved by externalizing the code for opening a file into a helper class. Then we can write an event listener for mouse events and register it at the table that calls the helper class. Analogously, the file open action would delegate to this class. This would work without major code duplication, but it is still inconvenient and involves creating an almost trivial event listener class. JGUIraffe solves the problem by providing tags that register special event listeners at components. Whenever specific events are received from the component the event listener delegates to an action. This happens automatically without intervention of the developer. Take a look at the code fragment below:
<f:table name="fileTable" model="dirModel"> ... <a:mouseEvent actionName="fileOpenAction"> </a:mouseEvent> </f:table>
This example shows how the <a:mouseEvent>
tag (which
is implemented by the
MouseListenerTag
class) is used to map mouse events to the action
with the name fileOpenAction. The action must be available in the
current context - typically it has been declared before in the same builder
script. The tag is placed in the body of the input element tag defining the
element the event handler is to be registered at. This works not only for
mouse events, but for all standard event types supported by the event system
of JGUIraffe. Event custom events are supported. The following
table lists the tags for the different event types:
Tag | Implementation class | Event type |
---|---|---|
<a:actionEvent> |
ActionListenerTag
|
Action events |
<a:changeEvent> |
ChangeListenerTag
|
Change events |
<a:focusEvent> |
FocusListenerTag
|
Focus events |
<a:mouseEvent> |
MouseListenerTag
|
Mouse events |
<a:customEvent> |
FormEventListenerTag
|
Custom events |
So the <a:mouseEvent>
tag in the body of the
<f:table>
tag basically means that whenever the table
fires a mouse event the specified action is to be executed. But wait! There
are several types of mouse events (e.g. mouse pressed, mouse released,
mouse double-clicked, etc.), but we only want the action to be triggered for
double-click events. This can be achieved by nesting another tag in the
body of <a:mouseEvent>
which defines a specific event
filter:
<f:table name="fileTable" model="dirModel"> ... <a:mouseEvent actionName="fileOpenAction"> <a:eventFilter eventType="MOUSE_DOUBLE_CLICKED" class="net.sf.jguiraffe.gui.builder.event.filter.TypeEventFilter"/> </a:mouseEvent> </f:table>
Using the <a:eventFilter>
tag (implemented by the
EventFilterTag
class) a filter can be added to the automatically
generated event listener which constraints the execution of the associated
action: only if a specified condition is matched by the event, the action gets
fired. In this example the filter accepts only events with the type
MOUSE_DOUBLE_CLICKED. The <a:eventFilter>
tag
creates an object implementing the
EventFilter
interface and initializes it. Here we use an instance
of the
TypeEventFilter
class, a specialized EventFilter
implementation which can check for an event's type
property.
The net.sf.jguiraffe.gui.builder.event.filter
package contains
various implementations of event filters. It is also possible to combine
multiple filters using boolean logic (and and or). In the
following example a filter is defined which accepts single or double-click
mouse events:
<a:mouseEvent actionName="fileOpenAction"> <a:orEventFilter> <a:eventFilter eventType="MOUSE_DOUBLE_CLICKED" class="net.sf.jguiraffe.gui.builder.event.filter.TypeEventFilter"/> <a:eventFilter eventType="MOUSE_CLICKED" class="net.sf.jguiraffe.gui.builder.event.filter.TypeEventFilter"/> </a:orEventFilter> </a:mouseEvent>
This fragment creates two instances of TypeEventFilter
with
different values of the type
property and combines them using an
<a:orEventFilter>
tag. That way complex conditional logic
can be specified in a declarative way.
In addition to the standard events supported by JGUIraffe's event
system, it is also possible to map custom events to actions. For this
purpose the generic <a:customEvent>
tag exists. The
actual event type is defined by a nested <a:listenerType>
tag. This tag is implemented by the
EventListenerTypeTag
class. It expects the event listener type
(as required by the FormEventManager
class) and the class of the
event listener interface as attributes. The latter is needed only for
non-standard event types. The next example maps the tree expansion
event of a tree component to an action:
<f:tree name="testTree" model="treeModel"> <a:customEvent actionName="expansionAction"> <a:listenerType type="Expansion" listenerClass="net.sf.jguiraffe.gui.builder.components.model.TreeExpansionListener"/> </a:customEvent> </f:tree>
It should now be clear how to map arbitrary events generated by input
components to actions. One last hint: The enabled
property of
a
FormAction
determines whether a mapped action is invoked or not:
When an event arrives (which is accepted by a possibly installed event
filter) the automatically generated event listener checks whether the action
is enabled. Only then it executes the action. This is another advantage
over the standard approach with event listeners: a typical event listener
has no built-in mechanism to temporarily disable event processing.
In the chapter about Commands we already pointed out that the programming model around command objects enforces a centralization of business logic which increases the maintainability of an application. Now about Actions we claimed something similar. How do these concept relate together?
To answer this question it makes sense to first emphasize the main difference between actions and commands: Actions are directly invoked by the thread that manages the UI - the event dispatching thread. Commands in contrast are intended to be executed by a separate worker thread. This allows the UI to stay responsive while the application is working in the background.
Because of their different execution modes actions and commands are taylored to different kinds of operations. If an operation runs very fast - maybe just an update of the UI -, an action is appropriate. However, all longer running operations should be implemented as commands running in a background thread.
In order to create and execute a command usually some glue code is required. A typical use case is that a menu item or a tool bar button should trigger an operation which may take a while. The graphical elements (the menu item or the tool bar button) are associated with an action. In the task of this action the logic is placed that handles the execution of the command. Here lies the typical relation between actions and commands: actions act as starters for command objects. They bridge between the UI thread and the worker thread for background execution.
Because starting a command from an action is a standard pattern in UI
programming JGUIraffe provides some special support for it. With
the
CommandActionTask
class there is a specialized task implementation
that executes a configurable command. An instance of
CommandActionTask
can be fully defined in a builder script.
It is assigned the command to be executed. This command can also be defined
in the builder script. An example should make this clearer. Imagine we have
written a command class for opening a dialog with view settings. This is a
simple command class which does not expect any configuration parameters. So
its definition in a builder script is straightforward:
<di:bean name="openViewDefsDialogCommand" beanClass="net.sf.jguiraffe.examples.tutorial.viewset.OpenViewSettingsDlgCommand"> </di:bean>
This declaration just defines an instance of the
OpenViewSettingsDlgCommand
class and makes it available in the
current bean context under the name openViewDefsDialogCommand. The
class implements the Command
interface. Now we define an
instance of CommandActionTask
and associate it with the command
bean:
<di:bean name="openViewDefsDialogTask" beanClass="net.sf.jguiraffe.gui.app.CommandActionTask"> <di:setProperty property="commandBeanName" value="openViewDefsDialogCommand"/> </di:bean>
The property commandBeanName
connects the task implementation
to the command to be executed. The last step is to assign this task to an
action:
<a:action name="extrasViewDefAction" textres="act_extrviewdef_text" tooltipres="act_extrviewdef_tip" mnemonicres="act_extrviewdef_mnemo" taskBean="openViewDefsDialogTask"> <f:icon resource="view.gif"/> </a:action>
This is a complete action definition. Of special importance is the
taskBean
attribute referencing the task we setup in the
previous step.
So
CommandActionTask
can be used out of the box to associate actions
with long-running background tasks. The class provides another feature which
is useful in this context: it allows disabling or enabling specific UI
elements while the background processing is running. Often there are some
restrictions while an operation runs in a background task. For instance,
some other operations cannot be started until results of the current
operation are available. Another typical use case is that it is not allowed
to start an operation again while it is still running. To achieve this, a
CommandActionTask
instance can be associated with an
ElementEnabler
. An ElementEnabler
is a generic object
that knows how to disable or enable certain UI elements, e.g. actions,
action groups, or input components. Have a look at the following example
which is a slight extension of the previous one:
<di:bean name="openViewDefsDialogTask" beanClass="net.sf.jguiraffe.gui.app.CommandActionTask"> <di:setProperty property="commandBeanName" value="openViewDefsDialogCommand"/> <di:setProperty property="beforeEnabler" value="action:extrasViewDefAction"/> </di:bean>
Here again the task for the action for opening the dialog with view settings
is defined, but this time the additional beforeEnabler
property is set. If you look at the Javadocs, you will see that the
beforeEnabler
property actually expects an object implementing
the ElementEnabler
interface. However, when executing a builder
script a specialized data type
converter for element enablers is active. The converter is able to
transform a textual representation of an element enabler to a concrete
implementation object. This is done with the help of the
EnablerBuilder
class. In this example fragment the value
action:extrasViewDefAction means that during the execution of the
associated background command the action with the name
extrasViewDefAction (this is the action itself) should be
disabled. So the user cannot accidently open multiple dialog windows by
issuing multiple clicks. There is also a property named
afterEnabler
which allows setting an enabler to be invoked
after the command was executed. If it is not defined, an enabler is set
which does exactly the opposite of the before enabler; therefore all
elements that have been disabled when the execution of the command started
are enabled again when it finishes. If you want to change the states of
elements in a non-symmetric way, you can specify a different object for the
afterEnabler
property.
Action enablers are a pretty neat feature that
supports dealing with concurrency issues that might arise when working with
background tasks. Have a look at the Javadocs for the
CommandActionTask
and
EnablerBuilder
classes for more details about the enabler
mechanism.
One special case of longer-running tasks is the processing of builder
scripts. Well, actually typical builder scripts are executed pretty fast,
but nevertheless this operation involves I/O activities, so it will not do
any harm to put it in a background thread. JGUIraffe provides a
pre-defined Command
implementation that processes a builder
script and opens the main window defined by the script. This command can be
used for instance everytime an application needs to open a dialog window.
The following code fragment shows an example:
<di:bean name="openNewFileDialogCommand" beanClass="net.sf.jguiraffe.gui.app.OpenWindowCommand"> <di:constructor> <di:param> <di:bean beanClass="net.sf.jguiraffe.locators.ClassPathLocator"> <di:factory> <di:methodInvocation method="getInstance" targetClass="net.sf.jguiraffe.locators.ClassPathLocator"> <di:param value="newfile.jelly"/> </di:methodInvocation> </di:factory> </di:bean> </di:param> </di:constructor> </di:bean>
OpenWindowCommand
implements the Command
interface
in a way that it executes the builder script defined by the
Locator
object passed to the constructor. After the script has been evaluated
the window it defines is opened. The bean definition in the example fragment
declares an instance of OpenWindowCommand
and also defines the
Locator
object to be passed to the constructor - a
ClassPathLocator
in this case. The major part of the bean
definition deals with the creation of the ClassPathLocator
object. This is because instances of this class are created using a static
factory method which complicates the definition. Fortunately, there is a
simpler way to write this declaration because the dependency injection
framework provides a specialized
data type converter for Locator
objects:
LocatorConverter
. The Javadocs of this class describe the syntax
understood by this converter. Using this mechanism the declaration of the
command can be abbreviated as follows:
<di:bean name="openNewFileDialogCommand" beanClass="net.sf.jguiraffe.gui.app.OpenWindowCommand"> <di:constructor> <di:param value="classpath:newfile.jelly"/> </di:constructor> </di:bean>
This is much easier, isn't it? Now this command bean can be assigned a
CommandActionTask
bean, and then it is directly available to an
action:
<di:bean name="openNewFileDialogTask" beanClass="net.sf.jguiraffe.gui.app.CommandActionTask"> <di:setProperty property="commandBeanName" value="openNewFileDialogCommand"/> <di:setProperty property="enablerSpecification" value="action:fileNewAction"/> </di:bean>
Because the builder script newfile.jelly is read the dialog window
created by the command is proably about creating a new file. So the
corresponding task can be referenced by the fileNewAction. Note
that this action also appears in the enablerSpecification
property of the task bean. So while the builder script is processed, the
user cannot trigger the action again to open multiple copies of the dialog.
When the dialog is open it is modal, so actions in the parent window are
not available per se.
These examples show that actions for opening dialog windows that are defined
by builder scripts can be completely defined in a declarative way. No
programming is required unless there are special requirements related to the
initialization of the builder. For instance, if some application-specific
properties have to be passed to the builder script, a custom class has to
be created. This class can be derived from
OpenWindowCommand
; it can override the
prepareBuilderData()
method to perform additional
initialization before the builder is invoked.