This is a user manual that presents a different view from the Javadoc raw explanations.
It is a good place to start and grasp the general ideas on what UIHierarchy really is, and quickly start.
For more detailed explanations about a specific feature, the Javadoc of the concerned classes may provide the information.
Additionally, the demo program is another good place to understand and test the different features.
Note that if you find some errors or some missing explanations, you can let me know. I also welcome any suggestion to improve this manual.
You can send your comments at chrriis@brainlex.com.
© 2003 Christopher Deckers
chrriis.uihierarchy.unit
package
UIHierarchy is a Java library that aims at simplifying GUI development when using AWT or Swing.
Useful declarations of components remains, but UIHierarchy takes care of their containment hierarchy. And once the hierarchy is created, UIHierarchy even allows some operations on its levels.
The main problem I identified is that the way people think and design user interfaces does not match the way to program them.
Several solutions try to solve this problem, but I was not satisifed with their approach:
UIHierarchy provides a lot to GUI development:
The library is designed for Java development. The supported versions are J2SE 1.1 and above.
UIHierarchy supports AWT, and Swing if it is present.
No special step is required appart from adding the UIHierarchy.jar
library file to your project's classpath.
For all information regarding latest versions, please refer to the UIHierarchy website:
http://chrriis.brainlex.com/projects/uihierarchy
The license used is the LGPL 2.1.
If you don't like this license you can contact me so that we agree on a different one, as long as it remains open.
The best way to understand how UIHierarchy simplifies the developers' job is to have a quick look on how to use it.
Let's start with a comparison between a rough design, the traditional implementation and the UIHierarchy approach.
The design is logical, and follows what one draws on paper.
Using natural language, with some Java constructs in mind, here is the resulting containment hierarchy:
// Component declarations A button "A button" is declared that will trigger some action. // Hierarchy declaration * The content pane of a root pane container. + A panel, using a gridbag layout, is added to the center. - A label "A label: " is added to the cell [0, 0], and is left aligned. - The button "A button" is added to the cell [0, 1]. // Declaration of actions The actions are associated to the button aButton. |
One possible (and among the shortest) way to implement the original design is the following code:
// User interface declaration Container contentPane = getContentPane(); JPanel aPanel = new JPanel(); contentPane.add(aPanel, BorderLayout.CENTER); GridBagLayout gridBagLayout = new GridBagLayout(); aPanel.setLayout(gridBagLayout); GridBagConstraints constraints = new GridBagConstraints(); JLabel aLabel = new JLabel("A label: "); constraints.anchor = GridBagConstraints.West; gridBagLayout.setConstraints(aLabel, constraints); aPanel.add(aLabel); JButton aButton = new JButton("A button"); constraints.gridx = 0; constraints.gridy = 1; gridBagLayout.setConstraints(aButton, constraints); aPanel.add(aButton); // Declaration of listeners ... |
Now the same using UIH, the base class of the whole UIHierarchy library:
// Component declarations JButton aButton = new JButton(); // Hierarchy declaration UIH h = new UIH(); h.openNew(getContentPane()); h.open().layout(new GridBagLayout()).constrain("center"); h.add("A label: ").constrain("anchor=West"); h.add(aButton).constrain("gridx=0, gridy=1"); h.close(); h.close(); // Declaration of listeners ... |
Each level of the containment hierarchy is clearly defined. A level that contains another level is opened and then closed.
This indentation is the one I personaly use, but even indented differently, the code is much more readable than the traditional implementation.
There are only a few methods to know:
open(...)
: create a node in the hierarchy tree. It expects some children components and is itself a component of a container.openNew(...)
: similar to the open(...)
method, but the container will not be assigned a parent. It is used to start a hierarchy.close()
: close a node of the hierarchy tree.add(...)
: add a leaf in the hierarchy tree.Whenever a level is defined, a corresponding level object is created that maintains relevant information given by the developer.
When the last close()
is invoked, or when the realize()
method is invoked if not automatic at the last close()
(if the library was configured so), the hierarchy is realized:
For more examples, you can download the UIHDemo on the UIHierarchy website.
The UIHDemo contains different pre-defined examples, with some comparisons between UIHierarchy versions and traditional implementation.
It uses scripting with Java syntax, so the examples are actually editable.
The UIHDemo requires a 1.3 JRE or later, or 1.4 and above for syntax highlighting.
The UIH declaration presented in the previous section already shows some simplified declarations:
Using UIHierarchy's configuration capabilities, it is possible to change the default behaviors. It is then possible to handle different type of constraints, String based or not, or to add or change implicit component creations.
When constructing a component hierarchy, it is often that a developer needs to include an intermediate panel only to start a new group of components with a new layout manager.
In traditional implementation, there is a need to explicitely declare such panel to be able to apply some constraints on it and to add its sub components.
Using an empty h.open()
performs the implicit panel declaration:
... h.open().layout(new GridBagLayout()); ... h.close(); ... |
This example is strictly equivalent to h.open(new JPanel())
, or to h.open(new Panel())
if not using Swing.
It helps readability as these empty panels are not explicitely declared.
Similarily, labels are often created to show a String on screen. In traditional implementation, the explicit declaration of the label is needed to be able to set its constraints and add it to its parent.
This is simplified in UIHierarchy, implicitely declaring a label when using a String:
... h.add("A label: "); ... |
In addition to supporting traditional constraints objects as parameters, UIHierarchy allows to define some handlers able to manipulate simplified constraints declarations.
By default, UIHierarchy provides some handlers for most of the layout managers (including some third party ones) that can handle String-based constraints.
Here is a simple comparison between traditional implementation of some GridBagConstraints
and the UIHierarchy String-based equivalent.
First, the traditional implementation:
... GridBagConstraints constraints = new GridBagConstraints(); JLabel label = "A label: "; constraints.anchor = GridBagConstraints.WEST; constraints.fill = GridBagConstraints.HORIZONTAL; gridBagLayout.setConstraints(label, constraints); contentPane.add(label); ... |
And the UIHierarchy equivalent, assuming the parent's layout manager is a GridBagLayout
:
... h.add("A label: ").constrain("anchor=West, fill=horizontal"); ... |
This is just a small presentation of the simplified way of handling constraints. More information about this mechanism may be found in the Layout Managers chapter.
One comment on String-based constraints: they can raise the argument of compile-time checking versus runtime checking. Well, for user interfaces, even if the code compiles, the only way to know if it renders correctly is to launch the application. So having a runtime check, eventually resulting in an explicit detailed exception, is not a problem.
In practice they prove to be really nice to use in place of their object-based counterparts.
For several reasons, it is sometimes useful to map some levels of the hierarchy to some names. Some of those reasons are:
The way to declare some mappings is done this way:
h.add("A label: ").constrain("anchor=West").map("label, leaf"); h.add(aButton).constrain("gridx=0, gridy=1").map("button, leaf"); |
Several names can be mapped to a single level, and several levels can map to the same name. Though it is possible to chain the calls to map(...)
, the preferred way is to use a comma separated list of names.
Layout managers play a great part in an AWT/Swing containment hierarchy. They allow code to be designed once and to run similarly on different operating systems.
UIHierarchy simplifies and creates harmony in the way one uses the layout managers.
Defining a layout manager is a method call on a level, once it is declared. Of course this operation can only be called on levels that contain other levels:
h.open().layout(new GridBagLayout()); |
The layout manager can be declared in-line, since there is no need to keep a reference on it. Even the GridBagLayout works this way, and is really more readable with UIHierarchy. But have a look at how the constraints are defined too.
Constraints may be set for a component to indicate how it is going to be laid out, considering the layout manager of the container. With UIHierarchy, constraints are defined at the same time a level is added to a parent level:
h.add(aButton).constrain("gridx=0, gridy=1"); |
It is possible to associate some constraints handlers to the layout managers, to allow simplified declarations. By default, UIHierarchy associates handlers for:
UIHierarchy's Javadoc explains how to declare the constraints for each handler. The naming convention for a handler is so that "MyCustomLayout" will have a corresponding handler named "MyCustomHConstraints".
Sometimes, the container defining the layout manager must be constrained using its own manager. UIHierarchy provides the method subConstrain(...)
for that purpose. This is useful for example with the SpringLayout:
UIH h = new UIH(); h.openNew(getContentPane()).layout(new SpringLayout()).subConstrain("east=field.east, south=field.south"); h.add("A label: ").constrain("x=5, y=5").map("label"); h.add(new JTextField()).constrain("x=5+label.east").map("field"); h.close(); |
Note that it is not compulsory to use the shortened versions of the constraints, and the real constraints object can be used as a parameter. If the constraints cannot be understood by a handler, it will be used as is.
Additionaly, if a constraints handler (an implementation of LayoutHConstraints
) is used as a parameter, its createConstraints
method will be invoked.
There are multiple reasons why developers may need some help to debug graphical implementation. Some are:
Designed with debugging in mind, UIHierarchy provides several debugging facilities:
UIHierarchy solves a lot of the containment hierarchy debugging problems with a single command, which prints the complete hierarchy up to the point of invocation:
h.print(); |
This print()
method is a real benefit of UIHierarchy. With the first basic example, the result that is seen in the System output is:
> UIH [Closed: true] [Realized: true] * JPanel + JPanel [Layout: GridBagLayout] [Cons: center] - JLabel [Cons: anchor=West] - JButton [Cons: gridx=0, gridy=1] |
The first line indicates whether the hierarchy is closed (all the close()
corresponding to open(...)
and openNew(...)
are present). It also indicates whether the hierarchy is realized.
The next lines simply detail what was defined by the programmer for each level.
Sometimes with some layouts, it is hard to understand what space a component is occupying. For example, a label's text would be seen, but the actual size of the label is not visible.
UIHierarchy provides a debugging mechanism to act on any type of levels:
h.add("Label 1").debug(); h.add("Label 2").debug("Blue"); |
The debug
command eventually accepts some parameters (as shown above).
The effect of this command depends on the debugging class that was plugged to the configuration of the level.
The default behaviour is to change the background color of the component of the level with a color.
The parameterless call defaults to red, but any color of the Color
class constants can be supplied as well as the random
parameter.
Moreover, Swing components that are not opaque are changed so that the component appears.
Since levels that are declared support mapping to a name, UIHierarchy allows to debug several levels at once:
h.debug("mappedName1, mappedName2", "Blue"); |
And that is it, all the levels that match the specified names will be debugged.
Whenever some error is detected by the library, an explicit exception is raised.
Let's look at a (shortened) exception stack trace generated by the library:
chrriis.uihierarchy.constraints.IllegalConstraintsException: At level 1.4.1.1: "weigdhtx=1, fill=horizontal" at chrriis.uihierarchy.HParentLevel.realizeChild(Unknown Source) ... Caused by: java.lang.IllegalArgumentException: "weigdhtx" is not a known constraint key! at chrriis.uihierarchy.constraints.GridBagHConstraints.analyzeConstraints(Unknown Source) ... |
The IllegalConstraintsException
is a subclass of UIHException
that allows to spot an exact level, using its dotted decimal path.
With only a quick look at our hierarchy, we can easily find the "1.4.1.1" level:
* JPanel [Layout: GridBagLayout] - JLabel - JTextField [Cons: gridx++, insets=0:10:0:10, weightx=1, fill=horizontal] - JButton [Cons: gridx++] + JPanel [Layout: CardLayout] [Cons: gridy++, gridwidth=3, weighty=1, fill=both] + JPanel [Layout: GridBagLayout] [Cons: 4] - JLabel [Cons: weigdhtx=1, fill=horizontal] // Problematic constraints! - JLabel |
UIHierarchy was first meant to help building hierarchy of components. When this goal was reached, I felt that the library could provide powerful means to manipulate the hierarchy.
Accessors are used to access one or several levels in a hierarchy, in order to perform some operations. An operation can affect all the levels manipulated by an accessor, or even their sub-levels if needed.
Some operations can change some of the components settings, and some can modify the hierarchy.
An accessor is defined by using names that were mapped to some levels of the hierarchy.
An accessor is created using the UIH class, which itself will use the factory of accessors that is in place (this can be changed through configuration).
Once the accessor is obtained, it can be casted to the actual type of the accessor to offer more possibilities.
... Accessor anAccessor = h.createAccessor("leaf"); SingleLevelAccessor aSingleLevelAccessor = (SingleLevelAccessor)h.createAccessor("aLabel"); ParentLevelAccessor aParentLevelAccessor = (ParentLevelAccessor)h.createAccessor("aParentLevel"); |
In the above example, three accessors are created:
RootNode
or Node
) can be manipulated.
If a mapping name matches several levels and some of the levels should be omitted when creating an accessor, it is possible to exclude them.
The following example creates an accessor on the named levels "map1" and "map2", excluding levels mapping to "map3".
... Accessor anAccessor = h.createAccessor("map1, map2", "map3"); |
UIHierarchy tries to simplify as much as possible the actions that we do all the time on user interfaces. One of the most used operations is to enable or disable components according to some changing settings.
Accessors allow two types of states operations:
setHEnabled(boolean isHEnabled)
: this enables/disables the components manipulated by the accessor, as well as all the sub-levels. When enabling, all the components that were disabled before the hierarchy was set to be disabled remain disabled.setEnabled(boolean isEnabled)
: this enables/disables the components manipulated by the accessor. When enabling and if the hierarchy is disabled, the component will remain disabled, but will be enabled when the hierarchy is set to be enabled.Here is an example of an accessor that is created to disable all the levels mapped to "map1" and "map2" as well as their sub-levels:
... h.createAccessor("map1, map2").setHEnabled(false); |
One capability that UIHierarchy offers is the automatic handling of multiple conditions affecting components' state. Indeed, sometimes a component or set of components is enabled only if several conditions are met.
This is automatically taken care of by the library, when an ID is used as an additional parameter to setEnabled
and setHEnabled
. A component is enabled if all the state actions using different IDs indicate that the component is enabled.
Another action that is often performed is changing the visibility of some components. Similarly to their state, the accessors provide the setVisible(boolean isVisible)
method.
The Accessor
class provides the remove()
method that removes all the components manipulated by the accessor from their parent container. This also breaks the internal representation of a level from its parent.
Additionaly, the ParentLevelAccessor
provides several methods to modify a hierarchy from the container it manipulated:
removeAll()
: removes all the children manipulated by the parent level.add(UIH uih)
and add(int index, UIH uih)
: adds the levels from a hierarchy to the current hierarchy. A level cannot belong to two hierarchies so it is first removed from the previous hierarchy.
Here is an example on how to move some components manipulated by anAccessor
as children of aParentLevelAccessor
:
... Accessor anAccessor = h.createAccessor("leaf"); ... ParentLevelAccessor aParentLevelAccessor = (ParentLevelAccessor)h.createAccessor("aParentLevel"); ... UIH h2 = new UIH(anAccessor.getConfig()); h2.add(anAccessor).constrain("Constraints to use when will be added"); aParentLevelAccessor.add(h2); |
In the example above, we create a hierarchy using the configuration of the original hierarchy. This is not mandatory, but allows hierarchy to share the exact same configuration (and mapping definitions).
Enabling/disabling a part of the hierarchy of components or adding/removing some levels is not flexible enough. This is why UIHierarchy provides a way to run some actions on the levels manipulated by the accessor:
Object[] run(AccessorRunnable accessorRunnable)
: runs an AccessorRunnable
on the levels of the accessor.Object[] runTraversing(AccessorRunnable accessorRunnable)
: runs an AccessorRunnable
on the levels and their sub-levels.Both methods return an array of objects. The length of the array is the count of levels manipulated by the accessor. Note that the results of the actions on sub-levels are not returned.
Implementing a custom action only requires to implement the AccessorRunnable
interface.
The method defined by this interface also provides an accessor to the children of the current level (if any), so that traversing can be done in a more personalized way if needed.
Traditional layout managers are mostly pixel based, which proves to be a limitation for advanced user interfaces.
Units are important to render components with proper relative spacing whatever the font or resolution is. For example, if the user has a setup with big fonts and a high resolution, a 2-pixel gap between two labels leaves the impression of no gap at all. With the use of a gap defined as 0.5cm
for example, the spacing renders correctly.
Some advanced layout managers provide this unit handling, but it is not consistent between the different layout managers, and is not provided for the traditional layout managers.
chrriis.uihierarchy.unit
package
The chrriis.uihierarchy.unit
package contains all the necessary classes to handle units in a program.
This package does not interract with other packages and can be used to perform conversions between units.
Additionaly, the Unit
class centralizes the units registration, so that units can be used with names or abbreviations. New custom units can be added through this class.
The set of supported units, along with their possible names, consists of:
"pixel"
, "px"
"centimeter"
, "cm"
"millimeter"
, "mm"
"inch"
, "in"
"point"
, "pt"
"dlux"
, "dux"
"dluy"
, "duy"
The dialog units are a convenient unit to define spacing and other characteristics of user interfaces. Dialog units are defined so that:
- One horizontal dialog unit is equal to one-fourth of the average character width using a default font.
- One vertical dialog unit is equal to one-eighth of an average character height using a default font.
Note that because dialog units are proportional to some font, spacing, lengths and sizes remain homogeneous on systems with smaller or bigger fonts than expected.
The easiest way to make conversions from units is to use the static methods of the classes defined above.
The following example demonstrates 4 ways to convert 2.15 inches to pixels (as a rounded integer value):
System.out.println(Pixel.intValue(Inch.getUnit(), 2.15)); System.out.println(Pixel.intValue(Unit.getUnit("in"), 2.15)); System.out.println(Pixel.intValue("in", 2.15)); System.out.println(Pixel.intValue("2.15in")); |
Units are used by UIHierarchy to enhance constraints and provide more than simple pixel-based declarations.
Such declarations can be replaced by appending the unit identifier to the values representing lengths.
The following line, which is pixel based:
h.add(field1).constrain("gridx++, insets=0:10:0:10, weightx=1, fill=horizontal"); |
... can easily be enhanced to take advantage of units:
h.add(field1).constrain("gridx++, insets=0:0.7cm:0:0.7cm, weightx=1, fill=horizontal"); |
At this stage, we know how to define constraints that make use of units. But several layout managers define spacing or other characteristics at construction time.
The support of layout declarations using units is added by using the classes from the chrriis.uihierarchy.layout
package in place of the existing layout managers constructors.
The classes of this package internally call the existing layout managers, but transform the unit-based declarations to their pixel equivalent when the hierarchy gets constructed.
Here is a declaration of a BorderLayout
using units to define the gaps between the components:
import chrriis.uihierarchy.layout.BorderHLayout; ... h.open().layout(new BorderHLayout("15dlux", "15dluy")); ... |
In this chapter, you can find some common problems and tricks to ease user interfaces design.
Now that UIHierarchy is explained, it is good to see coding conventions to make development even more effective.
Note that these conventions are more or less the way I personaly use the library.
Let's see some bits of a commented source code that shows the different sections of a code made with UIHierarchy:
... // Components declarations Container contentPane = getContentPane(); JPanel hierarchyPanel = new JPanel(); hierarchyPanel.setBorder(BorderFactory.createTitledBorder("Hierarchy")); JButton disableEnableHierarchyButton = new JButton("Disable/enable hierarchy"); // Hierarchy creation final UIH h = new UIH(); h.openNew(contentPane); h.open(hierarchyPanel).layout(new BorderLayout()).constrain("Center").map("hierarchy"); ... h.close(); h.open().layout(new GridLayout(0, 2)).constrain("South"); h.add(disableEnableHierarchyButton); ... h.close(); h.close(); // Debug printing h.print(); // Listeners additions disableEnableHierarchyButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Accessor hierarchyAccessor = h.createAccessor("hierarchy"); hierarchyAccessor.setHEnabled(!hierarchyAccessor.isHEnabled()); } }); ... |
To sum up the order of things, the sections can be:
Of course, if you are into good design, you would rather follow something like:
Some special containers need to add components in a certain way. In practice, UIHierarchy is able to handle those containers using existing mechanisms (i.e. no hack).
Those containers are usually the TabbedPane and the ScrollPane.
Here is an example of a tabbed pane, in which a component and a container are added in the tabs named "TabTitle1" and "TabTitle2":
h.open(new JTabbedPane()); h.add(aComponent).constrain("TabTitle1"); h.open(aContainer).constrain("TabTitle2"); ... h.close(); h.close(); |
Scroll panes are more tricky in the sense that components are not added directly, but to its viewport. UIHierarchy allows to start a hierarchy from another component, which solves the problem:
h.open(new JScrollPane()); h.openNew(((JScrollPane)h.getContainer()).getViewport()); // addition of the content of the scroll pane h.close(); h.close(); |
Menu bars can also be handled by UIHierarchy.
If a menu item requires listeners or some specific configuration it is declared before, otherwise it can be declared inline:
// Components declarations JMenuItem myMenuItem1_1 = new JMenuItem("Item 1.1"); JMenuItem myMenuItem1_2 = new JMenuItem("Item 1.2"); JMenuItem myMenuItem2_1 = new JMenuItem("Item 2.1"); JMenuItem myMenuItem2_2 = new JMenuItem("Item 2.2"); // Hierarchy creation setJMenuBar(new JMenuBar()); UIH h = new UIH(); //-- Menu bar -- h.openNew(getJMenuBar()); h.open(new JMenu("Menu1")); h.add(myMenuItem1_1); h.add(myMenuItem1_2); h.close(); h.open(new JMenu("Menu2")); h.add(myMenuItem2_1); h.add(myMenuItem2_2); h.close(); h.close(); //-- Containment hierarchy -- h.openNew(getContentPane()); ... h.close(); // Listeners additions ... |
Configuring the library is possible through different means:
A general comment about configuration, is that if the library misses a feature, then anyone can add it.
In the Javadoc, only the public API is exposed. But in reality, a lot more is accessible: a lot of fields and methods exist as protected
, and developers may plug their own subclasses that uses those fields and methods.
A good reference is to look at how the defaults set by the library are implemented.
Most of the configuration affecting the levels of a hierarchy is contained within the UIHConfig
class.
A hierarchy defines a configuration to use when creating the levels, and the levels keep a reference to their configuration.
There are two levels of configuration:
To access the default configuration, just call UIHConfig.getDefault()
which returns the configuration object holding the defaults.
Otherwise, just call getConfig()
on the hierarchy to configure a specific hierarchy.
The configuration retains several configurable behaviors:
close()
invocation or needs an explicit call to realize()
.Additionaly, the configuration holds the mapping from names to components, to share those definitions among levels.
Each registered constraints handler is associated to the class of its corresponding layout manager.
To register some constraints handler to a layout manager class, use the setLayoutConstraints(...)
.
Note that a layout manager class that is registered does not match its subclasses: the subclasses have to be registered as well.
Each registered component creator is associated to the class of the type of object it handles.
To register some component creator to an object class, use the setComponentCreator(...)
.
Note that an object class that is registered does not match its subclasses: the subclasses have to be registered as well.
For example, you could automatically create JPanel
s with a title border and some internal insets automatically, simply by defining its title (with a special indicator).
Here is the code to add somewhere in the begining of your program. If the indicator is not found, it will invoke the original String
handler.
UIHConfig.getDefault().setComponentCreator(String.class, new StringComponentCreator() { public Component createComponent(UIHConfig uihConfig, Container parentContainer, Object component, int level) { String comp = (String)component; String indicator = "[TBP]"; if(comp.startsWith(indicator)) { JPanel panel = new JPanel(); panel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder(comp.substring(indicator.length())), BorderFactory.createEmptyBorder(0, 10, 5, 10))); return panel; } return super.createComponent(uihConfig, parentContainer, component, level); } }); |
And then, when you want to create your special panel with its title border:
h.open("[TBP]The title of the panel"); ... h.close(); |
The way accessors are created depends on the factory that is in place. A factory is a subclass of the default one, AccessorFactory
that is.
The default factory generates three types of accessors:
To set a custom factory, use the method setAccessorFactory(AccessorFactory)
from the configuration.
When debugging the levels of a hierarchy using the debug
method, a special class is used. This class is the Debugger
class or a custom subclass.
It is possible to replace the debugger in use by using the setDebugger
method from the configuration and provide a custom subclass of the Debugger
class.
Some specific applications or accessors may wish to attach some data to some levels. A concrete example is the fact of enabling a component based on conditions so that it is only enabled when all the conditions indicate it is enabled.
This capability is offered using accessors: the Accessor
base class allows to aquire a slot that can be used to store and retrieve stored data from levels.
To use this slot facility, one would create a custom accessorFactory
, which creates a custom accessor making use of a slot.
A good example to study is the code of the Accessor
class, especially the setEnabled
and setHEnabled
methods.
UIHierarchy produces really logical containment hierarchies. Since hierarchies are logical, their declarations are easy to map to XML documents.
An XML extension provides classes that can handle hierarchy constructions from XML documents.
It uses the tiny NanoXML lite library (http://nanoxml.n3.net). Make sure it is present in the class path if you try the XML module.
The principle is that components declarations is still done in the Java code, but their hierarchy is defined in an XML document.
Loading is done by using the XmlUIH
class.
Assume a file called "MyDocument.xml" contains the document. Here is a sample on how to load its hierarchy:
// Components declarations JButton testButton = new JButton("Test"); ... // Hierarchy creation try { XmlUIH xmlUIH = new XmlUIH(); xmlUIH.build(new FileInputStream("MyDocument.xml"), new Object[] { "contentPane", getContentPane(), "testButton", testButton, ... }); // Debug printing xmlUIH.getUIH().print(); } catch (Exception e) { e.printStackTrace(); } |
And this could be the content of "MyDocument.xml":
<?xml version="1.0"?> <!DOCTYPE uih PUBLIC "-//UI Hierachy DTD 1.0//EN" "http://chrriis.brainlex.com/projects/uihierarchy/uih.dtd"> <uih> <hierarchy> <rootNode name="contentPane" layout="GridBagLayout"> <leaf value="A button: " constrain="gridx=0, gridy=0"/> <leaf name="testButton" constrain="gridx=1, gridy=0, insets=0:10:0:10, weightx=1.0, fill=horizontal"/> </rootNode> </hierarchy> </uih> |
Three types of levels can be defined, which corresponds to the levels created using the UIH
class:
rootNode
: the level that is created when openNew(...)
is invoked.node
: the level that is created when open(...)
is invoked.leaf
: the level that is created when add(...)
is invoked.The same xml document can define more than one hierarchy as the hierarchy element can specify a name, which can be specified when loading.
When components are used in an XML document, they are defined with a name. From the code, components are associated to names using a component resolver, which is given to the XmlUIH
class.
Three default component resolvers are provided:
MapComponentResolver
: the developer provides a map from names to components.ArrayComponentResolver
: the developer provides an array, which alternates a name and the component that is concerned.FieldsComponentResolver
: the developer provides an object, which fields are components. It uses reflection, so that the names are the ones of the components.
Additionaly, the XmlUIH
class can accept an array, which will be used to create an ArrayComponentResolver
internally, or a Map
, which will create a MapComponentResolver
.
Anyone can define ComponentResolver
subclasses if a different way of finding components from names is needed.
Moreover, component resolvers can define an object instead of an actual Component, which will be used through its corresponding component creator to create the component.