Aside: Special
Properties of Composites
To make the conversion of composites more flexible, the following
special
properties of composites are always read, when the base key is not
directly mapped to a value:
1) The instance property
gives the user control over what instance is created and subsequently
configured. The instance property
is converted with the same converter
as the composite. There are three
uses for this property.
The first use is to use another resource bundle key as a template. Since resource bundle key
conversion normally results in a new object being created, the instance property can first
reference another resource bundle key which has various properties
configured. A new instance of the composite will be created from the
template, then its properties can be modified.
For example, an image or
multimedia application might have controls for the amount of red,
green, and blue in a picture. The amount of each color can be
controlled by a slider, which has values ranging from 0 to 100. The
slider is also to show tick marks. Rather than writing the code for
three very similar sliders, a template slider can be defined that has
most of its properties set to their desired values for all three
sliders. Then each slider can reference the template slider using the instance property. Because instance property points to a
resource bundle key, each slider is an independent object. Each slider
then has its value
property set according to the color the slider controls.
Here's a
resource bundle with the code implementing the above description:
slider.min=0
slider.max=100
slider.value=50
slider.majorTickSpacing=10
slider.minorTickSpacing=2
slider.paintTicks=true
# Reference the "slider" resource bundle key as a template
redSlider.instance=%slider
# Assume redValue is mapped to an Integer in the argument map
# ":rw" means read the initial value from the map, and upon
# user change, update the map
redSlider.value=$redValue:rw
greenSlider.instance=%slider
greenSlider.value=$greenValue:rw
blueSlider.instance=%slider
blueSlider.value=$blueValue:rw
Incidentally, literal strings can also used as templates.
The second use of the instance
property is to have a BeanShell script
(see Integration
with BeanShell) determine
what
object is actually created. This might be a subclass of the component
type that the text2gui library is expecting to create. For example,
consider the creation of a subclass of
JLabel, DisappearingLabel
whose text disappears after a delay:
dLabel.instance=<{
delay = 5000;
return new foo.bar.DisappearingLabel(delay);
}>
dLabel.text=This will disappear if not set again soon!
dLabel.hAlign=left
Because it ends with "Label",
DispatchingComponentConverter
expects that the dLabel
key will describe a JLabel
(see Class
ID Guessing in
DispatchingComponentConverter for details).
But the instance subkey
returns a subclass of JLabel,
DisappearingLabel, which is
configured as usual with the text
and hAlign
properties. Also, we could have performed some code before returning
the instance. Note that the code that converts the resource bundle key dLabel does not need to know
anything about DisappearingLabel
when it is
compiled.
The third use of the instance
property is to configure a composite object passed into the argument
map. A custom component not creatable by the text2gui converters might
be created by the application, then put in the argument map. Say that
this component is a disappearing label, as above, and is put as the
value of the disLabel
argument map key with the call:
argMap.putNoReturn("disLabel", new DisappearingLabel(5000));
Then the resource bundle might have the following definition:
dLabel.instance=$disLabel
dLabel.text=This will disappear if not set again soon!
dLabel.hAlign=left
The text and hAlign properties of the label
passed in will be set when the resource bundle key dLabel is converted to a
component. The result of conversion is the same label passed in,
although its properties are modified.
Sidenote: Components passed
into the argument map can be used directly without configuration, as in:
panel.contents=[$disLabel,
%someOtherComponent]
2) The preInit property
provides a chance for a BeanShell script to execute after the composite
is created, but before the properties of a composite are set. The
property is converted with a special instance converter. If the
property does map to a BeanShell script, the variable me will be set to the composite
object in the BeanShell environment. The result of the instance
converter is discarded.
A typical use would be to create a
button group for the children of a panel:
panel.preInit={
putGlobal("bg", new ButtonGroup(), argMap);
}
panel.layout=grid cols=2
panel.border=titled title="Select a position:"
panel.contents=[%onTopRadioButton, %onBottomRadioButton]
onTopRadioButton.text=Missionary
onTopRadioButton.buttonGroup=&bg
onBottomRadioButton.text=Cowgirl
onBottomRadioButton.buttonGroup=&bg
In the above example, a new button group is created and put into
the global namespace using the preInit
property. Because the preInit
property is converted before the rest of the properties, the global
variable bg is guaranteed
to be available by the time the contents
property is converted. Thus the bundle keys used to create the contents
may safely reference bg.
3) The postInit
property
provides a chance for a BeanShell script to execute after all
properties of a composite are set. The
property is converted with a special instance converter. If the
property does map to a BeanShell script, the variable me will be set to the composite
object in the BeanShell environment. The result of the instance
converter is discarded. Typically, this property is used to configure
the composite in a way that cannot be done by setting its properties,
or to notify some other object with the fully configured created
composite.
The following properties file demonstrates the use of the postInit property to ensure
that the last entry of a JList
is visible. This cannot be done by setting properties, so it needs to
be done with a script.
scrollPane.viewportView=%list
scrollPane.prefSize={width=150, height=100}
#Need to do this here instead of the postInit of the list because the
#list needs to be in a viewport for this to work. At the time of
#creation of the list, it's not in the viewport yet.
scrollPane.postInit={
me.getViewport().getView().ensureIndexIsVisible(9);
}
list.listData=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Only after the list is installed in the scroll pane, does JList.ensureIndexIsVisible()
method have any effect, so the method is called after the scroll pane
is fully constructed, in the postInit
script.