翻译了一篇在C#中使用ADOBE SVG VIEWER的文章

 
ASV封装
I.              介绍
II.              ASV3Sharp 的实现
      1. System.Type
      2. 调用方法
      3. 取得属性值
      4. 设置属性值
      5. ASVObject
      6. ASV3Sharp
III.              安装
      1. DLL动态库
      2. 在窗口工具箱中添加ASV控件
      3. ActiveX封装动态库
IV.              使用
      1. 调用方法和设置属性
      2. 事件处理器
      3. 从C#调用ECMAScript函数
      4. 从ECMAScript 调用C#方法
V.              已知问题
VI.              修改记录
      1. 版本 1.2
      2. 版本1.1
      3. 版本1.0
VII.              下载
      1. 库文件
      2. 源代码
      3. 演示程序
介绍
Adobe 已经以ActiveX组件的方式实现了其SVG查看器. 这就给Windows开发人员提供了在自己的应用程序中内置SVG查看器的良机. 本文介绍如何在C#应用程序中使用SVG控件.
不幸的是, 最新的Adobe查看器(aka. ASV), 3.0版本, 仅仅提供了SVGWindow接口. 如果需要操作实际的SVG DOM本身, 你需要使用C#的反射功能. Stefan Goessner在其站点上对此有详尽的介绍,并提供了代码. 顺便提一下这种方法能够兼容最终的3.0版本. 实际上本文介绍的库也将使用这种方法. 同时, Jim Longson实现了一个令人印象深刻的例子(需要Yahoo SVG开发人员组的成员身份下载), 该示例对我有很大帮助. 关于内置SVG控件的更多资料可以在SVG Wiki中找到.
我曾经编写了相当多与ASV相关的ECMAScript代码, 并习惯使用ECMAScript提供的SVG DOM API. 因此对C#中缺省不提供这些API感到很失望. 幸运的是, 之前我在一个IDL解释器上花费过一些时间, 尽管这个解释器还没有完善, 但已经足够用于生成有用的SVG库. 通过该解释器, 我生成了一系列封装类, 可以使C#开发人员在SVG控件中使用SVG DOM API. 本文接下来讨论这些类是如何实现的, 以及在自己的C#项目中如何使用这些类.
声明: 我刚刚开始使用C#和.NET开发. 因此对该库很可能有更好的实现方法. 如果你发现了某些错误, 或者有些建议, 请马上与我联系以便对代码进行改进.

ASV3Sharp 的实现
本章讨论ASV3Sharp库的底层,读者会对如何实现SVG DOM API调用有一个更好的了解. 这些知识对如何绕过ASV3Sharp调用很有帮助.
根据Adobe最新的支持文档中描述的, ASV控件提供了SVGWindow接口. 该接口对控件中显示的SVG文档提供了足够的控制方法. 同时, 你可以使用postURL, getURL等方法. svgDocument是DOM开发人员使用最多的接口之一, 但是, 如前所述, 该接口并没有提供给开发人员. 幸运的是, 反射机制可以让我们调用其方法, 前提是我们必须知道方法名称以及需要提供给方法的参数.
System.Type
如果仔细阅读了Visual Studio .NET文档, 会发现一个叫做System.Type的类. 通过该类可以在运行时调用方法, 取得或设置属性. (注意 通过该类可以完成更多的工作, 不过这里仅仅介绍我们感兴趣的部分) 以字符串的形式提供方法和属性名, 并提供所需的参数, 这种机制被称为反射. 我们仅仅使用一个叫做InvokeMember方法, 就能够完成调用方法, 取得或设置属性的操作.  通过一个BindingFlags类型的参数能够指定我们想要执行的哪一种操作.
调用方法
现在来看一下调用方法的例子. 我们假设SVG控件已经被创建, 初始化, 分配给ASV3Control并显示了一个SVG文档.
AxSVGACTIVEXLib.AxSVGCtl svgWindow = ASV3Control.getWindow();
object svgDocument = svgWindow.getDocument();
Type   type        = svgDocument.GetType();
 
object svgRoot = type.InvokeMember(
    "getRootElement",
    BindingFlags.InvokeMethod,
    null,
    svgDocument,
    null);
第一我们注意到对通过该控件取得的所有对象都使用最一般的类型, object. 第二我们要取得与对象相关的Type对象, 在本例中为SVGDocument. 通过Type对象中的InvokeMember方法可以调用控制对象中的方法, 或取得/设置控制对象中的属性. InvokeMember参数中第一个为控制对象的方法或属性名称, 第二个参数决定对控制对象进行何种操作. 对我们而言第三个参数永远设为null. 第四个参数为控制对象, 最后是一个对象数组, 以传递需要的参数.
本例中调用svgDocument对象的getRootElement()方法, 并返回当前控件显示的SVG文档中最上层的SVG元素.
取得属性值
下一个例子演示如何通过访问属性取得SVG根节点元素.
AxSVGACTIVEXLib.AxSVGCtl svgWindow = ASV3Control.getWindow();
object svgDocument = svgWindow.getDocument();
Type   type        = svgDocument.GetType();
 
object svgRoot = type.InvokeMember(
    "rootElement",
    BindingFlags.GetProperty,
    null,
    svgDocument,
    null);
几乎与上一个例子一模一样, 唯一的区别是BindingFlag类型改为GetProperty以便指定取得属性. 返回的还是当前控件显示的SVG文档中最上层的SVG元素.
设置属性值
下一个例子演示如何设置当前控件显示的SVG文档的currentScale属性.
type = svgRoot.GetType();
 
type.InvokeMember(
    "currentScale",
    BindingFlags.SetProperty,
    null,
    svgRoot,
    object[] {2});
这次我们使用BindingFlag的类型为SetProperty以指定设置属性. 最后一个参数说明了如何在调用方法或设置属性时传递参数. 该参数必须为对象数组, 因此即使只有一个参数也需要创建一个数组进行传递.
ASVObject
对InvokeMember的调用相当简单, 但对我来说在文字上太繁复了. 因此我创建了个新类ASVObject以简化方法调用以及属性取得或设置.下面是使用ASVObject类后改写的以上示例代码.
AxSVGACTIVEXLib.AxSVGCtl svgWindow = ASV3Control.getWindow();
ASVObject svgDocument = new ASVObject(svgWindow.getDocument());
ASVObject svgRoot     = new ASVObject(svgDocument.GetProperty("rootElement"));
 
svgRoot.SetProperty("currentScale", 2);
因为要将控件返回的对象全部封装为ASVObject, 因此编程人员需要增加一些开销. 但是, 一旦使用ASVObject, 代码变得更加简单明了. 你可以使用InvokeMethod(方法名, 参数1, 参数2, …)的方式调用方法, 可以通过GetProperty(属性名)以及SetProperty(属性名, 属性值)来取得或设置属性.
在调用的方法需要多个参数时, 可以简单的将参数列在方法名后, 下面是一个例子:
svgRoot.InvokeMethod(
    "insertBefore", someSVGNode, svgRoot.GetProperty("firstChild"));
ASVObject 将在调用Type对象的InvokeMember方法前把所有参数封装到一个对象数组中.
ASV3Sharp
既便ASVObject简化了代码, 但它并不能防止传递错误参数. 例如, 它无法阻止你给Application对象设置currentScale属性. 当然, 这个属性是无效的, ASV将(或者应该)产生错误, 但更好的办法是在编译时发现这些错误, 而不是在运行时. 这就是为什么要引入ASV3Sharp库.
我以C#接口和C#类两种方式重建了SVG IDL接口层次. 所有的C#类从ASVObject派生. IDL接口相关的所有的方法和属性被包括在C#类中. 请注意由于IDL接口的多重继承特点, 所以我在每个类中包含了所有父类的方法和属性. 这很可能并不是设计SVG类层次的最理想方式, 但由于我们仅仅需要封装ASV, 因此我认为这些重复不会带来任何问题.
现在看一下如何通过ASV3Sharp实现上面的缩放示例.
using KevLinDev.ASV3;
 
ASVWindow window = new ASVWindow( ASV3Control );
SVGDocument svgDocument = window.svgDocument;
SVGSVGElement svgRoot = svgDocument.rootElement;
 
svgRoot.currentScale = 2;
我们在一开始包含了ASV3名字空间, 以便缩短类型引用.在将控件封装在一个ASVWindow对象中后, 我们就可以象直接使用ASV API一样进行操作了.
最终, ASV3Sharp会调用Type对象的InvokeMember方法, 但对开发人员隐藏了其细节实现. 这就给开发人员可以直接使用SVG DOM API的错觉. 封装类可以引发编译器的在编译时的类型检查, 从而避免了潜在的运行时错误. 还有, 大多数的返回值被类型映射为有效的类型(与返回object类型形成鲜明对比).

安装
在开始使用ASV3Sharp库之前, 需要对Visual Studio项目进行一些设置.
动态库
首先, 需要在项目中包含ASV3Sharp.dll. 在项目解决方案资源管理器中右键引用, 从右键菜单中选择添加引用, 单击浏览.., 选择ASV3Sharp.dll, 最后单击完成. 现在应当在引用目录中看到ASV3Sharp.
在Windows Form 工具箱中添加ASV控件
如果从未在C#项目中使用过ASV控件, 则需要在Windows Forms工具箱中进行添加. 在设计模式下打开一个窗口, 在Windows Forms工具箱的任何地方单击右键, 选择 “选择项”, 在COM组件中选择SVG Document, 单击完成. 现在应在工具箱中看到一个名为SVG Document的新栏目. 如果在COM组件中没有找到SVG Document, 则需要安装Adobe SVG Viewer 3.0.
ActiveX 封装动态库
ASV3Sharp 库使用一系列ActiveX封装动态库, 以便C#与ASV3进行交互. Visual Studio会在向窗口添加ASV控件时自动生成这些封装动态库. 你可以在不需要时删除ASV控件, 封装动态库将保留在引用目录中.
对使用命令行创建C#项目的情况, 需要通过以下命令手工生成ASV ActiveX封装动态库.
aximp "C:/WINNT/system32/Adobe/SVG Viewer 3.0/NPSVG3.dll"
该命令在命令执行目录生成两个封装动态库, AxInterop.SVGACTIVEXLib.dll和Interop.SVGACTIVEXLib.dll. 确保在命令行生成你的项目时包含这两个动态库, 以及ASV3Sharp.dll.
好了, 我们现在可以开始使用ASV3Sharp动态库了.

使用
方法调用和属性
在前面章节中通过示例讨论过这些问题, 所以在此简单的提一下.如果一切顺利, 你可以通过SVG IDL(以及所有相关IDL)查找到某个方法调用或属性, 然后象使用其它C#方法和属性一样进行操作. 下面例子中创建ASVWindow对象, 然后定位到SVG根元素, 并更改其缩放属性值.
using KevLinDev.ASV3;
 
ASVWindow window = new ASVWindow( ASV3Control );
SVGDocument svgDocument = window.svgDocument;
SVGSVGElement svgRoot = svgDocument.rootElement;
 
svgRoot.currentScale = 2;
事件处理器
在我看来, 这才是有趣事情的开始. 这就是使用C#对象作为事件处理器是可行的. 例如, 下面的代码中先找到一个ID为"clickMe"的元素, 并为其设置一个事件处理器(该处理器为当前C#对象). 在clickMe元素上单击鼠标则会触发对象的handleEvent方法. 结果是给用户显示一个消息框.
using KevLinDev.ASV3;
 
public void setEventHandler() {
    ASVWindow window = new ASVWindow( ASV3Control );
    SVGDocument svgDocument = window.svgDocument;
    SVGRectElement rect = new SVGRectElement(
        svgDocument.getElementById("clickMe").Object);
 
    rect.addEventListener("mousedown", this, false);
}
 
public void handleEvent(object evt) {
    MouseEvent e = new MouseEvent(evt);
 
    MessageBox.Show( "clicked at (" + e.clientX + "," + e.clientY + ")" );
}
 
该例子也暴露了库的局限性和以及一个奇特的特点.首先注意rect变量的定义.可以看到在SVGRectElement 的构造函数中调用了getElementById(), 这里的风险在于, 根据IDL定义 getElementById()返回一个元素对象, 但addEventListener并没有在元素接口中定义. 我们只好假定返回的是一个矩形对象, 从而创建了一个新的SVGRectElement对象, 当然我们的假设很可能是错误的.
ASVObject 类中隐藏的细节是一个Object类型的属性. 该属性为ASV控制返回对象的引用. 所有的ASVObject派生类的构造函数都需要该对象, 这就是我们去取得它的原因.
很大程度上, 这里最有可能是我缺乏C#知识的一个表现. 最开始我创建了一个单独的构造函数,并将ASVObject作为一个参数. 不幸的是, 只有原始的构造函数被调用(使用object作为其参数), 我不知道如何解决这个问题. 此外, 我觉得用类型转换会比构造函数更好, 但想不出一个简便的方法, 去定义显式类型处理器, 而且能够避免所有可能的类型组合的组合爆炸. 还是那句话, 我当前的知识有限.
下一个问题是不可见的. 它与addEventListener的调用有关. 其中第二个参数期望一个实现了IEventListener C#接口的对象. 好了, 为了处理ASVObject隐藏的奥秘, 所有的接口从IASVObject接口. 所有的IASVObject派生类必须定义一个Object属性. 因为在addEventListener使用了当前对象(this), 我们必须保证,1) 当前对象包含并实现了IeventListener 2) 定义一个Object属性(因为IEventListener从IASVObject派生). 在这种情况下, Object属性简单的返回其自身. (接口中定义了属性, 这点比较奇怪 –译者)
最后一个奇怪的地方是handleEvent. 在理想情况下, handleEvent应当接收一个Event对象, 或者Event对象的子类. 然而ASV3Sharp事件对象是一个封装对象, 在ASV中发送给handleEvent一个真正的事件对象, 所以我们不得不通过该对象创建一个封装对象. 一旦得到了封装对象, 我们就可以使用标准的API调用了.(就象例子中的clientX 和 clientY)
提示: 使用C#对象作为事件处理器可以使用ASV脚本引擎或者Microsoft脚本引擎.
从C#调用ECMAScript 函数
从C#调用ECMAScript函数使用到ASVObject的助理方法. 在ECMAScript中的最顶层的方法实际上是SVGWindow对象的属性. 下面的代码调用了SVG文档中的sayHello()函数, 并将"Billy"字符串作为唯一的参数传递. 正象前面提到的, 在函数调用时需要多少参数就可以传递多少参数.
using KevLinDev.ASV3;
 
ASVWindow svgWindow = new ASVWindow( ASV3Control );
string result = (string) svgWindow.InvokeMethod("sayHello", "Billy");
MessageBox.Show("C#: result = " + result);
"result" 变量将保存ECMAScript sayHello()函数的返回值. 提醒一下, InvokeMethod使用了Type类的方法, 因此返回的是一个一般对象. 在例子中, 我们假设sayHello()函数返回一个字符串, 因此将返回值强制类型转换到一个字符串.
提示: 使用ASV脚本引擎无法在SVG文档中实现这个功能.
从ECMAScript调用C#函数
尽管还不完善, 但还是能够实现从ECMAScript调用C#方法. 在C#中一切都是对象, 如果希望调用一个具体的方法, 则需要得到一个对象的实例. 因此要ECMAScript能够访问到希望调用的对象, 必须在SVG window对象中设置一个属性指向该对象.
using KevLinDev.ASV3;
 
ASVWindow svgWindow = new ASVWindow( ASV3Control );
svgWindow.SetProperty("csDocument", this);
这就是要点. 为了实现这个功能, 需要在SVG文档ECMAScript代码中定义一个名为csDocument的属性. (在实际演示程序中名为csharp –译者). 同时,我没能发现在C#中创建新ECMAScript对象属性的方法.
为了调用C#对象方法, 例如sayHello(), 我们需要如下的ECMAScript代码
var csDocument;
 
function callCSharp() {
    if ( window.csDocument != null )
        csDocument.sayHello("Johnny");
}
我在此又一次遇到知识匮乏的问题. 我能够成功的从ECMAScript中调用C#中Form对象的方法, 然而我无法调用任何ASVObject派生对象的方法. 调用ASVObject派生对象的方法会得到 “自动化没有实现” 的错误, 如果能够解决这个问题, 则可以扩展ASV功能, 以达到ECMAScript中几乎透明的程度.
提示: 使用ASV脚本引擎无法在SVG文档中实现这个功能.
检查返回值是否为Null
由于所有的返回值都被封装在ASVObject中, 因此, 为了检查返回值是否为null, 必须检查其Object属性. 例如, 下面的代码对指定节点的所有子节点进行遍历, 在对子节点进行遍历之前, while语句将检查子节点是否为null.
node = parent.firstChild;
 
while ( node.Object != null ) {
    // do something with the current node
    node = node.nextSibling;
}

已知问题
  1. 无法在使用ASVObject构造对象时触发接受ASVObject的构造函数. 很明显,在使用基于ASVObject构造函数前就调用了使用Object为参数的构造函数.
  2. 我希望使用强制类型转换的手段完成从一般类型到特定类型的转换. 如果能够实现该转换, 同时能避免创建各种类型转换组合, 则问题1也就不存在了.
  3. 如果方法能够返回特定类型,而不是现在的一般类型就好了. 例如, getElementById()返回一个元素, 我希望它能够返回一个SVGRectElement或者其它与之相匹配的特定元素.
  4. 无法从C#中为ECMAScript对象中增加新的属性.
  5. 事件处理器必须创建自己的基于ASVObject的对象, 这点无法避免, 原因时ASV必须向事件处理器提供对象.
  6. 我无法从调用ECMAScript任何ASVObject派生对象的方法. 调用ASVObject派生对象的方法会得到 “自动化没有实现” 的错误. 对我来说奇怪的是:1) 可以调用Form的方法, 2)可以使用任何对象作为事件处理器. 从错误消息看来这是与COM相关的问题. 似乎需要启动些什么以完成这种类型的交互, 但看起来它已经启动, 因为我已经能够调用一些方法. 对此如果有什么建议将不胜感激.
  7. 我无法使用ASV脚本引擎实现从C#调用ECMAScript方法, 反之也不行. 但由于能够实现使用ASV脚本引擎通过事件处理器触发C#方法, 这个功能似乎应该可以实现.
我确信随着该库的使用会有更多的问题得到暴露. 因此如果你遇到什么问题请告知我, 我会将其加入上面的问题列表(当然更好的是解决问题). 正如上面所说, 我正在学习C#, 因此如果你发现什么非正规的地方, 或者有些什么建议, 请给我发邮件.

修改记录
版本 1.2
  1. SVG控件返回的对象全部封装为ASVObject. 当SVG控件返回null 时引发了ASVObject构造函数异常. 现在正在测试ASVObject 构造函数中对null的处理. 顺便说一下, 你必须检查返回对象的Object 属性是否为null, 例如if (svgNode.firstChild.Object == null) { ... }.
版本1.1
  1. IDL中定义的无符号返回类型无法强制类型转换回正确的无符号类型. 似乎所有IDL无符号类型在ASVSharp中都是有符号类型. COM应该支持无符号类型, 所以我认为问题可能出在SVG控件上, 但并不能确定.
版本1.0
  1. 发布动态库, 源代码和演示程序

下载
下载动态库(ASV3Sharp, AxInterop.SVGACTIVEXLib.dll 和 Interop.SVGACTIVEXLib.dll)
下载ASV3Sharp源代码
下载一个简单的浏览器. 支持XSLT并演示本文中提到的相关技术.
Copyright 2000-2003, Kevin Lindsey
 
 
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值