DOM et UI

Cette section explique pas à pas comment implémenter sa propre grammaire XML et son propre renderer UI. Cette exemple s'appuiera sur une grammaire XML MyXMLMarkup qui sera indentifiée par le namespace http://tk-ui.sourceforge.net/MyXMLMarkup .Le renderer implémenté sera SWT.

Le principe est de charger dans un DOM Document implémentant l'interface org.akrogen.tkui.core.dom.IDOMDocument un contenu MyXMLMarkup . Ce DOM document doit être initialisé avec deux types de factory :
  • org.akrogen.tkui.core.dom.IDOMElementFactory qui est la factory DOM à utiliser pour instancier un DOM Element org.akrogen.tkui.core.dom.IDOMElementNS en fonction d'un namespaceURI (dans notre cas http://tk-ui.sourceforge.net/MyXMLMarkup ) et un localName (dans notre cas MyTextbox ). Cette factory est utilisée lorsque la méthode DOM Document#createElementNS() est appelée. Dans notre cas, la factory devra retourner une instance de MyTextboxElementImpl lorsque localName=MyTextbox.
  • org.akrogen.tkui.core.ui.elements.IUIElementFactory qui est la factory UI à utiliser pour instancier un org.akrogen.tkui.core.ui.elements.IUIElement qui permet de wrapper une widget d'un renderer (Swing, SWT..). Dans notre cas la factory devra retourner une instance de MySWTTextImpl qui permet de wrapper une widget SWT Text.
Les étapes seront :
  • Implémenter sa propre grammaire MyXMLMarkup . L'exemple permettra d'interpréter la description suivante :
    <MyPage xmlns="http://tk-ui.sourceforge.net/MyXMLMarkup" >
    	<MyTextbox text="bla bla bla" />
    </MyPage>
    en SWT comme ceci :
  • Implémenter son propre UI renderer à l'aide de SWT.

Vous pourrez trouver l'exemple de cette section dans le projet Eclipse org.akrogen.tkui.samples.myxmlmarkup-1 stocké sur SVN.

XML Markup

DOM Document

La classe org.akrogen.tkui.impl.core.dom.bindings.DOMDocumentBindableImpl est l'implémentation du org.akrogen.tkui.core.dom.IDOMDocument de TK-UI qui permet de charger une grammaire XML et de la rendre en Swing, SWT.... Elle permet de gérer le binding à l'aide de JFace Databinding.

DOM MyTextbox Element

Dans notre cas nous voulons interpreter l'element MyTextbox en widget SWT Text. Pour cela il faut définir la classe MyTextboxElementImpl qui hérite de org.akrogen.tkui.impl.core.dom.bindings.DOMElementNSBindableImpl comme ceci :
package org.akrogen.tkui.samples.myxmlmarkup.impl.dom;

import org.akrogen.tkui.impl.core.dom.css.DOMElementNSStylableImpl;
import org.apache.xerces.dom.CoreDocumentImpl;
import org.w3c.dom.DOMException;

public class MyTextboxElementImpl extends DOMElementNSStylableImpl {

	public MyTextboxElementImpl(CoreDocumentImpl ownerDocument,
			String namespaceURI, String qualifiedName, String localName)
			throws DOMException {
		super(ownerDocument, namespaceURI, qualifiedName, localName);
	}

	public String getUIElementId() {
		return "myText";
	}
	
	protected void applyBindings() {
		// TODO : manage bindings between UI and DOM 
	}
}
le constructeur de MyTextboxElementImpl attend plusieurs paramètres qui seront utilisés lorsque la description XML sera importé dans le DOM Document. La métode getUIElementId retourne "myText" . Cette constante sera ensuite utilisée par le DOM DOMDocumentBindableImpl pour faire le lien entre l'element DOM MyTextbox et l'élement UI qui wrappe une widget SWT Text. Ceci sera décrit plus tard dans la section UI Elements Factory.

DOM Elements Factory

Il faut ensuite créer la factory MySWTElementFactoryImpl . Dans notre cas, il faut instancier un element MyTextboxElementImpl, lorsque l'on trouvera le nom MyTextbox lors de l'import XML.
package org.akrogen.tkui.samples.myxmlmarkup.dom;

import org.akrogen.tkui.core.dom.IDOMElementFactory;
import org.akrogen.tkui.samples.myxmlmarkup.impl.dom.MyTextboxElementImpl;
import org.apache.xerces.dom.CoreDocumentImpl;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

public class MyDOMElementFactoryImpl implements IDOMElementFactory {

	private static final IDOMElementFactory instance = new MyDOMElementFactoryImpl();
	
	public static IDOMElementFactory getInstance() {
		return instance;
	}
	
	public String getNamespaceURI() {	
		return "http://tk-ui.sourceforge.net/MyXMLMarkup";
	}
	
	public Element createElementNS(Document ownerDocument, String namespaceURI,
			String qualifiedName, String localName) {
		if ("MyTextbox".equals(localName)) {
			return new MyTextboxElementImpl((CoreDocumentImpl) ownerDocument,
					namespaceURI, qualifiedName, localName);
		}
		return null;
	}
}
La métode createElementNS permet d'instancier les elements en fonction du localName. Dans notre cas si localName="MyTextbox", on retourne une instance de MyTextboxElementImpl. La méthode getNamespaceURI retourne le namespace que doit traiter cette factory. Pour rappel voici la grammaire que l'on souhaite traiter.
<MyPage xmlns="http://tk-ui.sourceforge.net/MyXMLMarkup" >
	<MyTextbox text="bla bla bla" />
</MyPage>
Comme vous pouvez le remarquer, l'element racine MyPage a le namespace http://tk-ui.sourceforge.net/MyXMLMarkup .

UI

UI Text

Implementation SWT de Text avec la classe MySWTTextImpl. Toute implementation d'une UI doit implémenter l'interface org.akrogen.tkui.core.ui.elements.IUIELement . Il existe une classe abstraite de base org.akrogen.tkui.impl.core.ui.AbstractUIElement qui définit une partie des getters/setters de l'interface IUIELement. Dans notre exemple nous partirons sur cette classe abstraite. Voici le code de MySWTTextImpl :
package org.akrogen.tkui.samples.myxmlmarkup.impl.ui;

import org.akrogen.tkui.core.ui.elements.IUIElementInfo;
import org.akrogen.tkui.core.ui.elements.IUINativeElement;
import org.akrogen.tkui.impl.core.ui.bindings.AbstractUIElementBindable;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Text;

public class MySWTTextImpl extends AbstractUIElementBindable {
	
	protected Object buildNativeWidget(IUINativeElement parentUIElement,
			IUIElementInfo uiElementInfo, IUINativeElement uiNativeElement) {
		Composite parent = (Composite)parentUIElement.getNativeWidget();
		Text text = new Text(parent, SWT.BORDER);
		return text;
	}

	public void dispose() {
		((Text)getNativeWidget()) .dispose();
	}

	public boolean isDisposed() {
		return ((Text)getNativeWidget()).isDisposed();
	}

	public IUINativeElement getUIWindowElement() {
		return null;
	}

}

UI Elements factory

Factory pour les widgets UI (implemente en SWT)
package org.akrogen.tkui.samples.myxmlmarkup.ui;

import org.akrogen.tkui.core.ui.UIConstants;
import org.akrogen.tkui.core.ui.elements.IUIElementFactory;
import org.akrogen.tkui.core.ui.elements.IUINativeElement;
import org.akrogen.tkui.impl.core.ui.AbstractUIElementFactory;
import org.akrogen.tkui.samples.myxmlmarkup.impl.ui.MySWTTextImpl;

public class MySWTElementFactoryImpl extends AbstractUIElementFactory {

	private static final IUIElementFactory instance = new MySWTElementFactoryImpl();

	public static IUIElementFactory getInstance() {
		return instance;
	}

	public String getId() {
		return "MySWT";
	}

	public String getType() {
		return UIConstants.SWT_UI_ELEMENT_FACTORY_TYPE;
	}

	protected void registerDefaultUIElements() {
		registerUIElement("myText", MySWTTextImpl.class);
	}

	public IUINativeElement getUINativeElement(final Object nativeWidget) {
		return new IUINativeElement() {
			public Object getNativeWidget() {
				return nativeWidget;
			}

			public IUINativeElement getUIWindowElement() {
				return null;
			}
		};
	}
}
getId() identifiant de l'UI factory. getType() type de renderer (dans notre cas SWT). registerDefaultUIElements enregistre sous la cle "myText" la classe MySWTTextImpl. Rappelons le code de MyTextboxElementImpl
....
		public String getUIElementId() {
			return "myText";
		}
Comme vous pouvez le remarquer getUIElementId retourne "myText". MyTextboxElementImpl sera mappé avec l'UI MySWTTextImpl.

Test

Voici la classe de test HTMLLoaderTest :
package org.akrogen.tkui.samples.myxmlmarkup;

import java.io.InputStream;

import org.akrogen.tkui.core.IConfiguration;
import org.akrogen.tkui.core.dom.bindings.IDOMDocumentBindable;
import org.akrogen.tkui.core.ui.elements.IUIElementFactory;
import org.akrogen.tkui.impl.core.ConfigurationImpl;
import org.akrogen.tkui.samples.myxmlmarkup.dom.MyDOMElementFactoryImpl;
import org.akrogen.tkui.samples.myxmlmarkup.impl.dom.MyDocumentImpl;
import org.akrogen.tkui.samples.myxmlmarkup.ui.MySWTElementFactoryImpl;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;

public class TestMyPage {

	public static void main(String[] args) {
		try {
			Display display = new Display();
			Shell shell = new Shell(display, SWT.SHELL_TRIM);
			shell.setLayout(new FillLayout());

			// 2. Load MyPage into Xerces DOM parser
			InputStream source = TestMyPage.class
					.getResourceAsStream("page.myxmlmarkup");
			org.apache.xerces.parsers.DOMParser parser = new org.apache.xerces.parsers.DOMParser();
			parser.setFeature("http://xml.org/sax/features/namespaces", true);
			parser.parse(new InputSource(source));
			source.close();

			// 3. Initialise TK-UI configuration
			IConfiguration configuration = new ConfigurationImpl();
			// Register My DOM element factory
			configuration
					.registerDOMElementFactory(MyDOMElementFactoryImpl.getInstance());
			// Register My SWT UI element factory
			IUIElementFactory mySWTUIElementFactory = MySWTElementFactoryImpl.getInstance();
			configuration.registerUIElementFactory(mySWTUIElementFactory);

			// 4. Initialize TK-UI DOM document
			final IDOMDocumentBindable document = new MyDocumentImpl();
			// Regsiter configuration
			document.setConfiguration(configuration);
			// Regsiter UI Element SWT factory to use to create SWT widgets.
			document.setUIElementFactoryId(mySWTUIElementFactory.getId());
			// Set Shell as root. SWT widgets created.
			document.setNativeWidgetRoot(shell);

			// 6. Import nodes of the DOM HTML into TK-UI DOM document
			// and add nodes imported into TK-UI DOM document
			Document sourceDocument = parser.getDocument();
			Node newNode = document.importNode(sourceDocument
					.getDocumentElement(), true);
			document.appendChild(newNode);

			shell.pack();
			shell.open();

			while (!shell.isDisposed()) {
				if (!display.readAndDispatch())
					display.sleep();
			}

			shell.dispose();

		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

Test/Toolkit

Le code de Test ci dessous peut paraitre lourd et il est possible de le simplifier en utilisant des IToolkit et une instance de IPlatform. TK-UI se configure a l'aide d'uen instance IConfiguration qui stocke les IDOMElementFactory et IUIElementFactory. Un ITookit est une instance qui permet de configurer un IConfiguration. Dans cette section nous allons creer un Toolkit qui permet d'enregistrer le IDOMElementFactory (MyDOMElementFactoryImpl) et le IUIElementFactory (MySWTElementFactoryImpl).

DOM Toolkit

Creer la classe MyDOMToolkit. Cette classe enregistre la factory MyDOMElementFactoryImpl au sein d'une instance configuration :

package org.akrogen.tkui.samples.myxmlmarkup.dom.toolkit;

import org.akrogen.tkui.core.IConfiguration;
import org.akrogen.tkui.core.dom.toolkit.IDOMToolkit;
import org.akrogen.tkui.samples.myxmlmarkup.dom.MyDOMElementFactoryImpl;

public class MyDOMToolkit implements IDOMToolkit {

	private static final IDOMToolkit toolkit = new MyDOMToolkit();

	public static IDOMToolkit getDefaultToolkit() {
		return toolkit;
	}

	public void initialize(IConfiguration configuration) {
		configuration.registerDOMElementFactory(MyDOMElementFactoryImpl
				.getInstance());
	}

}

UI Toolkit

Creer la classe MySWTToolkit. Cette classe enregistre la factory MySWTElementFactoryImpl au sein d'une instance configuration :

package org.akrogen.tkui.samples.myxmlmarkup.ui.toolkit;

import org.akrogen.tkui.core.IConfiguration;
import org.akrogen.tkui.core.ui.toolkit.IUIToolkit;
import org.akrogen.tkui.impl.core.ui.toolkit.AbstractUIToolkitImpl;
import org.akrogen.tkui.samples.myxmlmarkup.ui.MySWTElementFactoryImpl;
import org.eclipse.core.databinding.DataBindingContext;

public class MySWTToolkit extends AbstractUIToolkitImpl {

	private static final IUIToolkit toolkit = new MySWTToolkit();

	public static IUIToolkit getDefaultToolkit() {
		return toolkit;
	}

	protected void registerUIElementFactories(IConfiguration configuration) {
		configuration.registerUIElementFactory(MySWTElementFactoryImpl
				.getInstance());
	}

	public String getDefaultUIElementFactoryId() {
		return MySWTElementFactoryImpl.getInstance().getId();
	}

	public DataBindingContext getDataBindingContext(Object nativeWidgetRoot) {
		return null;
	}

}

Platform

Creer classe MySWTPlatform qui initialise le IConfiguration avec les 2 toolkits crees ci dessus. MySWTPlatform est uen glue entre le renderer SWT et la grammaire XML My.
package org.akrogen.tkui.samples.myxmlmarkup.platform;

import org.akrogen.tkui.core.IConfiguration;
import org.akrogen.tkui.core.exceptions.TkuiRuntimeException;
import org.akrogen.tkui.core.platform.IPlatform;
import org.akrogen.tkui.impl.core.platform.AbstractPlatformImpl;
import org.akrogen.tkui.samples.myxmlmarkup.dom.toolkit.MyDOMToolkit;
import org.akrogen.tkui.samples.myxmlmarkup.ui.toolkit.MySWTToolkit;
import org.eclipse.swt.widgets.Widget;

public class MySWTPlatform extends AbstractPlatformImpl {

	private static IPlatform instance = new MySWTPlatform();

	public static IPlatform getDefaultPlatform() {
		return instance;
	}

	public MySWTPlatform() {
		super(MySWTToolkit.getDefaultToolkit());
	}

	protected void initializeDOMToolkit() {
		IConfiguration configuration = super.getConfiguration();
		MyDOMToolkit.getDefaultToolkit().initialize(configuration);
	}

	protected void checkNativeWidgetRoot(Object nativeWidgetRoot) {
		if (!(nativeWidgetRoot instanceof Widget)) {
			throw new TkuiRuntimeException(
					"widget parameter of createDocument method must be instance of "
							+ Widget.class);
		}
	}

}

Test/Platform

package org.akrogen.tkui.samples.myxmlmarkup;

import org.akrogen.tkui.core.dom.bindings.IDOMDocumentBindable;
import org.akrogen.tkui.core.platform.IPlatform;
import org.akrogen.tkui.samples.myxmlmarkup.platform.MySWTPlatform;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

public class TestMyPageWithPlatform {

	public static void main(String[] args) {
		try {
			Display display = new Display();
			Shell shell = new Shell(display, SWT.SHELL_TRIM);
			shell.setLayout(new FillLayout());
			
			// Get MySWTPlatform
			IPlatform platform = MySWTPlatform.getDefaultPlatform();
			IDOMDocumentBindable document = platform.createDocument(shell,
					null, null);
			// Load MyPage into TK-UI DOM Document
			platform.loadDocument(TestMyPageWithPlatform.class
					.getResourceAsStream("page.myxmlmarkup"), document);

			shell.pack();
			shell.open();

			while (!shell.isDisposed()) {
				if (!display.readAndDispatch())
					display.sleep();
			}

			shell.dispose();

		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
En passant par une instance de platform, le code est tres simplifié. TK-UI fournit SWTPlatform qui permet de configurer le renderer SWT avec les grammaires XUL et XHTML.