Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

How to Harness the Power of XHTML and XForms in your .NET Applications

4.15/5 (9 votes)
11 Feb 2009CDDL4 min read 2   258  
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 leverage this power in your own applications.
Screenshot

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:

C#
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:

C#
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:

C#
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:

C#
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:

C#
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:

C#
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:

C#
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:

C#
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

License

This article, along with any associated source code and files, is licensed under The Common Development and Distribution License (CDDL)