Introduction
XForms is an important recommendation from the W3C that enables complex XML-handling applications to be defined in a simple, declarative syntax. This article demonstrates how you can harness this technology in your own C# applications. However, the same techniques may be easily applied to other .NET languages, or indeed any programming language that has a COM binding. The XHTML and XForms rendering power at the heart of it all will be provided by Ubiquity formsPlayer, a free open-source XForms processor.
Why XForms?
The advantages of XForms over other solutions have been argued far more eloquently elsewhere than I could manage, so I won't go into great detail here. Instead, those of you interested in reading around the subject in more depth are directed to the links that appear at the foot of this article. To summarise some of the most salient points, however:
- XForms documents are device- and platform-independent.
- XForms uses a declarative syntax, which aids faster development and reduces the potential for bugs when compared to more traditional, procedural approaches.
- XForms separates data, user interface, and application logic according to the model-view-controller (MVC) design pattern.
- XForms is an open standard, which means that the same document may be deployed unmodified across all conforming processors.
- XForms documents are inherently accessible by design.
- XForms enables comprehensive input validation at the point of entry, reducing the need for server round trips in distributed web applications.
Getting Started
Before you can begin, you will need to install Ubiquity formsPlayer, which is a free and a reasonably small download. Additionally, you'll also need to separately install the Ubiquity Browser eXtensions to get the embeddable Renderer component. Once both of these have been installed, you must import two COM libraries into your C# project via the 'Add Reference' dialog:
- [Program Files]\Common Files\UBX\Renderer\Renderer5.dll
- [Program Files]\Common Files\UBX\DOM\DOM2Events.dll
The first of these libraries, the Renderer, provides a wrapper for all of the XHTML and XForms rendering functionality. The second component, an implementation of the W3C's DOM Level 2 Events recommendation, facilitates communication between your application, the Renderer, and the live document.
Instantiating the Renderer
Firstly, you must add a new class to your application, representing the window that will host the Renderer. In the example code, this class is called RenHost
. The constructor for this class needs to instantiate a copy of RenderLib.RendererClass
, storing a pointer to the instance's IRender
interface:
public class RenHost : Form {
private RenderLib.IRender renderer;
...
public RenHost()
{
InitializeComponent();
this.components = new System.ComponentModel.Container();
this.components.Add(this.buttonOpen);
this.components.Add(this.labelUrl);
this.components.Add(this.textboxUrl);
this.components.Add(this.panelRenderer);
this.createRenderer();
}
private void createRenderer()
{
this.renderer = (RenderLib.IRender)new
RenderLib.RendererClass();
}
...
}
Next, this.renderer
must be made a child of a Panel
on your RenHost
class. This is achieved by calling Initialise()
and passing in the Panel
's handle as a parameter, like so:
private void createRenderer() {
this.renderer = (RenderLib.IRender)new RenderLib.RendererClass();
this.renderer.Initialise(null,
(int)this.panelRenderer.Handle);
}
Dispatching Events
Your application may now customise various characteristics of this.renderer
, such as setting its initial size or instructing it to delegate any navigation requests to your application. These properties and many others are all set by dispatching events to this.renderer
's IEventTarget
interface.
To simplify this process, add a new method to your class along the following lines:
private void dispatchEvent(String type, int arg1, int arg2, String arg3) {
DOM2EventsLib.IRendererEvent rendererEvent =
(DOM2EventsLib.IRendererEvent)new DOM2EventsLib.RendererEvent();
rendererEvent.initRendererEvent(type, false, false, arg1, arg2, arg3);
DOM2EventsLib.IEventTarget eventTarget = (DOM2EventsLib.IEventTarget)this.renderer;
eventTarget.dispatchEvent((DOM2EventsLib.DOMEvent)rendererEvent);
}
It is not necessary to worry about what all of the different parameters mean at this point. Simply know that the code above allows you to dispatch a renderer event of the specified type
, along with some event-specific contextual parameters.
Initialising your Renderer Instance
The first thing that you will want to do with this new method, then, is to inform this.renderer
of its initial size and position:
private void createRenderer() {
this.renderer = (RenderLib.IRender)new RenderLib.RendererClass();
this.renderer.Initialise(null, (int)this.panelRenderer.Handle);
this.dispatchEvent("renderer-set-dimensions",
this.panelRenderer.ClientRectangle.Width,
this.panelRenderer.ClientRectangle.Height, "");
this.dispatchEvent("renderer-set-position", 0, 0, ""); }
You also need to ensure that this.renderer
gets notified of any resizing that occurs in the parent window. This requires you to add a handler for the Resize
event:
private void panelRenderer_Resize(object sender, EventArgs evt) {
this.dispatchEvent("renderer-set-dimensions",
this.panelRenderer.ClientRectangle.Width,
this.panelRenderer.ClientRectangle.Height, ""); }
Once these initial window-related properties have been set, you must instruct this.renderer
to take active ownership of its Panel
:
private void createRenderer() {
this.renderer = (RenderLib.IRender)new RenderLib.RendererClass();
this.renderer.Initialise(null, (int)this.panelRenderer.Handle);
this.dispatchEvent("renderer-set-dimensions",
this.panelRenderer.ClientRectangle.Width,
this.panelRenderer.ClientRectangle.Height, "");
this.dispatchEvent("renderer-set-position", 0, 0, "");
this.dispatchEvent("renderer-activate", 0, 0, "");
}
Rendering Documents
Your renderer instance is now initialised and ready to display your documents. This, in itself, is a two stage process. First, the document must be loaded in one of two ways:
- By passing a string representation of the markup to
LoadFromString()
- By passing a Microsoft
IXMLDOMDocument
interface pointer to LoadFromDOM()
Regardless of the method that you use, the document must be well-formed, otherwise the load will fail; malformed documents may only be rendered using the supplementary RenderUnmodified()
and NavigateUnmodified()
methods. After the document has been loaded successfully, it can be rendered by calling the Render()
method. You may also pass a fragment identifier to this method, if necessary.
So, based on the above, a set of methods along the following lines would enable a browser-style application to display a document entered by the user:
private void loadDocument() {
XmlDocument document = new XmlDocument();
Uri uri = this.getUri();
try
{
document.Load(uri.AbsoluteUri);
this.renderer.LoadFromString(document.OuterXml,
this.getBase(uri));
this.renderer.Render(this.getFragmentId());
}
catch (XmlException exception)
{
MessageBox.Show(this, "Failed to parse " + exception.SourceUri
+ "\n\nReason: " + exception.Message + "\nLine: " +
exception.LineNumber + "\nColumn: " + exception.LinePosition +
"\nSource: " + exception.Source, "RenHost XML parse error");
}
catch (COMException exception)
{
MessageBox.Show(this, "The renderer component encountered an error\n" +
exception.StackTrace, "RenHost error");
}
}
private Uri getUri()
{
if (this.getFragmentIndex() != -1)
return new Uri(this.textboxUrl.Text.Substring(0,
this.getFragmentIndex()));
return new Uri(this.textboxUrl.Text); }
private String getBase(Uri uri)
{
return uri.AbsoluteUri.Substring(0, uri.AbsoluteUri.LastIndexOf('/') + 1); }
private String getFragmentId()
{
if (this.getFragmentIndex() != -1)
return this.textboxUrl.Text.Substring(this.getFragmentIndex() + 1);
return "";
}
private int getFragmentIndex()
{
return this.textboxUrl.Text.IndexOf('#');
}
Clearing Up
You can continue to load and render as many documents after this as you wish. All that remains is to ensure that you call the Destroy()
method before your application terminates:
protected override void Dispose(bool disposing) {
this.destroyRenderer();
if (disposing && this.components != null)
{
this.components.Dispose();
}
base.Dispose(disposing);
}
private void destroyRenderer()
{
this.renderer.Destroy();
this.renderer = null;
}
Links
History
- 09-Oct-2007: Created
- 13-Nov-2007: Added separate download information for the
Renderer
component, pending resolution of an issue in the formsPlayer installer - 05-Dec-2007: Switched license to CDDL, and made some minor cosmetic changes
- 11-Feb-2009: Updated code and download details to use latest versions of formsPlayer and UBX