65.9K
CodeProject 正在变化。 阅读更多。
Home

VB.NET/C# 和 JavaScript 通信

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.82/5 (55投票s)

2009年4月11日

CPOL

12分钟阅读

viewsIcon

344203

VB.NET/C# 和 JavaScript 通信,怎么做。

目录

简介

本文是关于在 VB.NET/C# WinForms 和 JavaScript 之间传递数据。

在继续阅读之前,我必须警告你,本文不是关于 ASP.NET 的。 文章中涵盖的概念应用于桌面应用程序。

背景

我正在做一个项目,它需要在我开发的 VB.NET WinForms 应用程序和 JavaScript(位于 HTML 页面内)之间传递数据。在这个过程中,我遇到了一些问题,试图解决它们花费了我大量的时间在网上搜索(主要是 Google),所以我认为写这篇文章是为了帮助那些正在寻找解决类似问题的开发人员。网络上关于这个主题的详细信息很少,除了微软在 MSDN 上提供的几个“Hello World”示例。

起点

好了,废话不多说,我将深入探讨主题。

Hello World

首先,我们将从一个非常简单的例子开始;它只会从 VB.NET 调用一个 JavaScript 函数来显示一条带有“Hello world”消息的警报。同样,通过 JavaScript 从 HTML 页面,我们将调用一个 VB.NET 函数,它也会显示一条带有“Hello world”消息的消息框。为了实现这一切,你需要按照以下步骤操作:

从 VB.NET 调用 JavaScript

  • 在 Visual Studio 中,创建一个新的 WinForms 应用程序,随意命名。
  • 在你的 `form1`(主窗体)中添加一个导入语句,如 Imports System.Security.Permissions
  • 为 `form1` 添加一些属性,例如:
  • <PermissionSet(SecurityAction.Demand, Name:="FullTrust")> _
    <System.Runtime.InteropServices.ComVisibleAttribute(True)> _
    Public Class Form1
    End Class

    它们的作用是告诉 .NET Framework 我们想要完全信任,并使类对 COM 可见,这样 JavaScript 就可以看到这个类。

  • 向窗体添加一个 `WebBrowser` 控件,并将其 `url` 属性设置为 _c:\temp\mypage.html_(这只是一个示例路径,你应该设置为你将使用的 HTML 页面的路径)。
  • 在窗体上添加一个 `Button` 控件,并在其点击处理程序中编写以下代码:
  • Private Sub Button1_Click(ByVal sender As System.Object, _
                ByVal e As System.EventArgs) Handles Button1.Click
        Me.WebBrowser1.Document.InvokeScript("showJavascriptHelloWorld")
    End Sub
  • 在你的 HTML 页面中,添加以下代码:
  • <script type="text/javascript">
        function showJavascriptHelloWorld() {
            alert("Hello world");
        }
    </script>

从 JavaScript 调用 VB.NET

  • 继续上一个项目,在你的 `form1` 中添加以下代码行:
  • Public Sub showVbHelloWorld()
        MsgBox("Hello world")
    End Sub
  • 在你的 `form1` 的加载事件中,添加这一行代码:
  • Me.WebBrowser1.ObjectForScripting = Me

    这一行代码的作用是,它将你的 `form1` 类暴露给 HTML 页面上的 JavaScript。只要你使类对 COM 可见并将它的权限设置为 _fulltrust_,你就可以暴露任何类。

  • 在你的 HTML 页面中,添加一个 `Button`,并在其点击事件中调用此函数:
  • <script type="text/javascript">
        function showVbHelloWorld() {
            window.external.showVbHelloWorld();
        }
    </script>
  • 就这样。我们已经实现了双向通信。但是,大多数开发人员可能已经在 MSDN 或不同的论坛上看到过类似的示例。这只是一个起点,我们的下一步是向函数传递参数。

传递参数给函数

传递参数给 JavaScript 函数

要向 JavaScript 函数传递参数,你只需要像平常一样在 JavaScript 代码中声明函数即可。在你的 VB.NET 代码中,你可以传递单个参数,例如:

Me.WebBrowser1.Document.InvokeScript("javascriptFunction", _
   New String() {"Message from vb.net to javascript"})

如果你想传递多个变量,你可以简单地像这样添加它们:

Me.WebBrowser1.Document.InvokeScript("javascriptFunction", _
   New String() {"Message from vb.net to javascript", _
  "Argument 1","Argument 2" , "Argument n"})

到目前为止,我只传递字符串类型的参数,但你不限于简单的字符串。你可以轻松地传递字符串、整数、布尔值,而无需任何特殊的附加。例如:

Me.WebBrowser1.Document.InvokeScript("javascriptFunction", _
   New Object() {"Message from vb.net to javascript",200,true})

参数不限于简单类型,你可以传递任何你想要的类型。唯一的限制是你必须使其对 COM 可见。在这个例子中,我将传递一个 `Person` 对象给 JavaScript。为了使其工作,我将遵循以下步骤:

  • 创建一个名为 `Person` 的类,如下所示:
  • <PermissionSet(SecurityAction.Demand, Name:="FullTrust")> _
    <System.Runtime.InteropServices.ComVisibleAttribute(True)> _
    Public Class Person
        Public Sub New(ByVal firstName As String, _
               ByVal lastName As String, ByVal age As Integer)
            Me._firstName = firstName
            Me._lastName = lastName
            Me._age = age
        End Sub
    
        Private _firstName As String
        Private _lastName As String
        Private _age As Integer
    
        Public Function getFirstName() As String
            Return Me._firstName
        End Function
    
        Public Function getLastName() As String
            Return Me._lastName
        End Function
    
        Public Function getAge() As Integer
            Return Me._age
        End Function
    
    End Class
  • 在你的 `form1` 中,像这样调用它:
  • Me.WebBrowser1.Document.InvokeScript("sendPerson", _
       New Person() {New Person("John", "Smith", 37)})
  • 在你的 JavaScript 中,你可以像这样使用 `Person` 对象:
  • <script type="text/javascript">
        function sendPerson(person) {
            alert("First Name:" + person.getFirstName() + 
                  "Last Name:" + person.getLastName() + 
                  "Age:" + person.getAge());
        }
    </script>
  • 大多数开发人员应该都知道这一点,但我必须指出,当你将对象传递给 JavaScript 时,它是完全暴露的(所有公共方法/属性),JavaScript 可以修改你的对象。例如,如果我在我的 `Person` 类中添加这个函数:
  • Public Sub setFirstName(ByVal firstName as String)
        Me._firstName=firstName
    End Sub

    现在,在你的 JavaScript 中,修改 `sendPerson` 函数,如下所示:

    function sendPerson(person) {
        person.setFirstName('Roger');
    }

    在此调用之后,你的 person 的名字将是“Roger”,而不是“John”。为了避免这种情况,有两种选择。将 `setFristName` 设置为 private/protected,但这会意味着你无法从 VB.NET 访问此方法。更好的解决方案是为 `setFirstName` 添加一个属性,例如:

    <System.Runtime.InteropServices.ComVisibleAttribute(False)> _
    Public Sub setFirstName(ByVal firstName as String)
        Me._firstName=firstName
    End Sub

    现在,你的函数在你的 VB.NET 代码中可见,但 JavaScript 将无法调用它,因此如果你不想将特定方法暴露给 JavaScript,结果将是无法修改它。

向 JavaScript 函数传递数组(不成功的尝试)

好的,关于向 JavaScript 传递数组。有些开发人员可能会问为什么它是一个单独的部分。原因是传递数组并不像大多数人想象的那么直接。如果你尝试这段代码:

Dim firstNames() As String = {"John", "Richard", "Micheal"}
Dim lastNames() As String = {"Smith", "Stone", "Day"}
Me.WebBrowser1.Document.InvokeScript("passNameArrays", _
                        New Object() {firstNames, lastNames})

在你的 JavaScript 中,如果你尝试这样做:

<script type="text/javascript">
    function passNameArrays(firstNames, lastNames) {
        alert(firstNames[0]);
    }
</script>

你将收到一个 JavaScript 错误(如果启用了错误消息)。

那么,为什么它不起作用,你一定会问。原因很简单。你只能传递简单类型,而数组不是简单类型。你必须设置一个解决方法来传递数组。

向 JavaScript 函数传递数组(成功的尝试)

好的,可以理解的是,我们不能将数组传递给 JavaScript(至少,我不知道有任何方法可以传递它们;如果有人知道方法,欢迎他们纠正我)。最简单的解决方法是创建一个包装你的数组的类型,然后传递该类型而不是简单的数组。例如,你可以包装你的字符串数组,如下所示:

<PermissionSet(SecurityAction.Demand, Name:="FullTrust")> _
<System.Runtime.InteropServices.ComVisibleAttribute(True)> _
Public Class myArr
    Implements IList(Of String)

    Private _list As New List(Of String)

    Public Sub New()

    End Sub
    Public Sub New(ByVal arr() As String)
        For Each Str As String In arr
            Me._list.Add(Str)
        Next
    End Sub

    Public Sub Add(ByVal item As String) _
           Implements System.Collections.Generic.ICollection(Of String).Add
        Me._list.Add(item)
    End Sub

    Public Sub Clear() _
           Implements System.Collections.Generic.ICollection(Of String).Clear
        Me._list.Clear()
    End Sub

    Public Function Contains(ByVal item As String) As Boolean _
           Implements System.Collections.Generic.ICollection(Of String).Contains
        Return _list.Contains(item)
    End Function

    Public Sub CopyTo(ByVal array() As String, ByVal arrayIndex As Integer) _
           Implements System.Collections.Generic.ICollection(Of String).CopyTo

    End Sub

    Public ReadOnly Property Count() As Integer _
           Implements System.Collections.Generic.ICollection(Of String).Count
        Get
            Return Me._list.Count
        End Get
    End Property

       'Rest of the class method needs to be implemented here
End Class

现在,让我们回顾一下上一节中我们尝试传递名字和姓氏数组却失败的例子,让我们再试一次:

Dim firstNames As New myArr(New String() {"John", "Richard", "Micheal"})
Dim lastNames As New myArr(New String() {"Smith", "Stone", "Day"})
Me.WebBrowser1.Document.InvokeScript("passNameArrays", _
               New Object() {firstNames, lastNames})

在你的 JavaScript 中,你可以这样使用它:

<script type="text/javascript">

    function passNameArrays(firstNames, lastNames) {
        alert(firstNames.item(0));
    }
</script>

现在,你应该看到一个显示“John”的警报。所有其他函数,如 `count`、`indexOf` 等,都可以从 JavaScript 中访问。

我已经实现了该类为 IList(Of String),但你可以将其实现为 IList(Of Object) 并传递任何类型的数组(只要它们对 COM 可见)。

一个需要记住的重要事实

一个大多数开发人员应该知道的重要事实是,你并不一定需要传递固定数量的参数给 JavaScript 函数。你可以传递任意数量的参数给 JavaScript 函数,例如,我们最后一个 JavaScript 函数可以这样编写和使用:

<script type="text/javascript">
    function passNameArrays() {
        alert(arguments[0].item(0));
    }
</script>

从 JavaScript 向 VB.NET 函数传递参数

我们花了足够的时间来传递参数给 JavaScript。让我们进入下一步,向 VB.NET 函数传递参数。你可以在 JavaScript 中简单地这样做:

<script type="text/javascript">
    function sendPerson() {
        window.external.sendPerson("John", "Smith", 26);
    }
</script>

在你的 VB.NET 端,你可以定义一个函数来接收这些参数,如下所示:

Public Sub sendPerson(ByVal firstName As String, _
           ByVal lastName As String, ByVal age As Integer)
    MsgBox(String.Format("First name: {0} Last Name: {1} Age: {2}", _
                         firstName, lastName, age))
End Sub

这样你就完成了。

同样,你不限于传递简单类型作为参数。如果需要,你可以将 JavaScript 对象作为参数传递。看看最后一个例子,但以不同的方式。JavaScript 代码将是:

<script type="text/javascript">
    function sendPerson() {
        var person = new Array();
        person["firstName"] = "John";
        person["lastName"] = "Smith";
        person["age"] = 26;
        window.external.sendPerson(person );
    }
</script>

你可以在 VB.NET 端使用 `Person` 对象,如下所示:

Public Sub sendPerson(ByVal person As Object)
    MsgBox(String.Format("First name: {0} Last Name: {1} Age: {2}", _
           person.firstName, person.lastName, person.age))
End Sub

类似的例子,但实现相同结果的方法不同。

从函数返回值

从 JavaScript 函数返回一个值给 VB.NET

关于从函数返回值呢?很简单,你可以轻松地从 JavaScript 函数获取值;例如,如果我们 VB.NET 代码调用这行:

Dim str As String = _
    Me.WebBrowser1.Document.InvokeScript("getJavascriptString")
MsgBox(str)

而在 JavaScript 中,我们可以编写函数返回一个字符串,如下所示:

<script type="text/javascript">
    function getJavascriptString() {
        return "String returned from javascript";
    }
</script>

同样,你不限于简单类型。你可以非常轻松地返回自定义的 JavaScript 对象。例如,让我们这次在 JavaScript 中创建一个 `Person` 类,并编写一个函数来将 `Person` 对象返回给 VB.NET。

<script type="text/javascript">
    function person(name,age) {
        this.name = name;
        this.age = age;
        this.getName = function() {  return this.name; }
        this.getAge = function() { return this.age; }
    }
    
    function getPersonObject() {

        myPerson = new person("Chris McCreadie", 48);
        return myPerson;
    }
</script>

在你的 VB.NET 端,你可以这样做(_必须警告你,下面的代码不会工作_):

Dim jsPerson As Object = Me.WebBrowser1.Document.InvokeScript("getPersonObject")
MsgBox(String.Format("Name: {0} Age:{1}", jsPerson.getName(), jsPerson.getAge()))

如果你尝试上面的代码,第一行会正常运行,但在运行时(因为它是后期绑定),你会在第二行收到一个异常。如果你问为什么它不起作用,坦白说,我不知道,但如果你知道答案,欢迎你来解释。

那么,解决方案是什么?解决方案非常简单。你可以借助 `System.Reflection`,并修改上面的代码,使其如下所示:

Dim jsPerson As Object = Me.WebBrowser1.Document.InvokeScript("getPersonObject")
Dim jsPersonType As Type = jsPerson.GetType()
Dim name As String = jsPersonType.InvokeMember("getName", _
         Reflection.BindingFlags.InvokeMethod, Nothing, jsPerson, Nothing)
Dim age As Integer = jsPersonType.InvokeMember("getAge", _
        Reflection.BindingFlags.InvokeMethod, Nothing, jsPerson, Nothing)
MsgBox(String.Format("Name: {0} Age: {1}", name, age))

上面的代码应该能完美工作并给你期望的结果。我不会详细解释 `InvokeMember` 函数,MSDN 和 Google 上有大量的帮助信息。

现在下一步是什么?我们可以访问简单类型和复杂(自定义)类型。我们能访问页面上的对象(HTML 按钮、文本字段、div)吗?是的,我们可以。而且令人惊讶的是,它们更容易访问。让我们卷起袖子,用 JavaScript 写一个简单的函数,它将返回页面上碰巧存在的 `Button` 对象。

<script type="text/javascript">
    function getHtmlButton() {
    //assuming there's a button with id myBtn on the page
        return document.getElementById('myBtn');
    }
</script>

现在,在 VB.NET 端,我们可以这样做:

Dim htmlButton As Object = Me.WebBrowser1.Document.InvokeScript("getHtmlButton")
htmlButton.value = "Set from vb.net"

上面的代码会将按钮文本更改为“Set from vb.net”。我们可以通过做一些事情来稍加改进上面的代码:

  • 在你的项目中添加对“Microsoft HTML Object Library”的引用,可以在 COM 部分找到。
  • 现在,将上面代码的第一行更改为:
  • Dim htmlButton As mshtml.HTMLInputElement = _
        Me.WebBrowser1.Document.InvokeScript("getHtmlButton")

这些更改的作用是,首先,它将使我们的 `htmlButton` 对象成为强类型,而不是 `Object` 类型。其次,你将获得 Visual Studio 的智能感知,这将使你更容易地弄清楚要调用/设置 `htmlButton` 的哪个方法/属性。

我将再举一个例子来展示可能性。

'assuming JavaScript got a function which will return a div on the page
Dim htmlDiv As mshtml.HTMLDivElement = _
    Me.WebBrowser1.Document.InvokeScript("getHtmlDiv")
htmlDiv.style.background = "Red"

MSHTML 中的大多数类型都是不言自明的,你可以猜测 HTML 将如何转换为 MSHTML 类型。但如果你不确定,这里有一个小技巧:_最初将你的变量设置为 `Object` 类型,并且一旦你从 JavaScript 收到一个 HTML 对象(假设你设置了断点),将鼠标悬停在它上面,Visual Studio 将会告诉你确切的类型,或者,你可以在 Visual Studio 的 Autos 或 Local 窗口中查看_。

_一个技巧_:几乎所有的开发人员都知道这一点,但如果有人不知道,这里有一个小技巧,如果你想从 JavaScript 获得不止一个值。如果你传递的是对象,JavaScript 可以修改这些对象。如果我们以创建 VB.NET 中的 list(of String) 并需要从 JavaScript 获取名字和姓氏列表为例,我们可以这样做:

Dim firstNames As New myArr()
Dim lastNames As New myArr()
Me.WebBrowser1.Document.InvokeScript("getNames", _
               New Object() {firstNames, lastNames})

而在 JavaScript 端,我们可以这样做:

function getNames(firstNames, lastNames) {
    firstNames.add("John");
    firstNames.add("Chris");
    lastNames.add("Martin");
    lastNames.add("Churn");
}

结果是,我们可以轻松地从 JavaScript 获取多个返回值。我承认这不是最好的例子,但我希望你能明白我想要解释的概念。

从 VB.NET 函数返回一个值给 JavaScript

我确信我不需要这一节,但为了完整起见,我将在这里举一个例子。在 VB.NET 端,我们可以编写一个简单的函数来返回当前的日期和时间,如下所示:

Public Function getCurrentTime() As DateTime
    Return DateTime.Now
End Function

而在 JavaScript 端:

function getCurrentTime() {
    alert(window.external.getCurrentTime());
}

问我一个问题 :-) 我知道你现在一定有一个问题。我是否犯了错误或提交了不完整的代码?我怎样才能从 VB.NET 返回一个 `DateTime`,因为它不是一个简单类型,而且我还没有在它周围添加任何包装类?答案是,这是一个完全可工作的代码,即使 `DateTime` 不是一个简单类型,它是特殊的类型之一,会自动转换为 JavaScript 的 `Date` 类型。所有关于从 VB.NET 返回值的其他概念都在上面解释了,我认为不需要进一步的解释。你可以返回字符串、整数、布尔值、日期时间,而无需任何特殊的安排,但对于其他类型,你必须将类型/类暴露给 COM,以便它可见。

异常处理

到目前为止,我所做的所有示例(为了简单起见)都没有进行任何异常处理,但这并不意味着在生产代码中你会这样做。每一次从一个平台到另一个平台的调用都应该基于一个假设,即它可能不会成功,并且在某个时刻会出错。由于 JavaScript 是一种解释型语言,即使脚本中存在一些错误,直到你在 VB.NET 代码中调用 JavaScript 时,运行时才可能可见。同样,当你从 JavaScript 调用 VB.NET 时,如果 VB.NET 函数抛出了一个异常,它不会导致你的程序/exe崩溃,而是会被抛回给 JavaScript,你将从 Internet Explorer 收到一个 JavaScript 错误对话框。

在 JavaScript 中处理 VB.NET 异常

即将推出……

在 VB.NET 中处理 JavaScript 异常

即将推出……

事件处理

从 JavaScript 处理 VB.NET 事件

即将推出……

从 VB.NET 处理 JavaScript 事件

即将推出……

关注点

即将推出……

历史

  • 2009/04/12:首次迭代,仅编写了第一个名为“Hello world”的部分。
  • 2009/04/13:添加了向 JavaScript 传递参数的部分。
  • 2009/04/14:添加了向 JavaScript 传递数组和向 VB.NET 函数传递参数的部分。
  • 2009/04/16:添加了从 JavaScript 函数返回一个值给 VB.NET 的部分。
  • 2009/04/19:完成了从 JavaScript 返回值的章节。添加了从 VB.NET 函数返回一个值给 JavaScript 的部分。
© . All rights reserved.