UIHierarchy manual

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


Table of contents

1. About UIHierarchy
1.1. Generalities
1.2. Supported systems
1.3. Installation
1.4. Updates
1.5. License
2. Simple example
2.1. Design
2.2. Traditional implementation
2.3. UIH implementation
2.4. Some explanations
2.5. More examples
3. Simplified declarations
3.1. Implicit component declaration
3.2. Simplified constraints
4. Levels mapping
5. Layout managers
5.1. Declaration
5.2. Constraints
6. Debugging facilities
6.1. Solving hierarchy problems
6.2. Working on a component
6.3. Exceptions
7. Accessors
7.1. Declarations
7.2. Components operations
7.3. Modification of a hierarchy
7.4. Custom actions
8. Units
8.1. The chrriis.uihierarchy.unit package
8.2. UIH handling
8.3. Units in layouts
9. Some tricks
9.1. Some coding practices
9.2. Using tab panes or scroll panes
9.3. Declaring a menu bar
10. Configuration
10.1. UIHConfig
10.2. Constraints handlers
10.3. Component creators
10.4. Accessors
10.5. Debugging
10.6. Storing data in levels
11. XML
11.1. Usage
11.2. Component resolvers


1. About UIHierarchy

1.1. Generalities

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:

1.2. Supported systems

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.

1.3. Installation

No special step is required appart from adding the UIHierarchy.jar library file to your project's classpath.

1.4. Updates

For all information regarding latest versions, please refer to the UIHierarchy website:
http://chrriis.brainlex.com/projects/uihierarchy

1.5. License

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.

2. Simple example

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.

2.1. Design

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.

2.2. Traditional implementation

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
...

2.3. UIH implementation

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.

2.4. Some explanations

There are only a few methods to know:

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:

2.5. More examples

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.

3. Simplified declarations

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.

3.1. Implicit component declaration

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: ");
...

3.2. Simplified constraints

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.

4. Levels mapping

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.

5. Layout managers

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.

5.1. Declaration

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.

5.2. Constraints

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.

6. Debugging facilities

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:

6.1. Solving hierarchy problems

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.

6.2. Working on a component

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.

6.3. Exceptions

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

7. Accessors

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.

7.1. Declarations

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:

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");

7.2. Components operations

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:

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.

7.3. Modification of a hierarchy

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:

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).

7.4. Custom actions

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:

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.

8. Units

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.

8.1. The 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:

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"));

8.2. UIH handling

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");

8.3. Units in layouts

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"));
...

9. Some tricks

In this chapter, you can find some common problems and tricks to ease user interfaces design.

9.1. Some coding practices

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:

9.2. Using tab panes or scroll panes

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();

9.3. Declaring a menu bar

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
...

10. Configuration

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.

10.1. UIHConfig

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:

Additionaly, the configuration holds the mapping from names to components, to share those definitions among levels.

10.2. Constraints handlers

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.

10.3. Component creators

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 JPanels 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();

10.4. Accessors

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.

10.5. Debugging

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.

10.6. Storing data in levels

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.

11. XML

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.

11.1. Usage

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:

The same xml document can define more than one hierarchy as the hierarchy element can specify a name, which can be specified when loading.

11.2. Component resolvers

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:

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.