This page introduces the basics of coding a GUI surface
based on the core Superficial concepts of facets,
targets and retargeting:
On-page applets demonstrate how you can use the Facets implementation
of Superficial to
This page and its applets are at the beta stage of development.
Please notify bugs here.
Creating an application
Below is an applet running the Superficial implementation of "Hello
World".
You can view its complete code in a window or in the code
viewer (itself a demo application).
HelloLabel
is certainly a novel, and rather elaborate, way to display what
is really just a Swing label. Why split the code between two methods,
and how do they relate to each other? What are these STarget ,
STextual ,
SFacet ,
STargeter
types? What is a FacetsFactory ,
and what does a STextual.Coupler do?
These questions are answered in some detail below: the underlying
point is that Superficial is probably the first truly high-level
framework for GUI development. In Superficial you don't define a
GUI and then bind data to it - you define the data first, then (after
a bit of magic performed for you by the framework) the GUI that
gives access to it.
In this respect Superficial conforms to the Model-View-Controller
(MVC) paradigm: it defines a GUI surface
in terms first of its targets,
and only then of the facets that
expose them to user view and control; targets and facets are linked
by retargeting which avoids
the need for explicit data binding.
The target
The two methods of
HelloLabel (both called ultimately from the init
method of Applet ) construct the simplest possible GUI
application surface by defining a single text target, which in turn
has a single exposing label facet.
The first method called is newBasicTargets which returns
an array of STarget s,
the supertype of all target
implementations. In HelloLabel the array comprises
a single STextual
which represents a simple text string, of which the initial (and
in HelloLabel unchanging) state is passed to the constructor.
Of the other two parameters to the STextual ,
title is returned by all STarget s
for use as a caption by exposing facets. Since this STextual
is to be exposed by an uncaptioned label facet, a string is passed
that documents this.
The Coupler
parameter is a minimal example of an important feature of the Facets
implementation of Superficial as described later.
The facet
Since newBasicTargets returned a STextual
to be exposed in the GUI of HelloLabel ,
it makes sense for newBasicPanelFacets to return a
SFacet
that will create and manage a suitable Swing label. The puzzle is
that of the two types passed to newBasicPanelFacets ,
neither bears any obvious relationship either to the STextual
or to Swing.
The Swing issue is most easily resolved. Because a Superficial
GUI is defined purely in terms of facets,
the Facets implementation can hide details of the GUI toolkit from
client code using an abstract FacetsFactory .
This has a wide range of methods supplying instances of SFacet
that in turn create and manage GUI widgets.
For HelloLabel the only facet required is one that
creates a simple label, so the method to call is textualLabel .
Like most methods of FacetsFactory
this takes two parameters: a STargeter
and one or more concatenated HINT constants that can
supply low-level facet policy. But where does the STargeter
come from?
The targeter
The STargeter
array passed to newBasicPanelFacets has a single member
that can be used to create a label facet exposing the STextual
created in newBasicTargets . So STargeter s
must be related in some way to STarget s
- but how?
During application construction each STarget
creates a corresponding STargeter ,
which has the following characteristics:
- It is retargeted (passed a reference) initially on the
STarget
that created it.
- It can have any number of
SFacet s
attached to it.
- It can retarget these
SFacet s
on its current STarget
target.
It is this relationship between targeters, targets and facets that
makes the Superficial retargeting
mechanism so powerful.
The initial retargeting takes place before the STargeter
is passed to newBasicPanelFacets , where the FacetsFactory
is used to create and attach a SFacet
to it. Once the Swing GUI has been created as described below, the
STargeter
will in turn retarget the SFacet ,
and this will set its JLabel widget with the text of
the STextual .
The GUI
A major advantage of the Superficial approach is that it can shield
client code completely from the details of low-level GUI construction.
(Though in practice the Facets implementation allows surprisingly
fine control of the detailed appearance of a GUI, and even the use
of custom Swing components.)
All you need to know when coding with Superficial is that having
defined your STarget s
and attached SFacet s
to the STargeter s
that they create, you can rely on the framework to do the rest.
If you're curious to see what actually happens, you can start at
the top with demo.DemoSurface which manages the application
creation and retargeting sequences for CodingApplet s.
However even these are performed at a high level of abstraction
- if you insist on finding Swing code, you'll need to peer behind
another layer or so of interfaces.
But why worry about these details? Much better move on to the next
applet, and find out how Superficial helps you handle input to your
application.
Input and updates
HelloLabel
demonstrates the basics of a constructing a Superficial surface,
but its simple label facet can't show how Superficial handles input
to a GUI.
HelloField is a slightly more elaborate version of
HelloLabel that exposes the greeting text not just
with a label, but also with a text field that allows the the greeting
to be edited. This enables it to demonstrate
- how Superficial handles updating of targets and facets transparently
to client code
- how a single target can be shared by multiple facets
- how facets hide code complexity and promote code re-use
Given the substantial increase in functionality provided by HelloField , it
may surprise you how little the code differs from HelloLabel .
- The
STextual
is constructed with a title that can be used by an edit field
facet, and a Coupler
that defines policy for the facet.
- Such a facet is returned by
newBasicPanelFacets
together with the label facet.
- And that's it! How does that work?
The edit field facet
The edit field facet returned by textualField in
the FacetsFactory
demonstrates how Superficial's high-level approach shields client
code from low-level GUI complexities, while at the same time promoting
code re-use.
To start with, the facet creates and manages not just a single
widget, but a JPanel containing both a JLabel
and a JTextField . The JLabel is set to
the title property of the STextual ;
the JTextField allows editing of the STextual 's
current state.
Now try editing in HelloField and you will find the JTextField
has more intelligence than you might expect. It won't allow you
to blank it (this would make a nonsense of most texts); pressing
Esc reverts its state while Enter commits the edited state.
Because the code that constructs the facet's widgets and enables
it to handle input intelligently is hidden behind the SFacet
reference, client code is hugely simplified. Code re-use is guaranteed
because its functionality is available from every instance of FacetsFactory .
Moreover, the edit field facet appears magically bound to the label
facet. The label reflects even uncommitted edits, while reverting
the field reverts the label as well. Yet there's no code in HelloField for
this binding (the Coupler
only sets a flag as described below) - how is it defined?
Binding targets and facets
Compared with HelloLabel ,
HelloField can
demonstrate much more clearly the advantages of Superficial for
data binding.
Sharing a target
An obvious advantage is the ease with which multiple facets can
be bound to a single target.
The label and edit field facets are both attached in newBasicPanelFacets
to the same targeter, so they share the same STextual
target. Because it is the target that represents the data to be
exposed in the surface, after each retargeting
both facets automatically display the latest state of that data.
Handling input
A more subtle advantage demonstrated by HelloField is
the complete absence of code that explicitly updates the target
and facets after input. To understand why this is not required,
let's consider how input is handled by the text field facet.
When the GUI is created by the applet host, the facet constructs
its JPanel with child JLabel and JTextField
components. Setting properties as required to match the title and
text state of its STextual ,
it then listens for input on the JTextField .
As input arrives, the facet can respond appropriately to signals
such as Esc and Enter, and ask the Coupler
attached to the STextual
to validate the current text in the JTextField (which
is why it rejects blank texts). Above all, it can decide at some
point to update its target with the current text, and trigger a
retargeting.
The retargeting
Whenever a retargeting is triggered, the framework takes over.
First all targeters are retargeted on appropriate targets. This
has no particular effect in a simple application such as HelloField ,
but is crucial when there may be a selection
change.
Next all facets are retargeted on the latest targets of their targeters,
and have the opportunity to update their widgets to match the current
state and policy of these targets. Facets that share a target will
set their widgets to the same state, in effect binding them all
to the same data.
Exactly when the text field facet triggers a retargeting depends
on a flag set by the Coupler .
The coupler
Both facets and application generally require more than the core
functionality of simple STarget s
such as STextual .
- The facet may need to know how it should display or update the
data represented by the
STarget .
For instance in HelloField
the edit field facet needs to know if it should wait for Enter
before committing edits and updating its target.
- The application often needs to know when a
STarget
has been updated. In HelloField no further action
is required once the edit field facet updates its STextual
target. But most applications will need to update data that their
targets represent, or (like the next few applets on this page)
take some other action..
The Facets implementation of Superficial uses appropriate Coupler s
to provide this extra functionality. Because couplers are parameter
objects that hold no reference to any particular STarget ,
they can be defined independently of their STarget s
and shared between logically related STarget s.
The larger part of the code related to simple STarget s
is contained within subclasses of default implementations such as
STextual.Coupler ; only in a simple case such as HelloLabel
can a default implementation be used 'as-is'.
In HelloField the
Coupler
is subclassed to make the text field facet behave in a non-standard
way. By default the facet waits for Enter as a signal to commit,
but because updateInterim in the coupler is reimplemented
to return true , the facet triggers a retargeting every
time that input pauses - this is how the label state tracks the
edit state so closely.
Flags, menus
Now that we've covered the basics of creating a Superficial surface
that responds to input, let's add some more logic and GUI. HelloSpaces
extends the functionality of HelloField
by adding a checkbox in the panel and a single-item menu, both defining
validation of the greeting text.
Looking at the code of HelloSpaces
you will see the following developments compared with HelloField .
- There are now two targets, one of them of a
SToggling
providing the validation flag.
- The
Coupler s
for both targets override several methods so as to provide the
necessary logic.
- A further method from
CodingApplet is implemented
to define a menu item.
The flag
The validation flag is provided by a SToggling ,
which is a STarget
that represents Boolean values. Like STextual
it is constructed from a title text and its own flavour of Coupler ,
but with its state set and returned as a boolean instead
of a String text.
Compared with STextual ,
SToggling
can be exposed by a large variety of facets. Targeters of SToggling s
can be passed to togglingX methods of FacetsFactory
to create facets which define and manage checkboxes and toggle buttons,
menu items and even multi-selection lists.
The couplers
HelloSpaces
provides a typical example of coding with couplers.
- Couplers are ideal anonymous subclasses. They can be instantiated
as class or method variables, or as parameter objects if they're
not shared.
- They often refer to targets instantiated after them, so to avoid
compiler errors targets have often to be defined as instance variables.
Sometimes even the order of instantiation of the targets is critical:
in HelloSpaces the SToggling
must be created first because isValidText in the STextual.Coupler
references it when the STextual
is created.
The two couplers provide examples of the two main reasons for overriding
default implementations: the SToggling.Coupler responds
to state change, while the STextual.Coupler supplies
special policy.
Responding to change
If the SToggling
is updated by one of its exposing facets so as to disallow spaces,
the existing state of the STextual
may become invalid.
The SToggling.Coupler allows for this by overriding
the default (empty) implementation of stateSet , which
is called whenever a SToggling
to which it is attached is updated. The new implementation responds
to resetting of the flag by fetching the current state of the STextual ,
stripping any spaces and updating the STextual
with the stripped text.
Modifying policy
Whenever the state of a STextual
is set, it calls isValidText in the coupler (including
at construction). Because an exception is thrown if the return is
false , the text field facet uses the same method to
pre-check text before updating its target.
The STextual.Coupler extends the default implementation
of isValidText which rejects a blank text, adding a
check for spaces if required by the state of the SToggling .
The menu
Reimplementing newBasicMenuFacets to return a non-null
array signals to the superclass CodingApplet that it
should construct a menu bar (inside the panel!), adding to it menu
items for each of the facets returned.
In HelloSpaces a single SFacet
is returned, created using togglingCheckboxMenuItems .
Because the facet is attached to the same targeter as the panel
checkbox facet, the framework ensures that their widgets are synchronised
just like the panel label and text field.
Buttons, disabling
HelloCommit is a another version of HelloField
which introduces another new STarget
type. It extends the functionality of HelloField with
logic that commits or cancels edits to the greeting text, exposed
both by buttons in the panel and items in the menu. Buttons and
menu items are disabled unless the actions they represent are meaningful.
The code of HelloCommit shows
the following developments compared with HelloField
and HelloSpaces .
- A second
STextual
holds uncommitted edits to the greeting text.
- Two
STrigger s
represent the actions of committing or cancelling edits.
- Each pair of targets shares a custom coupler instance.
- The logic provides for disabling of buttons and menu items.
The textuals
The two STextual s
are constructed from a shared custom STextual .Coupler
instance, which overrides updateInterim as before (this
will only be called for edits). It also overrides textSet
which is called whenever the state is set of either edits
(usually by its text field facet) or greeting (only by the
STrigger.Coupler ); in both cases it calls the convenience
method setTriggerLives described below.
The triggers
STrigger
is a stateless STarget
that represents an application action or process, exposed by either
buttons or simple menu items. STrigger s
are constructed with a Coupler
that has a single fired method, which here checks whether
commit or cancel has been fired and copies the state
of one of the STextual s
to the other as appropriate.
Enabling and disabling
Superficial makes it easy to manage the enabled state of GUI widgets.
Every STarget
returns a live value that can be checked at each retargeting
by its exposing facets and used to disable/enable the appropriate
widgets.
The live state can be set for any STarget ,
but it only returns true when any enclosing groups
(see below) are also live; this facilitates very fine-grained control
to the enabled state of the GUI.
Grouping
The commit and cancel STrigger s
are returned from newBasicTargets wrapped in the commitCancel
STarget
group. Grouping allows logically related targets to be treated by
the framework as one, and HelloCommit takes
advantage of it in two ways:
- From the single targeter exposing commitCancel, the
FacetsFactory
can construct with triggerButtons a single facet
that constructs and manages a panel of equally-sized buttons,
and from triggerMenuItems another that creates corresponding
menu items.
- All the buttons and menu items can be enabled or disabled at
once by setting the live state of commitCancel in the
setTriggerLives
convenience method.
Handling numbers
HelloLimit is very similar to HelloSpaces ,
but restricts the maximum length of the greeting text to a limit
exposed in several different ways.
The code of HelloLimit is
essentially that of HelloSpaces ,
updated to set a different constraint.
- The text of the
STextual
is limited in length.
- The limit is defined by a
SNumeric
which in turn is constrained by the NumberPolicy
returned by its Coupler .
- The
SNumeric
is the shared target of three facets in the applet panel and menu,
one exposing its state in two different ways.
The number
The length limit of the greeting represented by the STextual
is itself represented (as a double value) by a SNumeric .
When its value changes, valueSet is called in the Coupler ,
here implemented to ensure the text length does not exceed the new
limit.
Similarly, the STextual.Coupler overrides isValidText
to check the length of the text proposed against the current limit.
The policy
SNumeric
holds a double of potentially any value. How can you
ensure that it is constrained in the GUI to an integer value within
a sensible range?
The SNumeric.Coupler returns the necessary validation
policy as a NumberPolicy
initialised with the minimum and maximum limits to be set. In HelloLimit a Ticked
is returned, which extends NumberPolicy
to supply the display policy required by a slider facet to define
its ticks and labels. Re-implementing unit constrains
both slider and numeric field to allow entry only of multiples of
5 in the defined range, and sets the size of the nudge for the panel
buttons and menu items.
The facets
The facets created in newBasicPanelFacets are broadly
similar to those in HelloSpaces ,
and demonstrate further the power of the facet concept, not to speak
of the flexibility and concision of the FacetsFactory
interface.
The textual facets are exactly as before, the spacer facet and
BREAK constant together add a blank row to the
panel.
Contrary to what you might expect, there just two numeric facets
(separated by a horizontal spacer facet).
- The
HINT constants passed to numericSliders
ensure that the facet returned not only creates a titled, labelled
and ticked slider to meet the NumberPolicy
supplied by the Coupler
of its SNumeric
target, but also adds a numeric field and arranges its widgets
in two rows.
- The facet obtained from
numericNudgeButtons is
a rare example of a GUI element providing control without view
(almost). Once again following the NumberPolicy ,
its paired buttons increment and decrement
the value where possible, becoming disabled when the value is
at the end of its range.
The single facet returned by newBasicMenuFacets creates
a pair of menu items corresponding to the panel nudge buttons, then
groups them in a sub-menu.
Choosing from a list
HelloChoose differs from applets such as HelloSpaces
in that it does not allow its greeting to be edited, but instead
presents a choice of texts using the last of the simple STarget
types.
The code of HelloChoose is
simpler than HelloSpaces .
- An
SIndexing represents a list of possible texts and an index
into that list.
- The state of the
STextual
is updated whenever indexSet is called in the SIndexing.Coupler .
- The
SIndexing
is exposed by a total of five panel and menu facets, creating
and managing two or three widgets each.
Editing a selection
HelloSelect both allows the greeting to be edited
as in HelloField ,
and presents a choice of texts like HelloChoose .
As you might expect, the facet details in HelloSelect resemble
those for HelloField
and HelloChoose .
But otherwise the code presents several puzzles.
- Both targets and facets are defined within different methods
from the previous applets.
- The
STextual
is defined within a new
SFrameTarget type.
- No
SIndexing
is defined, but a suitable STargeter
is passed to the facet methods.
The methods
The key to the different methods lies in the constructor to HelloSelect ,
which creates an array of HelloText s and passes it
to the superclass. This triggers a radical change in the behaviour
of CodingApplet because it now has to allow editing
of a selection within the content supplied.
Just creating simple targets in newBasicTargets would
not handle this new complexity, which is why in HelloSelect
it returns an empty array. Instead, CodingApplet uses
the framework to construct elements (including the missing SIndexing )
that allow selection within the content array.
In HelloSelect each content selection is passed to
newSelectionFrame to allow targets to be created representing
its editable state; the initial targets together with the hidden
SIndexing
are used to create the extra STargeter s
passed to newContentPanelFacets and newContentMenuFacets .
The frame
On the face of it, newSelectionFrame might just return
an array of STarget s
like newBasicTargets . However the framework requires
targets representing a selection to be wrapped by a distinct subclass
of SFrameTarget
(which makes possible advanced features like adapting the GUI to
match the current selection).
SFrameTarget
extends the basic STarget
implementation not by specialising for a specific content type,
but by exposing its content directly as an immutable framed
member. While this is of little benefit in HelloSelect ,
it allows more complex applications to access the selected content
via the framework.
To ensure subclassing, SFrameTarget
cannot be constructed from its STarget
child elements; instead it creates them during the initial retargeting
using lazyElements . In HelloSelect this
returns a STextual
much like that returned by newBasicTargets in HelloField ,
with the important difference that its Coupler
reimplements textSet to update the state of the selected
HelloText .
The facets
The main development in the facet creation methods is in the parameters
passed.
- basics are targeted as before on the targets returned
by
newBasicTargets (so the array is empty in HelloSelect ).
- indexing is targeted on the
SIndexing
created by CodingApplet and allows view and control
of the index into the content array.
- selection is targeted on the
SFrameTarget
returned by newSelectionFrame , so the first member
of its elements is targeted on the STextual .
Though the STargeter
parameters have STarget s
created in different places (including one in the superclass), the
exposing SFacet s
can be freely arranged in any combination.
Bringing it all together
HelloAll combines the functionality of all previous
applets on this page:
HelloAll therefore needs very little new code, but does
have a few points of interest.
- Non-selection targets are created in
newBasicTargets .
- The
Coupler
shared by the triggers can access the current selection using
a method from CodingApplet .
- Some of the
SFacet s
created in newContentPanelFacets are arranged within
a nested panel.
Where next?
Try downloading the source code of real-world Facets
applications and see how you get on.
© 2011 David M Wright
|