So far we have discussed how UI elements can be created and positioned using layout managers. We also described methods for interacting with such elements, so that the UI can be manipulated when certain events are received or the user triggered specific actions. Now, the UI of an application does not live on its own, but is embedded inside a window. Windows are natural elements of modern graphical-oriented operating systems. Nevertheless they have to be defined somehow.
JGUIraffe contains another tag library that deals with windows: the so-called window builder tag library. The tags provided by this library can be used to define multiple types of windows:
javax.swing.JInternalFrame
. They allow an
application to open multiple child windows inside a main frame window, e.g.
one child window for each document currently open. Such child windows were
popular for instance in early versions of the Windows operating system;
the approach was called Multiple Document Interface (MDI).
Meanwhile state of the art applications do not use this concept any more.
So it is recommended to avoid the use of internal frames. Also, they may
not be supported by all platforms.We will discuss the different window types and their creation in more detail in the following sub sections. But first we describe some of the basic interfaces used by the window builder tag library for the interaction with windows.
The creation of windows works similar to the creation of the other elements which have already been discussed in the previous chapters: They are defined using specialized tags in builder scripts. To use these tags it must be ensured that the window builder tag library has been added to the script. So the root element of the builder script must look as follows:
<?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"> </j:jelly>
Here - and in all following examples - we assign the prefix "w" to
tags of the window builder tag library. Now tags like
<w:frame>
or <w:dialog>
can be used to
define the corresponding windows. We will come to these tags in more detail
shortly. For now it is important to know that they act like container tags,
e.g. the tag for creating Panels. This
means that they can be assigned a layout by simply placing a corresponding
layout tag in their body. Also, the
tags for defining the actual UI - i.e. graphical elements like labels, text
components, tables, etc. - can be nested inside such window tags. This is a
pretty natural approach to defining windows which seamlessly continues the
declaration of other parts of the UI.
The
Builder
interface defines a specialized method for processing a
builder script that defines a window: buildWindow()
. Like the
other build methods it expects the locator to the builder script and the
BuilderData
object as arguments. It returns the main window
defined by the builder script.
In theory, a builder script is free to define an arbitrary number of windows.
In most cases however, only a single window - e.g. a dialog box - will be
defined. This corresponds to a "unit" in a graphical application.
In this case this single window is returned by the buildWindow()
method. If a window is pretty simple and only used in the context of another
window, it may make sense to define it together with the window it depends
on in the same builder script. An example would be a simple message window
that is displayed by a dialog box if the user enters invalid data.
In order to access multiple windows they must have been given unique names.
This is achieved by specifying the name
attribute at the tags
that create the windows. (In contrast to tags for input elements, the
name
attribute is optional here.) During the builder operation
there is an instance of the
WindowBuilderData
class which keeps track of the windows that are
defined by the script. This instance also takes care that all windows can
be queried from the current BeanContext
using the reserved
prefix window: followed by their name. Hence a code example
obtaining a specific window could look as follows:
// Input for the build method Locator scriptLocator = new ClassPathLocator("mydialog.jelly"); ApplicationBuilderData builderData = application.getApplicationContext().initBuilderData(); Builder builder = application.getApplicationContext().newBuilder(); // Process the builder script builder.build(scriptLocator, builderData); // Obtain the desired window Window wnd = (Window) builderData.getBuilderContext().getBean("window:msgbox");
This example invokes the generic build()
method on a newly
created Builder
instance. From the bean context created during
this operation it retrieves the window with the name msgbox.
Alternatively, the WindowBuilderData
instance itself can be
fetched from the bean context. It provides methods for querying windows
created during the builder operation, too. If this approach is used, the
last part of the example becomes:
... // Obtain the desired window WindowBuilderData wbd = (WindowBuilderData) builderData.getBuilderContext() .getBean(WindowBuilderData.KEY_WINDOW_BUILDER_DATA); Window = wbd.getWindow("msgbox");
In most cases, if the builder script defines only a single window, the
buildWindow()
convenience method of the Builder
interface should be used. It returns this window directly.
In JGUIraffe windows are represented by the generic
Window
interface. Compared with the APIs of other UI libraries
this interface is very simple. It provides about a dozen methods for
querying and setting properties of the underlying window. Typical
JGUIraffe applications do not need more because the major part of
the window's properties has already been set by the builder script. The
methods defined by the Window
interface are mainly intended to
interact with the window at runtime. The following features are accessible
through the Window
interface:
open()
is the close()
method. It hides the window and frees all its resources.
WindowClosingStrategy
. Before the window is actually closed it
asks its closing strategy for permission. Only if the closing strategy
allows this operation, the window is closed. A typical use case could be
a component that monitors whether the user has made unsaved changes on the
data managed by the application. When the application's main window is to
be closed and there is unsaved data, the closing strategy could display a
message box asking the user whether the modifications should be stored. If
the user now clicks on Cancel, the strategy would forbid the
close operation so that the window stays alive.getWindowController()
method. Controllers are discussed in
more detail later.
For many applications it is just sufficient to open and close a window.
What happens in between is controlled by view and business logic which does
not have to access the window object itself. Typically, interaction with
UI controls is needed, but the window stays as it is after it has been
created. Opening a window can also be automated by using the
OpenWindowCommand
class described in the section
Actions and commands.
So far for the fundamental concepts of the Window builder tag library. We will now describe the tags for creating windows.
As has already been pointed out, the Window builder tag library supports multiple types of windows. For each window type there exists a corresponding tag responsible for the creation of that window. The table below gives an overview over the tags and their associated window types.
Tag | Implementation class | Window type |
---|---|---|
<w:frame> |
FrameTag |
Frame windows |
<w:dialog> |
DialogTag |
Dialog windows |
<w:iframe> |
InternalFrameTag |
Internal frame windows |
The good news is that all these tags are used in a very similar way. They
are all derived from the abstract
WindowBaseTag
base tag handler class which already defines a set
of attributes common to all types of windows. These attributes include
In many cases the default values for these attributes are appropriate. For instance, the window's size is determined by its layout manager which calculates it based on the preferred sizes of the components contained. The position does not matter if the centered flag is set; this makes sense for dialog windows. So typical window definitions are indeed pretty simple. The following example fragment shows the definition of an application main frame window:
<w:frame titleres="main_title" menu="mainMenu" autoClose="false"> ... </w:frame>
Here we just define a title for the window (from a resource) and specify the menu bar. The menu has been defined using other tags in the same builder script. Examples for menu declarations and also for toolbars in windows can be found in the section Main menus and tool bars.
In this example the autoClose
attribute is set to
false. This can make sense in some special cases, e.g. if special
cleanup is needed when the window is shut down. For this main window the
reason for this attribute is a special event mapping: As was discussed in
the section Mapping
events to actions it is possible for actions to be triggered when
specific events are fired. For the main frame of the application we have
used this mechanism to map the window's closing event to the action that
exits the application. If such a mapping is active, the auto close
mechanism must be disabled because the close operation is already handled by
the action. More details about this topic will be provided when we talk
about window event listeners later in this document.
In order to define the window's content, tags from the component builder tag library can be placed inside the body of window tags. Each window defines an implicit panel acting as a container for arbitrary components. Make sure that the correct layout is set, so that the components are correctly arranged. For instance, a typical application main frame window might use a BorderLayout with a tool bar in the north and a panel with the actual content in center. A skeleton declaration for such a window could look like this:
<w:frame titleres="main_title" menu="mainMenu" autoClose="false"> <f:borderlayout/> <!-- A tool bar at the top of the window's content --> <a:toolbar> <f:borderconstr name="NORTH"/> <!-- Content of tool bar omitted --> </a:toolbar> <!-- The main panel --> <f:tabbedpane name="tabs"> <f:borderconstr name="CENTER"/> <!-- Content of main panel omitted --> </f:tabbedpane> </w:frame>
The definition of a dialog window looks very similar. Here is an example:
<w:dialog titleres="newfile_title" center="true" resizable="true" modal="true"> ... </w:dialog>
Instead of the <w:frame>
tag the
<w:dialog>
tag is used. Again, only a small sub set of
the possible attributes is used. This time we specify a title, set the
center
flag which causes the window to be centered on the
screen, and make it resizable. Whether a dialog window should be resizable
or not depends on the controls it contains. If the controls do not profit
from additional space (e.g. only small text fields or checkboxes are used),
there is no point in allowing the user to change the size. In this case the
default size calculated by the layout manager should be appropriate. If on
the other hand the dialog contains text areas, lists, or other components
that can grow with additional space becoming available, a value of
true for the resizable
attribute is suitable.
An attribute specific to the <w:dialog>
tag is the
modal
attribute. As its name implies, it determines whether the
resulting dialog should be modal. A modal dialog blocks all other windows of
the application; they cannot be used before the dialog is closed. Usually
dialogs for entering data are modal: they need to be filled before the
application can continue with its normal execution. Non-modal dialogs in
contrast are used when it makes sense to work with the application in
parallel. A typical example is a search and replace dialog. While it is open
it should still be possible to edit a text field in another window.
The tags for creating windows support icons. It is possible to place an
<f:icon>
tag in their body as described in the section
Labels and icons.
That's it. This has been pretty much all important information about the definition of windows using the Window builder tag library. In the next sections we deal with some specialities related to the interaction with windows.
As is true for most UI libraries, JGUIraffe supports events related
to windows and listeners that can receive those events. The event listener
interface to be implemented by objects that want to be notified about
state changes of a window is
WindowListener
. This interface defines a bunch of methods
corresponding to changes in the life-cycle of a window:
autoClose
attribute set to true, it will be closed
automatically. Otherwise, this event can be caught by a listener which can
then decide whether to close the window manually.
All methods of the WindowListener
interface are passed an object
of the
WindowEvent
class. This object provides access to the source
Window
object affected by the event, the type of the event
(this is an enumeration corresponding to the single methods of the
WindowListener
interface), and the original, platform-specific
event object.
Listeners for window events can be registered at a window programmatically.
For this purpose the
Window
interface defines the methods
addWindowListener()
and removeWindowListener()
.
(Note that in contrast to events related to form elements the
FormEventManager
class is not used for the registration of event
listeners. FormEventManager
only knows about elements contained
in the current form. Also its specialized multiplexing features - e.g.
registering an action listener for all elements providing this event - are
not supported for windows.) The following example code fragment shows how
a listener can be registered at a window created by a builder operation:
// Execute the builder script and obtain the main window Window window = builder.buildWindow(scriptLocator, builderData); // Create and register the window listener WindowListener listener = new MyWindowListener(); window.addWindowListener(listener);
Here it is assumed that the MyWindowListener
class implements
the WindowListener
interface. In addition to the manual
registration of event listeners in program code, it is also possible to use
a declarative approach: by defining window listeners in a builder script.
This works similar to declarative registration of listeners at form elements
as described in the section
Declarative event listener registration: Basically, the bean representing
the window listener is defined in the builder script. Then the
<a:eventListener>
tag is used to add the listener bean to
the desired window. The window is referenced using the
targetBean
attribute:
<!-- Declaration of the window --> <w:dialog name="myDialog" title="Test dialog"> ... </w:dialog> <!-- The bean for the window listener --> <di:bean name="windowListener" beanClass="com.mypackage.MyWindowListener"/> <!-- Register the listener bean --> <a:eventListener beanName="windowListener" targetBean="myDialog" eventType="Window"/>
As usual, the full power of the dependency injection framework can be used to initialize the window listener bean. For instance, references to required UI components or helper objects can be injected.
In the section Mapping
events to actions a set of tags was described which are able to
connect actions to events fired by form elements. With
<w:windowEvent>
(implemented by the
WindowListenerTag
class) a corresponding tag handler
implementation exists for windows. Usage is analogous to the other tags for
form elements: The tag is placed in the body of the tag defining the window.
The name of the action to be associated with the window events has to be
provided as an attribute. Optionally, an event filter can be specified, e.g.
to map the action to a specific window event type. The following example
fragment shows how the closing event of a window is mapped to an
action. This is indeed an interesting use case. It allows the developer to
specify that the application's exit action should be invoked when the user
closes the main window by clicking on the close icon. That way code
duplication can be reduced:
<!-- The main window --> <w:frame titleres="main_title" menu="mainMenu" autoClose="false"> ... <!-- An event listener that delegates the window closing event to the application exit action. --> <w:windowEvent actionName="exitAction"> <a:eventFilter eventType="WINDOW_CLOSING" class="net.sf.jguiraffe.gui.builder.event.filter.TypeEventFilter"/> </w:windowEvent> </w:frame>
Note that in this case the autoClose
attribute of the
<w:frame>
tag was set to false. The window must
not be closed automatically because the event listener may decide to veto
the close operation - e.g. because there is still unsaved data. If everything
is okay, the exit action will terminate the application. This also causes
the main window to be shut down.
In a classical Model View Controller (MVC) architecture, the
controller plays an important role. It connects the model and the view and
processes user input accordingly. In the chapter about
Form controllers we have already discussed
that JGUIraffe provides a fully functional controller
implementation with the class
FormController
. This class can be used out of the box to control
the life-cycle of a form: it populates the form's input fields with their
initial values, performs validation of user input, and eventually updates
the model with the data entered by the user. Only if more interaction is
needed between the graphical elements of the form (e.g. if some input fields
have to be disabled based on the values entered in other ones), a custom
controller implementation (which should extend FormController
)
is required.
In the Form controllers chapter there was not much information about the creation and initialization of form controllers and how they can be associated with window objects. This is because a developer typically does not have to perform these steps manually. Rather, the complete initialization of a form controller is done by tags provided by the Window builder tag library.
In order to setup a controller for a window, a builder script has to contain the following declarations:
FormController
or a derived class. The full power of the
dependency injection framework can be used to initialize all properties of
the controller bean.<w:formController>
tag has to be placed. It must
reference the controller bean. It is this tag which establishes the
connection between the window and its controller.
The first step is to define a bean for the controller. Here all features
provided by the dependency injection framework as described in the chapter
The dependency injection builder can be used.
Because the base class for form controllers,
FormController
is fully functional, it can often be used directly
instead of implementing a custom controller class. A typical bean definition
for a controller might look as follows:
<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>
This declaration creates an instance of the base FormController
class. (FormController
has a default constructor, so it is
sufficient to just specify the bean class.) On the new instance some
properties are set:
The properties set in this example declaration are probably the most
important ones available for FormController
beans. Especially
the button names have to be defined in most cases so that the controller can
react on button clicks appropriately. Command objects for the OK and Cancel
actions are optional. They provide a convenient means for initiating complex
processing of the data entered into the form. If no complex processing
steps are required, you might prefer event listeners over command objects.
Event listeners are directly executed by the event dispatch thread. So they
should only perform short-running operations, otherwise the UI of the
application will block. Advantages of event listeners are that they are
easier to implement than a command object and that they are allowed to
access the UI directly. (Access to the UI is only safe from within the event
dispatch thread which executes the event listener objects.) The
FormController
class supports the event listener type
FormControllerFormListener
. Listeners of this type are notified
when the form is closed. They can then react accordingly, e.g. save the
data when the OK button was clicked. With the event listener tags introduced
in the subsection
Declarative event listener registration it is possible to create suitable
event listener objects in a builder script and register them at a form
controller. Given the form controller declaration above, an event listener
of the class com.mypackage.MyFormControllerListener
can be
registered as shown in the following fragment:
<a:eventListener targetBean="controller" eventType="Form" class="com.mypackage.MyFormControllerListener"/>
This means that an instance of the class
com.mypackage.MyFormControllerListener
is created. This
instance is passed to the addFormListener()
method of the
form controller bean referenced by the targetBean
attribute.
FormControllerFormListener
objects are always invoked when the
form associated with the controller is closed. Often, an operation should
only be performed when the OK button was clicked. One way to achieve this is
to manually inspect the
FormControllerFormEvent
object passed to the listener: the
type
property of this event can be queried to find out whether
the form was committed or canceled. An alternative approach is to map
FormControllerFormEvent
s to an action and add a filter for the
event type FORM_COMMITTED. (Refer to the sub section
Mapping events to
actions for more information about invoking actions through event
listeners.) To do so, first an action - say okAction - must be
defined in the builder script. Then the <a:customEvent>
tag can be used to connect the action to the event listener. This is
demonstrated in the code fragment below:
<!-- Definition of an action to be called when the form is committed --> <a:action name="okAction" .../> <!-- Register the action as listener at the controller --> <a:customEvent actionName="okAction" targetBean="controller"> <a:listenerType type="Form" listenerClass="net.sf.jguiraffe.gui.builder.window.ctrl.FormControllerFormListener"/> <a:eventFilter eventType="FORM_COMMITTED" class="net.sf.jguiraffe.gui.builder.event.filter.TypeEventFilter"/> </a:customEvent>
After these declarations the form controller is in place and fully
initialized - even with commands or event listeners for processing the data
entered into the form. The next step is to associate the controller bean
with the window it has to control. For this purpose the
<w:formController>
tag exists which is implemented by the
FormControllerTag
class. Typically, the tag is placed in the body
of the tag declaring the window. This might look as follows:
<!-- The form bean. This object acts as the model of the dialog.--> <di:bean name="createFileModel" beanClass="net.sf.jguiraffe.examples.tutorial.createfile.CreateFileData"/> <!-- The dialog window --> <w:dialog titleres="newfile_title" center="true" resizable="true"> ... <!-- Connect the form controller --> <w:formController beanName="controller" formBeanName="createFileModel"> </w:formController> </w:dialog>
In this short fragment some interesting things happen. First a bean acting
as the model of the form is declared. Per default, forms store their data in
plain Java beans whose properties match the names of the form's input fields
(refer to the sub section
Components and the form model for more information). A typical pattern
to obtain such a model bean is to simply declare it in the builder script.
It can then be injected into all objects that need access to it, e.g. into
the command object which is set as okCommand
at the controller.
This is a straightforward way to pass the form's data to the command which
has to process it.
Then the declaration of a dialog window starts. The major part is omitted.
We are only interested in the <w:formController>
tag
appearing in the body of the <w:dialog>
tag. This tag
looks pretty simple, but it plays an important role in the initialization of
the controller. It performs the following steps:
FormController
instance needs access to some internal
objects storing results of the builder process, e.g. the current
ComponentBuilderData
object. The tag initializes these objects,
so the developer does not have to care about it.formBeanName
attribute. The controller ensures that the
data entered by the user is stored in this model object.getWindowController()
method.
WindowListener
. If the controller implements this interface,
it is automatically registered as listener at the window.
WindowClosingStrategy
. If the controller implements this
interface, it is automatically set as the window's closing strategy.
After the execution of the <w:formController>
tag the
window and its controller are fully associated. The controller can then
interact with the form autonomously without intervention of the developer.
There is still a small thing missing: In the section Additional validation support some classes have been introduced which provide feedback about validation errors to the user so invalid input can be detected immediately. These classes are called field markers because they are able to highlight an input field if it contains invalid data. In a JGUIraffe application there is a pre-defined default field marker which can be accessed from a bean context using the reserved name jguiraffe.fieldMarker. (Of course, an application can define a bean with this same name overriding the default marker.) For a field marker to work it typically has to listen for some events triggered by the controller, e.g. validation events or events reporting field status changes. It would be possible to use the standard event registration tags we have already discussed to register the field marker at the controller. However, this would be pretty inconvenient, especially as it is not per se known which concrete field marker implementation is active and which events it requires. Therefore a special tag exists which handles the registration of event listeners at a form controller. With this tag the form controller declaration from the last example can be rewritten:
<!-- Connect the form controller --> <w:formController beanName="controller" formBeanName="createFileModel"> <w:formControllerListener beanName="jguiraffe.fieldMarker"/> </w:formController>
Here the <w:formControllerListener>
tag (implemented by the
FormControllerListenerTag
class) has been added. This tag must
be placed in the body of a <w:formController>
tag. It
obtains the bean specified by the beanName
attribute from the
current BeanContext
- in this case this is the default field
marker implementation. Then it checks which controller-related interfaces
are implemented by the bean. For each interface found the bean is registered
as a corresponding listener at the controller. In our example the tag causes
the default field marker to be registered for all required events fired by
the form controller. This effectively enables highlighting of input fields
containing invalid data.
This is all that has to be said about standard form controllers. Sometimes a
controller has to interact with the UI in a special way to make it more
dynamic. For instance, some input fields may only be enabled if certain
conditions are fulfilled, e.g. if a checkbox is checked. In such cases the
typical pattern is to implement a custom form controller class derived from
the base FormController
class. This class implements
corresponding event listener interfaces in order to react on changes at the
UI. For instance, to listen for the status of a checkbox, the
FormChangeListener
interface would have to be implemented. In
the builder script tags would be placed for registering the controller as
event listener at all required input components. The following example
shows how a controller is registered as change listener at three checkboxes:
<!-- Event listener declarations: The form controller is registered at some components to be notified for status changes. --> <a:eventListener component="filterTypes" eventType="CHANGE" beanName="controller"/> <a:eventListener component="filterSize" eventType="CHANGE" beanName="controller"/> <a:eventListener component="filterDate" eventType="CHANGE" beanName="controller"/>
In this example the referenced components are all checkboxes. Of course,
the controller bean must implement the FormChangeListener
interface. It is then invoked whenever one of these checkboxes changes its
value and can update the UI correspondingly.
This concludes our description of windows and their handling in the JGUIraffe library. You should now be able to define windows, associate them with controller objects and gather and process user input.