首先说些题外话,很久没有写博客了,空间里面的大部分文章还是11年写的。那时候刚毕业就来到这家公司,参与到一个Asp.net MVC的项目开发中,这个项目是一个全新的项目,连项目开发框架都没有,亏得领导的信任,让我研究一个MVC开发框架。那时候的我就像打了鸡血一样斗志高昂,努力奋斗了一个月后终于搭建了一个比较粗糙的Asp.net MVC+JQuery+EF4.0+Oracle的开发框架,不得不说MVC和Jquery Ajax简直就是天生一对。空间中的文章大部分都是这个时候写的。
随后的两年,我投入到基于WPF的逻辑图项目的研发中,学习到了很多东西,业务上的就不说了;技术上包括:如何利用WPF的图形渲染优势开发一套图形引擎、通用的撤销回做组件设计、图形布局算法(花了大量的时间)、鼠标工具、图形拓扑分析联动等。由于技术保密原因,这些东西都不能写成博客公开,因此这两年我一篇博客都没写。但是最近需要开发一个WPF浏览器应用程序与外部Flex程序的交互功能,查找了很多资料都没有找到解决办法,一般都是通过WebBrowser来中转实现,这局限性不能满足要求,所以最终还是采取了JS直接与WPF函数互调用的方式来实现。
言归正传,以下是这个问题的解决方案,希望各位不吝指教。
WPF浏览器应用程序(xbap)实际并不是一个标准的Web应用程序,它只不是由IE中承载的PresentationHost.exe充当宿主来解析执行,原理与ActiveX类似。JS与ActiveX交互的资料网上很多,把WPF浏览器应用程序封装成ActiveX然后开发与JS的交互函数,这也可以作为我们的一种解决方案。但是我觉得这种办法太复杂了,所以没有采用。
“应用程序承载到 HTML 框架中后,您可以与包含 XBAP 的网页通信。可以通过检索 BrowserInteropHelper 的 HostScript 属性来完成此操作。此属性会返回一个代表该 HTML 窗口的脚本对象。然后,您可以使用常规的点语法访问 window object(window 对象)的属性、方法和事件。您还可以访问脚本方法和全局变量”
上面是msdn上的一段说明,参照上面的做法:我创建了一个html页面,然后在页面中添加一个iframe,iframe的src属性指向目标xbap文件。我们确实可以在WPF的页面后台代码中取到外面html的HostScript脚本对象,并且可以通过这个脚本对象获取html页面的控件属性甚至调用它的JS函数。但是这个对象是只读的,也就是说你别想通过这个对象给外面的Html添加一个函数、事件监听什么的。
我们的问题卡在这里了,我们已经实现了WPF调用html页面的JS函数,但是JS函数怎么样才能调用到WPF的函数呢?
其实思路很简单,我在外面的html页面的JS部分定义一个变量wpfObj,并且定义一个设置wpfObj值的函数——SetWpfObj(wpfobj);然后在wpf页面后台构造函数中通过HostScript调用SetWpfObj函数给wpfObj复制一个C#对象,由这个对象来负责调用WPF的函数;最后JS函数执行的时候通过wpfObj调用C#对象的函数便完成了JS调用WPF函数的过程。
通过这两种方式的结合,便完成了JS和WPF函数的互调用,而且简单易复用,当我需要与外部交互的时候,我给外部展示这个html;当我不需要这个接口的时候,我直接展示这个xbap即可。无图无真相,下面详细贴出代码设置和运行结果。
html代码如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <meta http-equiv="X-UA-Compatible" content="IE=8"/> <!--不加上这一句,IE9貌似没效果--> <script type="text/javascript"> // 调用WPF的JS函数 function JSInvokeWPF() { if (wpfObj == null) { alert("中间对象为空!"); } else { alert(wpfObj.MyMethod("JS调用WPF后台函数")); } } // 供WPF调用的函数 function WPFInvokeJS(parameter) { alert(parameter); } var wpfObj = null; function SetWpfObj(obj) { wpfObj = obj; } </script> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>交互测试</title> </head> <body> <span> <button title="JS调用WPF函数" style="height: 30px; 100%;" onclick=" JSInvokeWPF();return false; ">JS调用WPF函数</button> </span> <iframe id="wpf" src="OPSYS.Web_Schematic.UI.xbap" Style=" 100%;height: 540px" ></iframe> </body> </html>
WPF后台文件代码如下:
using System.Runtime.InteropServices; using System.Windows; using System.Windows.Controls; using System.Windows.Interop; using MessageBox = System.Windows.MessageBox; namespace OPSYS.Web_Schematic.UI { /// <summary> /// ShellView.xaml 的交互逻辑 /// </summary> public partial class ShellView : Page { private dynamic scriptObject = null; public ShellView() { InitializeComponent(); // Retrieve the script object. The XBAP must be hosted in a frame or // the HostScript object will be null. if (!BrowserInteropHelper.IsBrowserHosted) { MessageBox.Show("不满足与JS调用条件"); return; } scriptObject = BrowserInteropHelper.HostScript; if(scriptObject!=null) { scriptObject.SetWpfObj(new CallbackClass()); } } private void JavaScriptInvoke_Click(object sender, RoutedEventArgs e) { if(scriptObject!=null) scriptObject.WPFInvokeJS("WPF调用JS函数"); } } //记得加上这个特性 [ComVisible(true)] public class CallbackClass { public string MyMethod(string message) { return "来自WPF的中转函数," + message; } } }
运行结果如下:
如果您发现VS老是报JS函数不存在,请设置如下内容(请运行在IE8模式):