Click here to Skip to main content
15,884,472 members
Articles / Programming Languages / Visual Basic

Dynamic Events in VB.NET

Rate me:
Please Sign up or sign in to vote.
4.52/5 (8 votes)
26 Mar 2013CPOL2 min read 38.9K   728   20   3
How to handle dynamic, late binding, events for COM objects in VB.NET.

Introduction

This article shows a way to use events with late bound COM objects. To solve the problem some C# script was used...

Background

There are two common ways in VB.NET to create a late bound object.

  • Using the CreateObject method.

    VB
    Dim ie As Object = CreateObject("InternetExplorer.Application")
  • Using the System.Activator.CreateInstance method.

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

And there are three common ways in VB.NET to call methods and properties on a late bound object.

  • With Option Strict turned off

    VB
    ' call a method
    ie.Navigate("about:blank")
    ' read a property
    Dim readyState As Integer = ie.ReadyState
    ' write a property
    ie.Visible = True
  • Using the Microsoft.VisualBasic.Interaction.CallByName method.

    VB
    ' 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)
  • Using the System.Type.InvokeMember method.

    VB
    ' 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})

There are some ways discussed on how to use events with late bound objects, see Google search ' Eventhandler for late bound objects). But there isn't any way to add event handlers on late bound COM objects like with the dynamic Type in C#.

  • Working in C#

    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()
    {
    
    }
  • Not working in VB.NET

    VB
    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)

A first idea was to write a library in C# but the name of the event is hard coded. So that wasn't possible too, except ...It is when compiling C# script at runtime...

  • So first a interface was made to make calls into the compiled C# script.

    VB
    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
  • Then a class was written that implemented the interface with Factory methods that compiled and returned instances of the class/interface. From then on events could be attached to and detached from an object.

    VB
    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

Using the code

To attach an event now:

  • A delegate declaration is required.

    VB
    Delegate Sub OnQuitDelegate()
  • A event handler is required.

    Sub ie_OnQuit()
        ' ...
    End SUb	
  • A instance of the delegate is required.

    VB
    Dim [delegate] As [Delegate] = New OnQuitDelegate(AddressOf ie_OnQuit)
  • And a instance of EventBinder for the specific event is required.

    VB
    Dim binder As EventBinder = EventBinder.CreateEventBinder("OnQuit")
  • From then on it's possible to attach, detach events...

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

But it's better for performance to create the interfaces for all events at once. (Not that speed matters, this is VB.NET and has the speed of light...)

  • Bind to more then one event:
    VB
    ' 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

History

  • 18/03/2013: First draft.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Belgium Belgium
Currently i work in a small company making software for windows mobile. I have to be very "all-round" working in C++, VB.Net, VBScript and WinDev.

This i do for Windows, Web and Mobile development.

Learning new things is what i like to do most, improving my style and attitude TID!

Comments and Discussions

 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA12-Apr-13 19:14
professionalȘtefan-Mihai MOGA12-Apr-13 19:14 
GeneralMy vote of 5 Pin
Member 373058726-Mar-13 2:02
Member 373058726-Mar-13 2:02 
GeneralRe: My vote of 5 Pin
Vozzie226-Mar-13 8:30
Vozzie226-Mar-13 8:30 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.