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

VB.NET 中的动态事件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.52/5 (8投票s)

2013年3月18日

CPOL

2分钟阅读

viewsIcon

40041

downloadIcon

755

如何在 VB.NET 中处理 COM 对象的动态、后期绑定事件。

引言

本文展示了一种使用后期绑定 COM 对象事件的方法。为了解决这个问题,使用了 C# 脚本...

背景

在 VB.NET 中创建后期绑定对象有两种常见方法。

  • 使用 CreateObject 方法。

    Dim ie As Object = CreateObject("InternetExplorer.Application")
  • 使用 System.Activator.CreateInstance 方法。

    Dim iet As Type = Type.GetTypeFromProgID("InternetExplorer.Application")
    Dim ie As Object = Activator.CreateInstance(iet)

在 VB.NET 中调用后期绑定对象上的方法和属性也有三种常见方法。

  • 在关闭 Option Strict 的情况下

    ' call a method
    ie.Navigate("about:blank")
    ' read a property
    Dim readyState As Integer = ie.ReadyState
    ' write a property
    ie.Visible = True
  • 使用 Microsoft.VisualBasic.Interaction.CallByName 方法。

    ' Call a method 
    CallByName(ie, "Navigate", CallType.Method, "about:blank")
    ' Read a property
    readyState = CInt(CallByName(ie, "ReadyState", CallType.Get))
    ' Write a property
    CallByName(ie, "Visible", CallType.Let, True)
  • 使用 System.Type.InvokeMember 方法。

    ' Call a method 
    ie.GetType().InvokeMember("Navigate", BindingFlags.InvokeMethod, Type.DefaultBinder, ie, {"about:blank"})
    ' Read a property
    readyState = CInt(ie.GetType().InvokeMember("ReadyState", BindingFlags.GetProperty, Type.DefaultBinder, ie, Nothing))
    ' Write a property
    ie.GetType().InvokeMember("Visible", BindingFlags.SetProperty, Type.DefaultBinder, ie, {True})

已经讨论了一些如何使用后期绑定对象事件的方法,请参阅 Google 搜索 ' Eventhandler for late bound objects)。但没有办法像在 C# 中使用 dynamic Type 那样为后期绑定 COM 对象添加事件处理程序。

  • 在 C# 中工作

    public delegate void OnQuitDelegate();
    
    static void Main(string[] args)
    {
        var iet = Type.GetTypeFromProgID("InternetExplorer.Application");
        dynamic ie = Activator.CreateInstance(iet);
    
        ie.OnQuit += new OnQuitDelegate(ie_OnQuit);
    }
    
    public static void ie_OnQuit()
    {
    
    }
  • 在 VB.NET 中不起作用

    Dim iet As Type = Type.GetTypeFromProgID("InternetExplorer.Application")
    ' No dynamic type...
    Dim ie = Activator.CreateInstance(iet)
    ' Not working...
    ' AddHandler ie.OnQuit, AddressOf ie_OnQuit
    ' Not working also...
    'ie.OnQuit += New OnQuitDelegate(AddressOf ie_OnQuit)

最初的想法是在 C# 中编写一个库,但事件名称是硬编码的。所以,除了...在运行时编译 C# 脚本时,这也不可行。

  • 因此,首先创建了一个接口,以便调用编译后的 C# 脚本。

    Public Interface IEventBinder
    
        Sub [AddHandler](ByVal source As Object, ByVal [delegate] As [Delegate])
        Sub [RemoveHandler](ByVal source As Object, ByVal [delegate] As [Delegate])
    
    End Interface
  • 然后编写了一个类,该类实现了该接口,并带有编译和返回类/接口实例的工厂方法。从那时起,就可以将事件附加到对象并从对象中分离事件了。

    Imports Microsoft.CSharp
    Imports System.CodeDom
    Imports System.CodeDom.Compiler
    Imports System.Reflection
    Imports System.Text
    
    Public NotInheritable Class EventBinder
        Implements IEventBinder
    
        Private _name As String
        Private _event As IEventBinder
    
        Private Sub New(ByVal name As String, ByVal [event] As IEventBinder)
            Me._name = name
            Me._event = [event]
        End Sub
    
        Public ReadOnly Property Name As String
            Get
                Return _name
            End Get
        End Property
    
        Public Sub [AddHandler](source As Object, [delegate] As System.Delegate) Implements IEventBinder.AddHandler
            Me._event.AddHandler(source, [delegate])
        End Sub
    
        Public Sub [RemoveHandler](source As Object, [delegate] As System.Delegate) Implements IEventBinder.RemoveHandler
            Me._event.RemoveHandler(source, [delegate])
        End Sub
    
        Public Shared Function CreateEventBinders(ByVal names() As String) As Dictionary(Of String, EventBinder)
            Using provider As CodeDomProvider = New CSharpCodeProvider()
                Dim parameters As New CompilerParameters() With {
                    .GenerateInMemory = True
                }
                parameters.ReferencedAssemblies.AddRange({
                    "System.dll",
                    "System.Core.dll",
                    Assembly.GetExecutingAssembly().Location,
                    "Microsoft.CSharp.dll"
                })
                Dim uid As String = Guid.NewGuid().ToString("N")
                Dim code As New StringBuilder()
                code.Append(
                    "using System;" & vbCrLf &
                    "" & vbCrLf &
                    "namespace DynamicEvent {" & vbCrLf
                )
                For Each name As String In names
                    code.Append(
                        String.Format(
                            "public class {0}Event{2} : {1}.IEventBinder" & vbCrLf &
                            "{{" & vbCrLf &
                            "   public void AddHandler(dynamic source, Delegate del) {{ source.{0} += del; }}" & vbCrLf &
                            "   public void RemoveHandler(dynamic source, Delegate del) {{ source.{0} -= del; }}" & vbCrLf &
                            "}}" & vbCrLf,
                            name,
                            GetType(IEventBinder).Namespace,
                            uid
                        )
                    )
                Next
                code.Append("}")
                Dim codeResult As CompilerResults = provider.CompileAssemblyFromSource(parameters, code.ToString())
                If codeResult.Errors.Count = 0 Then
                    Dim result As New Dictionary(Of String, EventBinder)
                    For Each name As String In names
                        Dim codeType As Type = codeResult.CompiledAssembly.GetType(String.Format("DynamicEvent.{0}Event{1}", name, uid))
                        Dim codeObject As Object = Activator.CreateInstance(codeType)
                        result.Add(name, New EventBinder(name, DirectCast(codeObject, IEventBinder)))
                    Next
                    Return result
                Else
                    ' todo: Compiler errors...
                    Return Nothing
                End If
            End Using
        End Function
    
        Public Shared Function CreateEventBinder(ByVal name As String) As EventBinder
            Return CreateEventBinders({name})(name)
        End Function
    
    End Class

使用代码

现在附加事件

  • 需要一个委托声明。

    Delegate Sub OnQuitDelegate()
  • 需要一个事件处理程序。

    Sub ie_OnQuit()
        ' ...
    End SUb	
  • 需要委托的一个实例。

    Dim [delegate] As [Delegate] = New OnQuitDelegate(AddressOf ie_OnQuit)
  • 需要一个特定事件的 EventBinder 实例。

    Dim binder As EventBinder = EventBinder.CreateEventBinder("OnQuit")
  • 从那时起,就可以附加、分离事件...

    Dim ie As Object = CreateObject("InternetExplorer.Application")
    binder.AddHandler(ie, [delegate])
    binder.RemoveHandler(ie, [delegate])

但最好一次性为所有事件创建接口。(不是说速度很重要,VB.NET 的速度是光速...)

  • 绑定到多个事件
    ' Create event binders
    Dim binders As Dictionary(Of String, EventBinder)
    binders = EventBinder.CreateEventBinders({
        "BeforeNavigate2",
        "DocumentComplete",
        "NavigateComplete2",
        "OnQuit",
        "StatusTextChange"
    })
    
    ' Create event handlers
    Dim delegates() As [Delegate] = {
        New BeforeNavigate2Delegate(AddressOf ie_BeforeNavigate2),
        New DocumentCompleteDelegate(AddressOf ie_DocumentComplete),
        New NavigateComplete2Delegate(AddressOf ie_NavigateComplete2),
        New OnQuitDelegate(AddressOf ie_OnQuit),
        New StatusTextChangeDelegate(AddressOf ie_StatusTextChange)
    }
    
    ' Bondage...
    For i As Integer = 0 To binders.Count - 1
        binders(binders.Keys(i)).AddHandler(ie, delegates(i))
    Next

历史

  • 2013/03/18:初稿。
© . All rights reserved.