XForms是由W3C推荐的一种重要技术,它允许以简单、声明式的语法定义复杂的XML处理应用程序。本文将展示如何在C#应用程序中利用这项技术。这些技术同样可以轻松地应用于其他.NET语言,或者任何具有COM绑定的编程语言。XForms的核心渲染能力由Ubiquity formsPlayer提供,这是一个免费的开源XForms处理器。
XForms相对于其他解决方案的优势在其他地方已经得到了更详细的讨论,这里不再赘述。有兴趣深入了解的读者可以参考文章末尾的链接。简而言之,XForms的一些关键优势包括:
在开始之前,需要安装Ubiquity formsPlayer,这是一个免费且相对较小的下载。此外,还需要单独安装Ubiquity Browser eXtensions以获取可嵌入的Renderer组件。安装这两个组件后,必须通过'添加引用'对话框将两个COM库导入C#项目:
这两个库中的第一个,Renderer,为所有的XHTML和XForms渲染功能提供了一个包装器。第二个组件,W3C的DOM Level 2 Events建议的实现,促进了应用程序、Renderer和活动文档之间的通信。
首先,必须在应用程序中添加一个新的类,代表将托管Renderer的窗口。在示例代码中,这个类被称为RenHost。这个类的构造函数需要实例化一个RenderLib.RendererClass的副本,并存储一个指向实例的IRender接口的指针:
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();
}
...
}
接下来,必须使this.renderer成为RenHost类上的Panel的子元素。这是通过调用Initialise()并传递Panel的句柄作为参数来实现的:
private void createRenderer() {
this.renderer = (RenderLib.IRender) new RenderLib.RendererClass();
this.renderer.Initialise(null, (int)this.panelRenderer.Handle);
}
现在,应用程序可以自定义this.renderer的各种特性,例如设置其初始大小或指示它将任何导航请求委托给应用程序。这些属性和其他许多属性都是通过分派事件到this.renderer的IEventTarget接口来设置的。
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);
}
目前不必担心所有不同的参数意味着什么。只需知道上面的代码允许分派一个指定类型的renderer事件,以及一些事件特定的上下文参数。
首先,需要使用新方法通知this.renderer其初始大小和位置:
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.renderer得到通知父窗口发生的任何调整大小。这需要为Resize事件添加一个处理程序:
private void panelRenderer_Resize(object sender, EventArgs evt) {
this.dispatchEvent("renderer-set-dimensions", this.panelRenderer.ClientRectangle.Width, this.panelRenderer.ClientRectangle.Height, "");
}
一旦这些初始窗口相关的属性被设置,必须指示this.renderer积极拥有其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, "");
}
renderer实例现在已经初始化并准备好显示文档。这本身就是一个两阶段的过程。首先,文档必须以两种方式之一加载:
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");
}
}
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;
}