Search code examples
excelvbavbscriptscriptcontrolmsscriptcontrol

How to Implement An Event in Microsoft Script Control?


All docs i've read on MSScriptControl say that it can respond to events of objects added to it.

the script control allows you to write script that will automatically fire when an event on an object occurs. https://msdn.microsoft.com/en-us/library/ms974586.aspx

the ScriptControl will be able to sink events generated by objects added using the AddObject method. http://flylib.com/books/en/1.492.1.154/1/

But i've not had success doing so. I assume this means code inside the ScriptControl will fire when the added object raises it's event. I'm unable to get any to work.

Seeking example code of adding any object to the ScriptControl, and handling events fired by that control. Don't care if the object is a custom class, a form, a control, or a built-in Excel object such as Worksheet.

Running Office 2010 32bit on Win Server 2008 64bit.

Open to alternate methods, such as WSH, but Tushar Mehta did not have success with that here http://dailydoseofexcel.com/archives/2009/08/19/using-vbscript-to-monitor-office-eventsor-not/

I've successfully added the Excel Application object to the ScriptControl, and executed code on the Excel Application object:

This works no problem:

Function TestProc()
          Dim oScriptCtl As New MSScriptControl.ScriptControl

          With oScriptCtl
                    ' init
                    .Language = "VBScript"
                    .AllowUI = True

                    ' add Excel application object
                    .AddObject "app", Application, True

                    ' add procedure
                    Dim sCode As String
                    sCode = "Sub TestProc : MsgBox ""hi"" : End Sub"
                    .AddCode sCode

                    ' run procedure. Msgbox displays. 
                    .Run "TestProc"
          End With

          ' cleanup
          Set oScriptCtl = Nothing
End Function

Fails:

In this test, m_oScriptCtl is a module-scoped variable. Nothing happens when i click the form:

Function TestForm()
          Set m_oScriptCtl = New MSScriptControl.ScriptControl

          With m_oScriptCtl
                    ' init
                    .Language = "VBScript"
                    .AllowUI = True

                    MyForm.Show False

                    .AddObject "app", Application, True
                    .AddObject "frm", MyForm, True
                    .State = Connected

                    Dim sCode As String
                    sCode = "Sub frm_Click():   MsgBox Chr(14):   End Sub"
                    .AddCode sCode
          End With
End Function

This next one reports the following error on .AddCode:

Expected ')'

Function TestSheet()
          Set m_oScriptCtl = New MSScriptControl.ScriptControl

          With m_oScriptCtl
                    ' init
                    .Language = "VBScript"
                    .AllowUI = True

                    .AddObject "app", Application, True
                    .AddObject "sheet", Sheet2, True
                    .State = Connected

                    Dim sCode As String
                    sCode = "Private Sub sheet_Change(ByVal Target As Range): MsgBox Target: End Sub"
                    .AddCode sCode
          End With
End Function

In the next test, MyClass is defined as:

Public Event MyEvent()

Public Sub TestEvent()
          RaiseEvent MyEvent
End Sub

But the following reports "object does not support property or method" on .Run. So in this case, it's not the event that fails-- i just can't run a method inside the class.

Function TestClassEvent()
          Set oScriptCtl = New MSScriptControl.ScriptControl

          Dim oClass As New MyClass

          With oScriptCtl
                    ' init
                    .Language = "VBScript"
                    .AllowUI = True

                    ' add objects
                    .AddObject "app", Application, True
                    .AddObject "oClass", oClass, True
                    .State = Connected

                    ' add code
                    Dim sCode As String
                    sCode = "Sub oClass_MyEvent() : MsgBox vbNullString : End Sub"
                    .AddCode sCode

                    .Run "oClass.TestEvent"
          End With

          ' cleanup
          Set oScriptCtl = Nothing
End Function

Clues:

Someone posted:

If you totally fail to sink your events, try calling 'ScriptControl1.Modules("Global").CodeObject.Name_Of_Your_Event(ParameterList)' http://computer-programming-forum.com/59-vbscript/4b059f9f6eacfaf0.htm

-- but that workaround is unclear to me: Event procedures are not supposed to be "called" explicitly, they're supposed to just fire. The following lines both give "Method or data member not found", in the above TestClassEvent example:

m_oScriptCtl.Modules("Global").CodeObject.MyEvent
m_oScriptCtl.Modules("Global").CodeObject.TestEvent

I've not tested the following, because i'm not quite sure how:

the script control can't handle events from a class in the same project as the application it's being hosted in https://diigo.com/08we68

Not sure if the following is relevant, don't quite understand it: http://www.programmersheaven.com/discussion/79452/me-activecontrol-and-events


Solution

  • The key to making it work is: You must set the event-firing-object in the listener-class after adding both to the Script Control-- not before. Meaning, this line must be executed inside the SC:

    Set oListener.EventFiringObject = oEventFiringObject

    Here's a working example of firing, and responding to, events between objects inside a Script Control.

    In this example:

    • I demonstrate 2 kinds of event-firing objects: a custom class, and a Worksheet.
    • The custom class is instantiated before adding it to the scriptcontrol ("sc").
    • I call a method in the custom sc object.

    To setup the demo

    • Start a new project (ie, add a new Workbook in Excel).
    • In your VB IDE, add a reference to Microsoft Script Control.
    • Create the following VB components:

    Code

    Class clsSheetListener:

    Public WithEvents oSht As Worksheet
    
    Private Sub oSht_Change(ByVal Target As Range)
      ' show  changed cell
      MsgBox "Sheet Listener" & vbCrLf & "Changed: " & Target.Address _
              & vbCrLf & Target.Cells(1).Value2
    End Sub
    

    Class clsEventClass:

    Public Event MyEvent(sCaller As String)
    
    Public Sub Raise_MyEvent(sCaller As String)
      RaiseEvent MyEvent(sCaller)
    End Sub
    

    Class clsClassListener:

    Public WithEvents m_oEventClass As clsEventClass
    
    Private Sub m_oEventClass_MyEvent(sCaller As String)
      ' show my execution-scope
      MsgBox "Class Listener, " & sCaller & " caller"
    End Sub
    

    Module Module1:

    Function Main()
      ' init scriptcontrol
      Set m_oScriptCtl = Nothing
      Set m_oScriptCtl = New MSScriptControl.ScriptControl
      With m_oScriptCtl
        .Language = "VBScript"
        .AllowUI = True
    
        ' add Excel application object, needed for all Excel methods in script-control
        .AddObject "sc_Application", Application, True
    
    
        ' add Sheet2 to the sc
        ' code executed in sc refers to objects by name, as defined in .AddObject
        .AddObject "sc_oSheet", Sheet2, True
    
        ' init sheet event-listener, and add to sc
        Dim oSheetListener As New clsSheetistener
        .AddObject "sc_oSheetListener", oSheetListener, True
    
        ' register the sheet-object with its listener in the scriptcontrol
        ' so the listener can hear the sheet's events
        .ExecuteStatement "Set sc_oSheetListener.oSht = sc_oSheet"
    
    
        ' init custom event-firing class object, and add to sc
        Dim oEventClass As New clsEventClass
        .AddObject "sc_oEventClass", oEventClass, True
    
        ' init class-event listener, and add to sc
        Dim oClassListener As New clsClassListener
        .AddObject "sc_oClassListener", oClassListener, True
    
        ' register event-firing object with its listener inside the Script Control
        ' so the listener can hear the object's events
        .ExecuteStatement "Set sc_oClassListener.m_oEventClass = sc_oEventClass"
    
    
        ' cause event to be raised. 
        ' Call from local context, then sc-context.
        ' it's the same object instance in both cases
        oEventClass.Raise_MyEvent "Local"
        .ExecuteStatement "sc_oEventClass.Raise_MyEvent ""SC"""
    
      End With
    End Function
    

    Testing

    Step through Main(). You'll see when Raise_MyEvent fires MyEvent in clsEventClass, clsClassListener responds to the event with a message box.

    Now switch to Excel front-end, and enter a value in a cell in Sheet2. You'll see clsSheetListener respond to the Change event with a message box.