如何在.NET应用程序中利用XHTML和XForms的强大功能






4.15/5 (9投票s)
XForms是W3C的一项重要推荐,它能够以简单、声明性的语法定义复杂的XML处理应用程序。本文将演示如何在您自己的应用程序中利用这一强大的功能。

引言
XForms是W3C的一项重要推荐,它能够以简单、声明性的语法定义复杂的XML处理应用程序。本文演示了如何在您自己的C#应用程序中利用这项技术。然而,同样的技术也可以轻松地应用于其他.NET语言,或者任何具有COM绑定的编程语言。所有这一切的核心XHTML和XForms渲染能力将由Ubiquity formsPlayer提供,它是一个免费的开源XForms处理器。
为什么选择XForms?
与其他解决方案相比,XForms的优势早已被更清晰地论证过,我在这里就不做过多赘述了。如果您想深入了解该主题,请参阅本文末尾的链接。然而,总结一些最显著的优点:
- XForms文档独立于设备和平台。
- XForms使用声明性语法,与传统的过程式方法相比,它有助于加快开发速度并减少出错的可能性。
- XForms根据模型-视图-控制器(MVC)设计模式分离数据、用户界面和应用程序逻辑。
- XForms是一个开放标准,这意味着相同的文档可以不经修改地部署到所有兼容的处理器上。
- XForms文档在设计上就具有固有的可访问性。
- XForms能够在输入点进行全面的输入验证,从而减少分布式Web应用程序中服务器往返的需求。
入门
在开始之前,您需要安装Ubiquity formsPlayer,这是一个免费且相对较小的下载。此外,您还需要单独安装Ubiquity Browser eXtensions来获取可嵌入的Renderer组件。一旦两者都已安装,您必须通过“添加引用”对话框将两个COM库导入到您的C#项目中。
- [Program Files]\Common Files\UBX\Renderer\Renderer5.dll
- [Program Files]\Common Files\UBX\DOM\DOM2Events.dll
第一个库Renderer为所有XHTML和XForms渲染功能提供了一个包装器。第二个组件是W3CDOM Level 2 Events推荐的一个实现,它促进了您的应用程序、Renderer和活动文档之间的通信。
实例化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);
}
此时不必担心所有不同参数的含义。只需知道上面的代码允许您分派指定type
的渲染器事件,并附带一些事件特定的上下文参数。
初始化您的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实例现在已初始化,并准备好显示您的文档。这本身是一个两阶段的过程。首先,必须通过以下两种方式之一加载文档:
- 通过将标记的字符串表示形式传递给
LoadFromString()
- 通过将Microsoft
IXMLDOMDocument
接口指针传递给LoadFromDOM()
无论您使用哪种方法,文档都必须格式正确,否则加载将失败;格式错误的文档只能使用附加的RenderUnmodified()
和NavigateUnmodified()
方法进行渲染。成功加载文档后,可以通过调用Render()
方法进行渲染。如果需要,您也可以将片段标识符传递给此方法。
因此,基于上述信息,一套类似下面的方法将能够使一个浏览器风格的应用程序显示用户输入的文档:
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('#');
}
清理
之后,您可以继续加载和渲染任意数量的文档。唯一剩下的就是确保在应用程序终止之前调用Destroy()
方法。
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;
}
链接
历史
- 2007年10月9日:创建
- 2007年11月13日:由于formsPlayer安装程序中的一个问题尚未解决,因此添加了
Renderer
组件的单独下载信息。 - 2007年12月5日:许可证切换为CDDL,并进行了一些小的外观更改。
- 2009年2月11日:更新了代码和下载详情,以使用最新版本的formsPlayer和UBX。