Fork me on GitHub
听雨轩
生命易破碎,梦想只争朝夕!

首先说些题外话,很久没有写博客了,空间里面的大部分文章还是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 的网页通信。可以通过检索 BrowserInteropHelperHostScript 属性来完成此操作。此属性会返回一个代表该 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; width: 100%;" onclick=" JSInvokeWPF();return false; ">JS调用WPF函数</button>
        </span>
        <iframe id="wpf" src="OPSYS.Web_Schematic.UI.xbap" Style="width: 100%;height: 540px" ></iframe>
    </body>
</html>
View Code

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;
        }
    }
}
View Code

运行结果如下:

如果您发现VS老是报JS函数不存在,请设置如下内容(请运行在IE8模式):

 

 

 

 

 

 

 

 

 

posted on 2014-01-17 11:50  流水殇  阅读(10416)  评论(5编辑  收藏  举报