home
products
education
support
partners
company

introduction

get started

articles

manual

tutorials

reference docs

case studies

knowledge base

goodies

screenshots

demos

echoes

Carousel zone




home

forums

downloads

bugs

mailing list
XUI Zone - manual

number of ratings 0
average rating 0.00

28 Adding components

At the heart of XUI and Carousel is the component factory. The built-in component factory constructs the XUI components and comes in two flavours; one for Swing and one for AWT. Using component factories hides much of the detail of building individual components and makes it possible to change the implementation without having to change all the client code.

Carousel allows component factories to be added and hence it can accommodate a wide range of components apart from the built-in set. Carousel also includes a facility to register new components via an XML registry and even an editor for this registry

Choosing how to install components

Of the two methods of installing components, the XML registry is by far the simpler as you can simple use the interactive registry editor and its built-in component factory without any custom coding. The alternative is to build a custom component factory and directly instantiate the components.

While creating a component factory may be more work it also gives more control over the process of creating and initializing the components. Using a component factory you can achieve things that are not normally possible with the XML registered components, for example Carousel's repeat element is implement via a special component factory.

Using the registry editor

The Integrated Component Registry Editor is a new feature in XUI 2.0

The component registry editor can be accessed by clicking the '*' button in the component palette toolbar. Almost all of Carousel's non-core components are added via this registry so you can see how the properties of the indvidual components are configured.

The editor assumes that the components are held in Jar files and are represented as Java Beans. Upon opening the editor you may add or delete Jar libraries.

The editor then scans the Jar for suitable components and displays a hierarchical view of the available components. It is important to note that the editor does not automatically include any of the components or any of the component properties. It is up to you to tell the editor what to use.

The component properties are shown as an individual component is selected. You may rename the components so as to give it a tag that is suitable for use in an XML file. Then you can choose which methods to use as properties of an individual tag. Normally only the setter methods are used in the XML but you may also use the getter methods.

The methods may also be configured to be visible in various modes:

Code Sample 28-1 - Component configuration modes

Mode

Usage

Novice

A minimal set of properties for new users

Normal

A set of properties for normal everyday use

Expert

A fuller set of properties for detail component configuration.

The idea of these modes is that reducing the number of visible properties makes it easier to locate commonly used properties. Fewer options may also make it easier for a novice to make use of the component.

Once the component and its properties have been chosen the editor generates a new components.xml file for the application. The component palette is also updated and the components can be used in the application.

The registry file for each project also acts as an overlay for the built-in registry files. Both KalIDEoscope and Carousel include registry files for their own components. These registry files are loaded as the first page is loaded after startup of the project. Each project has its own configuration file so that its configuration is independant of other projects even though it loads data from the shared sources of built-in registry files.

Carousel itself uses the registry for loading component Jar files and therefore an individual application may use components registered via several configuration files. Carousel and KalIDEoscope each register components this way and your application itself may add another, so an application will typically have several component registration files. However, for the most part you need not worry about these details as they are all catered for by the built-in regsitry editor.

The registry editor can be used in one of two modes. The first - a simple mode, is used when the editor is opened normally. In this mode only the methods with types that Carousel and XUI can construct directly from XML are allowed.

In the advanced mode, which can be accessed by holding down the CTRL key while opening the editor, all the methods are shown. In the advanced mode it is left to the user to ensure that the property values are correctly set. Normally this is ok if coding via Java, but it is difficult to achieve when using XML to define the pages.

Installing a component factory

The factory needs to be registered with the main class, this is done through the startup.properties file.

Code Sample 28-2 - Including a Component Factory

NumComponentFactories=1

ComponentFactory0=net.xoetrope.swing.SwingComponentFactory


The example adds one of the component factories shipped with XUI. This factory adds a number of Swing specific components.

Using the new components

The components added by the new factory can be used immediately. Each component type should have unique name so when adding a new component all that is necessary is that you know this name. Then the normal Java or XML coding can be used to construct the component.

Anatomy of a ComponentFactory

Every component factory must implement the XComponentConstructor interface. This interface is relatively simple and is discussed below in the context of the source code for the SwingComponentFactory class.

Code Sample 28-3 - A Component Factory Implementation

1 package net.xoetrope.swing;

2

3 import net.xoetrope.xui.XComponentConstructor;

4 import net.xoetrope.xui.XComponentFactory;

5 import java.awt.Component;

6 import java.util.Hashtable;

7

8 /**

9 * A factory for non-base Swing components such as Trees

10 *

Copyright: Copyright (c) Xoetrope Ltd., 2001-2004

11 *

$Revision: 1.3 $

12 * License see license.txt

13 */

14 public class SwingComponentFactory implements XComponentConstructor

16 {

17 private String packageName = "net.xoetrope.swing" ;

18

18 public SwingComponentFactory()

20 {

21 // Register the extra binding factory

22 SwingDataBindingFactory.register();

23 }

24

25 /**

26 * A generic factory for adding XComponents. The component is constructed, positioned and

27 * added to the parent panel if one exists. The component is named with a counter value

28 * to uniquely identify the control.

29 * This factory does not use this method and all components must be added by name

30 * When a ScrollPane is addd it becomes the parent.

31 * @param cf the calling component factory

32 * @param type a constant identifying the type of component to be created

33 * @param content the component text/content

34 */

35 public Component constructComponent( XComponentFactory cf, int type, String content )

36 {

37 return null ;

38 }

39

40 /**

41 * A generic factory for adding XComponents. The component is constructed, positioned and

42 * added to the parent panel if one exists. The component is named with a counter value

43 * to uniquely identify the control.

44 * @param cf the calling component factory

45 * @param type a name identifying the type of component to be created

46 * @param content the component text/content

47 */

48 public Component constructComponent( XComponentFactory cf, String type, String content )

49 {

50 Component comp = null ;

51 if ( type.charAt( 0 ) == 'X' )

52 type = type.substring( 1 , type.length() );53

54 if ( type.compareToIgnoreCase( "Tree" ) == 0 )

55 comp = new XTree();

56 else if ( type.compareToIgnoreCase( "Table2" ) == 0 )

57 comp = new XTable2();

58

59 return comp;

60 }

61

62 /**

63 * A generic factory method for adding non component elements.

64 * @param cf the calling component factory

65 * @param type the object type

66 * @param name a name identifying the element to be created

67 * @param content the component text/content

68 * @param attribs the element attributes if any

69 */

70 public Object addElement( XComponentFactory cf, String type, String name, String content, Hashtable attribs )

71 {

72 return null ;

73 }

74

75 /**

76 * Notify the component factories that some of their settings may have changed.

77 * This factory does not yet use any startup properties or parameters

78 */

79 public void update() {}

80

81 /**

82 * Set the package name for the factory's widgets.

83 */

84 public void setPackageName( String defPackage )

85 {

86 packageName = defPackage;

87 }

88 }

Code Sample 28-4 - Overview of the Component Factory Code

Lines 1-15

Register/Import individual components

Line 16

Names the package.

Lines 18-22

Construct a new instance of the factory and sets up the binding factory. The binding factory specifies the bindings that are used by default with the components in this factory. It is not necessary to register the binding types at this point but it is convenient to do so and this helps ensure that the bindings will be available immediately after component construction.

Lines 34-37

This is a deprecated API so no implementation is provided. The API was used in the first edition of XUI and proved difficult to extend and was therefore replaced by the API implemented in the next section.

Lines 47-59

Does the actual construction of the components. Essentially this factory is a switch on the component type name. This method is called from the component factory whenever its addComponent method is invoked. The factory iterates through the built-in component constructors and then the registered constructors till a component has been constructed for the named type. In this way it is important to name the component types uniquely and to order the factories in the correct order should there be a name conflict.

Lines 69-71

This method is not implemented but is required by the interface specification. The interface makes it possible to include non component information in a page description. For example Carousel's survey packages allow the response to questions to be specified along with the page. The response element is used by the Question component and does not itself create a component

The addElement method used for such implementations. The method is only called if no factory chooses to construct a component for the element. The essential difference from the previous method is that the addElement does not add a component to the page.

Lines 83-87

This method is used by the component factory and must be provided by all factories. The package name is that of the components rather than the factory itself.

: .

Code Sample 28-5 - Using the New Components





Sometimes it is useful to register components without first having to create a component factory. Components can be registered by adding entries to an XML file. The XRegisteredComponentFactory can be used to load components specified in this way.

Setup the factory

First the factory needs to be registered with the main class, this is done through the startup.properties file.

Code Sample 28-6 - Adding the XRegisteredComponentFactory

NumComponentFactories=1
ComponentFactory0=net.xoetrope.optional.registry.XRegisteredComponentFactory

Then, as this class is loaded it attempts to read the XML file detailing the components, this file is also named in the startup.properties file with the "ComponentRegistry" parameters e.g.

Code Sample 28-7 - Specifying the Component Registry

ComponentRegistry=components.xml

The components file then details each component for example:

Code Sample 28-8 - Using the New Components


  
     
     
  

A sample component

To demonstrate how easy it is to include a new component in Carousel we have included the source for a test component in the KalIDEoscope source distribution at test.xoetrope.components.TestShape . The component doesn't do very much except paint a few trivial shapes. Here's the source code:

Code Sample 28-9 - TestShape a sample component

package test.xoetrope.components;

import java.awt.Dimension;

import java.awt.Graphics;

import javax.swing.JComponent;

import net.xoetrope.xui.XAttributedComponent;

/**

* Draws a simple shape

*

Copyright: Copyright Xoetrope Ltd. (c) 2001-2005

*

License: see license.txt

*

$Revision: 1.1 $

*/

public class TestShape extends JComponent implements XAttributedComponent

{

protected int shape = 0;

public static final int RECTANGLE = 0;

public static final int ORTHO_LINE = 1;

public static final int EXTRABOLD_HORIZONTAL = 2;

public static final int BOLD_HORIZONTAL = 3;

public static final int NORMAL_HORIZONTAL = 4;

public static final int THIN_HORIZONTAL = 5;

public static final int EXTRABOLD_VERTICAL = 6;

public static final int BOLD_VERTICAL = 7;

public static final int NORMAL_VERTICAL = 8;

public static final int THIN_VERTICAL = 9;

public static final int RIGHT_TOP_LINE = 10;

public static final int LEFT_TOP_LINE = 11;

public static final int ELLIPSE = 12;

public static final int SOLID_ELLIPSE = 13;

public static final int SOLID_DIAMOND = 14;

public static final int DIAMOND = 15;

/**

* Constructor for a new XShape

*/

public TestShape()

{

}

/**

* Fills the shape with the background color

* @param g the graphics context

*/

public void paintComponent( Graphics g )

{

super.paintComponent( g );

Dimension d = getSize();

g.setColor( getForeground());

switch( shape ) {

case RECTANGLE:

g.fillRect( 0, 0, d.width, d.height );

break;

case ORTHO_LINE:

g.fillRect( 0, 0, getSize().width, getSize().height );

break;

case EXTRABOLD_HORIZONTAL:

g.drawLine( 0, 0 +3, d.width, 3 );

case BOLD_HORIZONTAL:

g.drawLine( 0, 0 +2, d.width, 2 );

case NORMAL_HORIZONTAL:

g.drawLine( 0, 0 +1, d.width, 1 );

case THIN_HORIZONTAL:

g.drawLine( 0, 0, d.width, 0 );

break;

case EXTRABOLD_VERTICAL:

g.drawLine( 3, 0, 3, d.height );

case BOLD_VERTICAL:

g.drawLine( 2, 0, 2, d.height );

case NORMAL_VERTICAL:

g.drawLine( 1, 0, 1, d.height );

case THIN_VERTICAL:

g.drawLine( 0, 0, 0, d.height );

break;

case EXTRABOLD_VERTICAL:

g.drawLine( 3, 0, 3, d.height );

case BOLD_VERTICAL:

g.drawLine( 2, 0, 2, d.height );

case NORMAL_VERTICAL:

g.drawLine( 1, 0, 1, d.height );

case THIN_VERTICAL:

g.drawLine( 0, 0, 0, d.height );

break;

case RIGHT_TOP_LINE:

g.drawLine( d.width, 0, 0, d.height );

break;

case LEFT_TOP_LINE:

g.drawLine( 0, 0, d.width, d.height );

break;

case ELLIPSE:

g.drawOval( 0, 0, d.width, d.height );

break;

case SOLID_ELLIPSE:

g.fillOval( 0, 0, d.width, d.height );

break;

case SOLID_DIAMOND:

case DIAMOND:

{

int[] xpts = new int[ 4 ];

int[] ypts = new int[ 4 ];

xpts[ 0 ] = 0 + d.width / 2;

xpts[ 1 ] = 0 + d.width;

xpts[ 2 ] = 0 + d.width / 2;

xpts[ 3 ] = 0;

ypts[ 0 ] = 0;

ypts[ 1 ] = 0 + d.height / 2;

ypts[ 2 ] = 0 + d.height;

ypts[ 3 ] = 0 + d.height / 2;

if ( shape == SOLID_DIAMOND )

g.fillPolygon( xpts, ypts, 4 );

else

g.drawPolygon( xpts, ypts, 4 );

}

break;

}

}

/**

* Get shape identifier. The ID is an enumerated constant

* @param shapeId the new shape ID.

*/

public void setShape( int shapeId )

{

shape = shapeId;

repaint();

}

/**

* Get shape identifier.

* @return the enumerated constant for this shape

*/

public int getShape()

{

return shape;

}

/**

* Set one or more attributes of the component. Currently this handles the

* attributes:

*

    *

  1. shape, value=1 to 15
  2. *

* @param attribName the attribute name

* @param attribValue the attribute value

*/

public void setAttribute( String attribName, String attribValue )

{

if ( attribName.compareTo( "shape" ) == 0 )

shape = new Integer( attribValue ).intValue();

repaint( 100 );

}

}

There isn't an awful lot to say about this component as it does so little and the details of the painting are unimportant. What is important though is the interface it offers for programming within Carousel.

The component paints a shape which is dictated by the shape property. This property has a getter and a setter method ( getShape and setShape ).

The component also implements the XAttributedComponent method to assist the setting up of the component via XML. The setAttribute method required by this interface sets the value of an attribute, in this case the ' shape ' attribute.

If we then build this component into a Jar file for redistribution it can be used within Carousel. Using the component registry editor we can add the file and select the Shape property for inclusion. This process generates the following XML:

Code Sample 28-10 - The XML registration for the test component.

Special components

Sometimes the component factories may not be enough to allow you handle the needs of an individual component. However, as a Java framework you can employ the normal programming techniques to implement your functionality. This can take you from one line code fragments for setting an esoteric component attribute to complete sets of custom classes. You can freely mix XUI code, XUI XML and normal Java and to illustrate this ability to extend the framework lets look at how to add a web browser component.

Hooking up a web browser

Using the Java.net JDIC project (http://jdic.dev.java.net) we can hook up an instance of the desktop's default web browser. In this example the browser will be embedded in a popup window, an instance of the XDialog class and therefore we need to subclass XDialog :

Code Sample 28-11 - Embed a web browser.

package net.xoetrope.mypackage;

import java.net.URL;

import java.awt.BorderLayout;

import java.awt.Panel;

import org.jdesktop.jdic.browser.WebBrowser;

import net.xoetrope.swing.XDialog;

import net.xoetrope.xui.data.XBaseModel;

import net.xoetrope.xui.data.XModel;

import javax.swing.SwingUtilities;

import org.jdesktop.jdic.browser.WebBrowserListener;

import org.jdesktop.jdic.browser.WebBrowserEvent;

import java.net.URLConnection;

/**

* Show more information about a component

*

Copyright Xoetrope Ltd. (c) 2003-2004

*

License: see license.txt

* $Revision: 1.3 $ not attributable

*/

public class XBrowserDialog extends XDialog implements WebBrowserListener

{

XModel selectedNode;

int nodeNumber;

XDialog webDialog;

WebBrowser webBrowser;

public XBrowserDialog()

{

}

public void pageActivated()

{

SwingUtilities.invokeLater( new Runnable()

{

public void run()

{

website();

}

} );

}

public void done()

{

if ( wasMouseClicked() ) {

stop();

closeDlg();

}

}

/**

* Show the website

*/

public void website()

{

try {

// Check if the net is accessible

URL url = new URL( "https://xoetrope.com" );

URLConnection conn = url.openConnection();

if ( conn != null ) {

conn.connect();

String websiteUrl = ( String ) getAttribute( "url", "websiteBtn" );

webDialog = new XDialog( false, 0 );

webDialog.setCaption( "Internet content" );

webDialog.setModal( true );

//Use below code to check the status of the navigation process,

//or register a listener for the notification events.

webBrowser = new WebBrowser();

webBrowser.setDebug( true );

webBrowser.addWebBrowserListener( this );

WebBrowser.Status myStatus = webBrowser.getStatus();

try {

webBrowser.setURL( new URL( websiteUrl ) );

}

catch ( Exception e ) {

System.out.println( e.getMessage() );

return;

}

Panel panel = new Panel();

panel.setLayout( new BorderLayout() );

panel.setLocation( 0, 0 );

panel.setSize( 700, 500 );

panel.add( webBrowser, BorderLayout.CENTER );

webDialog.setLayout( new BorderLayout() );

webDialog.getContentPane().add( panel, BorderLayout.CENTER );

webDialog.setSize( 700, 500 );

webDialog.doLayout();

webDialog.showDialog( parent );

closeDlg();

}

}

catch ( Exception ex ) {

}

}

private void stop()

{

if ( webBrowser != null ) {

webBrowser.stop();

webBrowser.setVisible( false );

webBrowser = null;

}

}

// Methods from WebBrowserListener

public void downloadStarted(WebBrowserEvent webBrowserEvent)

{}

public void downloadCompleted(WebBrowserEvent webBrowserEvent)

{

// webDialog.hide();

}

public void downloadProgress(WebBrowserEvent webBrowserEvent)

{}

public void downloadError(WebBrowserEvent webBrowserEvent)

{}

public void titleChange(WebBrowserEvent webBrowserEvent)

{}

public void statusTextChange(WebBrowserEvent webBrowserEvent)

{}

}

Much of the above code is dictated by the WebBrowserListener interface but it is worth noting a couple of points in relation to the code. First the pageActivated method use the SwingUtilities invokeLater method to delay display of the web page as the download of the web content may take considerably longer than displaying the dialog. Secondly the code calls the web browser's setDebug( true ) method to turn on extra diagnostics. The method causes additional information to be output to the console and this can be quite useful given the number of components involved in the process of displaying the web page and the fact that some of these components are native or operating system components and fall outside of the normal realm of debugging for Java programs.

Using third party components in this way also introduces issues with regard to distribution. While the development environment may allow you to include all the various sub components needed design and build your application yo may need to take some additional steps to ensure that the component can be properly used on end user systems. This additional requirement may simply be to include some additional Jar files or it may be more complex. Unfortunately Carousel can do little of this work for you but once you have figured out the requirements it should be possible to include the additional step(s), including the steps necessary to build the distribution files within your project's ANT build file.

Adding a splash screen

The customization of a component is not restricted to creation time and you can continue to interact and customize a component as you would in a typical Java application. To demonstrate this capability we will create an application splash screen that automatically dismisses itself after five seconds.

To display a splash screen for an application the simplest method is to invoke the display of a popup dialog in your application's first page. The process requires a small amount of Java coding. The display code can be embedded in the first page's pageActivated method as below:

Code Sample 28-12 - Launch a splash screen

private static boolean splashActivated = false;

public void pageActivated()

{

// This flag prevents the splash screen being redisplayed if the first page is reactivated

// or shown again.

if ( !splashActivated ) {

showVersionInfoDialog( true );

splashActivated = true;

}

}

private XDialog showVersionInfoDialog( boolean modal )

{

final XDialog popupDialog = (XDialog)pageMgr.loadPage( "SplashScreen" );

final XPage thisPage = this;

final boolean isModal = modal;

SwingUtilities.invokeLater( new Runnable()

{

public void run()

{

// Only needed if the content contains bound controls.

popupDialog.updateBindings();

popupDialog.setModal( isModal );

// Force the dialog to calculate its size

popupDialog.pack();

popupDialog.setSaveOnClose( false );

// Sleep for 5 seconds and then close the dialog - only for the 'modeless' version.

// Change or remove the next if statement if you want a model splash screen.

// to be dismissed on the timer

if ( !isModal ) {

new Thread()

{

public void run()

{

try {

sleep( 5000 );

}

catch ( Exception ex ) {

}

popupDialog.closeDlg();

}

}

.start();

}

popupDialog.showDialog( thisPage, "Welcome to my application", null );

}

} );

return popupDialog;

}

The showVersionInfoDialog method loads the SplashScreen page (it can be setup like any other dialog/page) and then starts a background thread that will dismiss the dialog after a sleep period of 5000 milliseconds.

Finally in implementing the actual splash screen it is important to note that the page being displayed is assumed to be an instance of XDialog and as such it needs to derived from the XDialog class instead of XPage . If you are using XML don't forget to specify the class in the page declaration as follows:

Code Sample 28-13 - Specify the XDialog class for your splash screen in XML

Like in the case of the web browser the interesting code embedded in a call to invokeLater .

Dropping to Java can help work around many limitations and solve many more problems than the above examples. In fact you should be able to employ just about any type of component or component feature within your Carousel application. And remember, where XML is insufficient you can supplement it with Java.

comments


If you were logged in you could rate this article or add a comment.