|

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 formsPlayer, a free 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 formsPlayer, which is freely available, and a reasonably small download. Additionally, pending the resolution of a minor issue in the formsPlayer installer, you'll also need to separately install the Renderer component. Once both of these have been installed, you must import two formsPlayer COM libraries into your C# project via the 'Add Reference' dialog:
- [Program Files]\Common Files\Sidewinder\Renderer\Renderer4.dll
- [Program Files]\Common Files\Sidewinder\DOM\DOM2Events.dll
The first of these libraries, the Renderer, provides all of the XHTML and XForms rendering functionality. The second component, an implementation of 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 article, this class is called RenHost. The initialisation method for this class needs to instantiate and initialise a copy of the Renderer, storing a pointer to its IRender interface:
public class RenHost : System.Windows.Forms.Form
{
private RendererLib.IRender Renderer;
...
private void RenHost_Load(object sender, System.EventArgs e)
{
this.Renderer = (IRender)new RendererLib.RendererClass();
this.Renderer.Initialise(null);
...
}
...
}
Dispatching events
Your application may now customise various characteristics of the Renderer instance, such as setting the handle to its parent window, setting its initial size, or instructing it to propagate any navigation requests out to your application. These properties and many others are all set by dispatching events to the Renderer's IEventTarget interface.
To simplify this process, add a new method to your class along the following lines:
private void DispatchRendererEvent(string sType, int nParam1, int nParam2)
{
IRendererEvent evt = (IRendererEvent)new RendererEventClass();
evt.initRendererEvent(sType, false, false, nParam1, nParam2, "");
IEventTarget tgt = (IEventTarget)this.Renderer;
tgt.dispatchEvent((DOMEvent)evt);
}
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 type sType, 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 make the Renderer a child of the Panel associated with your RenHost class. This is achieved by dispatching a couple of events during initialisation:
private void RenHost_Load(object sender, System.EventArgs e)
{
this.Renderer = (IRender)new RendererLib.RendererClass();
this.Renderer.Initialise(null);
DispatchRendererEvent("renderer-set-parentwindow",
(int)this.PanelRenderer.Handle, 0);
DispatchRendererEvent("renderer-set-windowstyle", 2, 0);
...
}
The Renderer should also be informed of its initial size and position within its parent, like this:
private void RenHost_Load(object sender, System.EventArgs e)
{
this.Renderer = (IRender)new RendererLib.RendererClass();
this.Renderer.Initialise(null);
DispatchRendererEvent("renderer-set-parentwindow",
(int)this.PanelRenderer.Handle, 0);
DispatchRendererEvent("renderer-set-windowstyle", 2, 0);
DispatchRendererEvent("renderer-set-position", 0, 0);
DispatchRendererEvent("renderer-set-dimensions",
this.PanelRenderer.ClientRectangle.Width,
this.PanelRenderer.ClientRectangle.Height);
...
}
Once these initial window-related properties have been set, you must instruct the Renderer to perform its own window creation:
private void RenHost_Load(object sender, System.EventArgs e)
{
this.Renderer = (IRender)new RendererLib.RendererClass();
this.Renderer.Initialise(null);
DispatchRendererEvent("renderer-set-parentwindow",
(int)this.PanelRenderer.Handle, 0);
DispatchRendererEvent("renderer-set-windowstyle", 2, 0);
DispatchRendererEvent("renderer-set-position", 0, 0);
DispatchRendererEvent("renderer-set-dimensions",
this.PanelRenderer.ClientRectangle.Width,
this.PanelRenderer.ClientRectangle.Height);
this.Renderer.InitialiseWindow();
...
}
Rendering documents
The Renderer instance is now initialised and ready to render your documents. This, in itself, is a two stage process. First, the document must be loaded in one of three ways:
- By specifying the location of the document (in RFC 3986 absolute URI format) to
LoadURI();
- By passing a string representation of the markup to
LoadXML();
- By passing a Microsoft
IXMLDOMDocument interface pointer to LoadDOM().
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 IRenderHTML interface. After the document has been loaded successfully, it can be rendered by the Render() method. You may also pass a fragment identifier and a base URI to this method, if necessary.
So, based on the above, if your application were to contain a browser-style address bar, the Button.Click handler might look something like the following:
private void Button_Click(object sender, System.EventArgs e)
{
int nFragmentIndex = this.Input.Text.IndexOf('#');
Uri uri;
String sFragId;
if(nFragmentIndex != -1)
{
uri = new Uri(this.Input.Text.Substring(0, nFragmentIndex));
sFragId = this.Input.Text.Substring(nFragmentIndex + 1);
}
else
{
uri = new Uri(this.Input.Text);
sFragId = "";
}
String sURL = uri.AbsoluteUri;
String sBase = sURL.Substring(0, sURL.LastIndexOf('/') + 1);
this.Renderer.LoadURI(sURL);
this.Renderer.Render(sFragId, sBase);
}
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)
{
if(disposing)
{
this.Renderer.Destroy();
...
}
base.Dispose(disposing);
}
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.
| You must Sign In to use this message board. |
|
| | Msgs 1 to 22 of 22 (Total in Forum: 22) (Refresh) | FirstPrevNext |
|
|
 |
|
|
I am playing around with the renderer and found that I need some events be catched at the host app. Is thre any sample code to illustrate event-based communicaction between the doc level and the host app? TIA.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi Abel,
I've not had time to write this next bit up as a proper tutorial, but hopefully the following code snippets and so on should help you get it working. If you want more information about the different event types and their arguments, I recommend inspecting DOM2Events.dll with the MS OLE/COM Object Viewer.
To fire events out of the rendered document to the host application, some script along the following lines should do the trick:
// Dispatch a simple event with no arguments. function dispatchDOMEvent(type) { var evt = new ActiveXObject("DOM2Events.DOMEvent"); evt.initEvent(type, true, false); document.EventTarget.dispatchEvent(evt);
// Returning an empty string allows the function // to be used on the rhs from xf:setvalue. return ""; }
// Dispatch an event with up to three arguments: number; number; string. function dispatchRendererEvent(type, arg1, arg2, arg3) { var evt = new ActiveXObject("Renderer.RendererEvent"); evt.initEvent(type, true, false, arg1, arg2, arg3); document.EventTarget.dispatchEvent(evt);
// Returning an empty string allows the function // to be used on the rhs from xf:setvalue. return ""; }
Then call these methods as and when you need to dispatch events up to your application. If you want to trigger this from one of formsPlayer's XForms controls, you first need to define the following namespace on the html element:
xmlns:js="http://www.formsplayer.com/inlineScript"
Then you might invoke the event dispatch like this:
<xf:trigger> <xf:label> Dispatch </xf:label>
<xf:setvalue ref="/foo/bar" value="js:dispatchRendererEvent('my-event', 6, 9, '42')" /> </xf:trigger>
Obviously, if you just want to dispatch an event from vanilla HTML+JavaScript, it is not necessary to jump through this hoop.
To handle the event in your application, you'll need some code resembling the below (assuming your app is written in C#):
public void handleEvent(DOMEvent evt) { switch(evt.type) { case "my-event": // TODO: Insert your event handling code here. break; ... } }
Lastly, you need to ensure that you register as a listener for the events that you are interested in, otherwise your handleEvent() will not get called. Additionally, to ensure sane destruction of the renderer and formsPlayer, you need to unregister as a listener for those events before you call Destroy():
private void init(void) { ...
IEventTarget tgt = (IEventTarget)this.m_Renderer; tgt.addEventListener("my-event", (EventListener)this, 0); }
private void destroy(void) { IEventTarget tgt = (IEventTarget)this.m_Renderer; tgt.removeEventListener("my-event", (EventListener)this, 0);
...
this.m_Renderer.Destroy();
... }
That should be everything you need to know. The code above is all written from memory and has not been tested, so there may be mistakes. Let me know if you have any problems, they should be straightforward to resolve.
Cheers, Phil.
modified on Wednesday, April 16, 2008 5:19 AM
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Thank you very much, Phil, for your help. I could successfully run an event across the doc limits into the host app using the dispatchDOMEvent() javascript function, that uses ActiveXObject("DOM2Events.DOMEvent"). But failed to use the several args alternative. It reports 'Automation server cannot create the object' when executing the following line: var evt = new ActiveXObject("Renderer.RendererEvent"); What's wrong? On the other side, what should be the handler code (on the app side) to get the 3 args sent by the javascript? TIA.
abel
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi Abel,
I'd normally guess that the error means DOM2Events.dll is not registered, but that is clearly not the case here since everything else is working okay. I'll see if I can reproduce the problem and debug what's going wrong, however I probably won't have time to do this until the weekend.
To extract the arguments from the renderer event object in your C# app, you might use some code that looks like this:
public void handleEvent(DOMEvent evt) { switch(evt.type) { case "my-event": IRendererEvent re = (IRendererEvent)evt; OnMyEvent(re.numberParameterOne, re.numberParameterTwo, re.stringParameter); break; ... } }
I definitely recommend inspecting DOM2Events.dll with the MS OLE/COM Object Viewer if you want to know more about all of the objects, interfaces, properties and methods that are available.
I'll post back here when I've had a chance to look at the automation error.
Cheers, Phil.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Thank you, Phil, for your valuable help. The code that I got working is the following:
1.- On the doc side:
// Dispatch an event with up to three arguments: number; number; string. function dispatchRendererEvent(type, integerArg1, integerArg2, stringArg3) { var evt = new ActiveXObject("DOM2Events.RendererEvent"); evt.initRendererEvent(type, true, false, integerArg1, integerArg2, stringArg3); document.EventTarget.dispatchEvent(evt);
// Returning an empty string allows the function // to be used on the rhs from xf:setvalue. return ""; }
2.- On the app side:
public void handleEvent(DOMEvent evt) { switch(evt.type) { case "my-event": int arg1, arg2; string arg3; IRendererEvent re = (IRendererEvent)evt; re.longParams(out arg1, out arg2); arg3 = re.strParam; OnMyEvent(arg1, arg2, arg3); break; ... } }
So this subject is finished this way. Two other problems arised in the meantime when playing around with the project downloaded from the article:
1.- The code that reads the XML document (xHTML actually) is as follows:
XmlDocument doc = new XmlDocument(); doc.Load(sURL); this.Renderer.LoadXML(doc.OuterXml);
If the document is not well-formed an exception is thown by LoadXML, but I could not catch that exception by no try-catch combination. Is there something special with that exception?
2.- When exiting the app a horrible .NET-BrodcastEventWindow Application Error window appears reporting an invalid memory access, but only if a document is rendered, if not the app closes gracefully. Is this something related to the renderer? Is this happenning to somebody else?
Thanks again for your help.
abel
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi Abel,
Any exceptions that are emitted from renderer methods have been converted from COM HRESULT error codes by the .NET framework. I believe this means that they must be caught as type COMException[^], however I'm not a C#/.NET guru so that is worth looking into properly.
The crash on closing your application is an issue that I believe has been fixed for a formsPlayer customer. I'll update the installer for the Renderer component later on today and you can see if it cures your problem.
Cheers, Phil.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Okay, I have built a new installer for the renderer, but I can not see how to update the link to the installer in the article. Instead, I've posted it on sendspace for now. If you give it a try and let me know how it goes, I'll look into getting the article updated if you find it works. Here is the link:
<link deleted>
Cheers, Phil.
modified on Monday, April 21, 2008 4:44 AM
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
Hi Abel,
Sorry about that. In that case, I recommend reverting to the previous renderer for now (remove the current one via Control Panel > Add/Remove Programs first, then get the previous version from the link in the article). Then I'll work through the article on a clean machine tomorrow and post back here with whatever is necessary to get it working.
Cheers, Phil.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi Abel,
I've now run through all of the steps in the article, without reproducing the exception that you experience on closing your app. So I'm wondering whether the cause of the bug could be that the Renderer has not been destroyed sanely in your case. Before your application termninates, please ensure that it does each of the following actions, in the order that they are listed:
- Call removeEventListener() on the Renderer object appropriately to match each call that has previously been made to addEventListener() [i.e. with the same values for all three parameters];
- Call Destroy() on the Renderer object;
- Call Dispose() on the Panel object that was set as the Renderer's parent with the
renderer-set-parentwindow event.
Do these fix the problem for you?
Cheers, Phil.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Phil, I'm back with the crach problem. The crash does not happen when the app opens and closes without loading documents in the renderer. But when rendering a document the app crashes after exiting Main(), that is during the app cleanup. How can I find out what's happening (and solve it) since it doesn't crash on user code? Any help is greatly appreciated. TIA
abel
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi Abel,
All my time is being taken up by another project for my employer at the moment, so it is hard for me to look into this. When I get a chance, I could build some debug DLLs for you so that you can get a stack trace when the crash occurs. However, this is unlikely to be until some time next week, sorry. Can you just quickly confirm that you checked the three points from my previous message, since they were enough to have the sample working for me:
- Call removeEventListener() on the Renderer object appropriately to match each call that has previously been made to addEventListener() [i.e. with the same values for all three parameters];
- Call Destroy() on the Renderer object;
- Call Dispose() on the Panel object that was set as the Renderer's parent with the renderer-set-parentwindow event.
Cheers, Phil.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
1.- This the relevant code:
protected override void Dispose(bool disposing) { if (disposing) { IEventTarget tgt = (IEventTarget)this.Renderer; tgt.removeEventListener("xforms-cancel", (EventListener)eventListener, 0); tgt.removeEventListener("xforms-send", (EventListener)eventListener, 0);
this.Renderer.Destroy();
this.ButtonSelect.Dispose(); this.LabelPath.Dispose(); this.InputPath.Dispose(); this.ButtonRender.Dispose(); this.PanelRenderer.Dispose();
if (Components != null) { Components.Dispose(); } }
base.Dispose(disposing); }
private void ivNet_Load(object sender, System.EventArgs e) { this.Renderer = (IRender)new RendererLib.RendererClass(); this.Renderer.Initialise(null);
this.eventListener = new customEventListener(this.Renderer); IEventTarget tgt = (IEventTarget)this.Renderer; tgt.addEventListener("xforms-cancel", (EventListener)this.eventListener, 0); tgt.addEventListener("xforms-send", (EventListener)this.eventListener, 0);
DispatchRendererEvent("renderer-set-parentwindow", (int)this.PanelRenderer.Handle, 0, ""); DispatchRendererEvent("renderer-set-windowstyle", 2, 0, "");
DispatchRendererEvent("renderer-set-restrict", 0, 0, "");
DispatchRendererEvent("renderer-set-position", 0, 0, ""); DispatchRendererEvent("renderer-set-dimensions", this.PanelRenderer.ClientRectangle.Width, this.PanelRenderer.ClientRectangle.Height, "");
this.Renderer.InitialiseWindow();
if (this.Controls["InputPath"].Visible == false) { this.Controls["InputPath"].Text = Application.StartupPath + "\\iVirtual.xhtml"; this.ButtonRender_Click(null, null); }
}
2.- See Dispose above. I'm calling Destroy after removing the event listeners.
3.- See Dispose above. I'm disposing the parent panel after calling Destroy.
It seems to me that everything is ok here. The point to analyze is that when no document is loaded the crash doesn't happen but when a document is loaded it crashes on exit. The disposing/destrying sequence is the same in any case. The difference between both situation is the inner state of the renderer (and/or the IE in which it lives). I also tried simpler documents and the crash doesn't happen, but I couldn't isolate the offending xforms code. That's why I suspect that the problem may be in the renderer an not in the code outside. But this has to be proved and I don't know how to debug it to prove or discard the point. Take your time, I can wait some days until you can take a look at this problem. TIA.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
 |
|
|
I don't have any file with this name in my project and it builds okay. However, I'm on a pretty old version of Visual Studio (2002), so maybe that is why. When I have a moment, I'll take a look at building it in the Express Edition of Visual Studio 2008 then get back to you.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
 |
|
|
 |
|
|
Ah, sorry about that nji78, I'll try and figure out what's going wrong for you. Firstly though, can you tell me what version number is on the following DLL (right click, Properties > Version)?
[Program Files]\Common Files\Sidewinder\Renderer\Renderer4.dll
Thanks, Phil.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hello, doesn't work for me either. My renderer4.dll version is 4.1.3.1005. Installed the latest version of formsplayer community edition.
Ben
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi Ben,
Thanks for the info and sorry for the delay in responding. I think the problem is that the formsPlayer installer is missing a couple of files that are required by the Renderer component. Since these files were already on my machine, I hadn't noticed the problem. I'll pass it on to the developer who works on formsPlayer, but in the meantime I'm going to update the article so that there is an extra installation step to work round the issue.
Thanks again, Phil.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
General News Question Answer Joke Rant Admin
|