Can we use Interfaces and Events together at the same time?

前端 未结 4 2078
情歌与酒
情歌与酒 2020-12-04 09:08

I\'m still trying to wrap my head around how Interfaces and Events work together (if at all?) in VBA. I\'m about to build a large application in Microsoft Access, and I want

4条回答
  •  情话喂你
    2020-12-04 09:40

    Because bounty is already headed for Pieter's answer I'll not attempt to answer the MVC aspect of the question but instead the headline question. The answer is Events have limits.

    It would be harsh to call them "syntactic sugar" because they save a lot of code but at some point if your design gets too complex then you have to bust out and manually implement the functionality.

    But first, a callback mechanism (for that is what events are)

    modMain, the entry/starting point

    Option Explicit
    
    Sub Main()
    
        Dim oClient As Client
        Set oClient = New Client
    
        oClient.Run
    
    
    End Sub
    

    Client

    Option Explicit
    
    Implements IEventListener
    
    Private Sub IEventListener_SomethingHappened(ByVal vSomeParam As Variant)
        Debug.Print "IEventListener_SomethingHappened " & vSomeParam
    End Sub
    
    Public Sub Run()
    
        Dim oEventEmitter As EventEmitter
        Set oEventEmitter = New EventEmitter
    
        oEventEmitter.ServerDoWork Me
    
    
    End Sub
    

    IEventListener, the interface contract that describes the events

    Option Explicit
    
    Public Sub SomethingHappened(ByVal vSomeParam As Variant)
    
    End Sub
    

    EventEmitter, the server class

    Option Explicit
    
    Public Sub ServerDoWork(ByVal itfCallback As IEventListener)
    
        Dim lLoop As Long
        For lLoop = 1 To 3
            Application.Wait Now() + CDate("00:00:01")
            itfCallback.SomethingHappened lLoop
        Next
    
    End Sub
    

    So how does WithEvents work? One answer is to look in the type library, here is some IDL from Access (Microsoft Access 15.0 Object Library) defining the events to be raised.

    [
      uuid(0EA530DD-5B30-4278-BD28-47C4D11619BD),
      hidden,
      custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "Microsoft.Office.Interop.Access._FormEvents")    
    
    ]
    dispinterface _FormEvents2 {
        properties:
        methods:
            [id(0x00000813), helpcontext(0x00003541)]
            void Load();
            [id(0x0000080a), helpcontext(0x00003542)]
            void Current();
        '/* omitted lots of other events for brevity */
    };
    

    Also from Access IDL here is the class detailing what its main interface is and what is event interface is, look for source keyword, and VBA needs a dispinterface so ignore one of them.

    [
      uuid(7398AAFD-6527-48C7-95B7-BEABACD1CA3F),
      helpcontext(0x00003576)
    ]
    coclass Form {
        [default] interface _Form3;
        [source] interface _FormEvents;
        [default, source] dispinterface _FormEvents2;
    };
    

    So what that is saying to a client is that operate me via the _Form3 interface but if you want to receive events then you, the client, must implement _FormEvents2. And believe it or not VBA will when WithEvents is met spin up an object that implements the source interface for you and then route incoming calls to your VBA handler code. Pretty amazing actually.

    So VBA generates a class/object implementing the source interface for you but questioner has met the limits with the interface polymorphism mechanism and events. So my advice is to abandon WithEvents and implement you own callback interface and this is what the given code above does.

    For more information then I recommend reading a C++ book that implements events using the connection point interfaces, your google search terms are connection points withevents

    Here is a good quote from 1994 highlighting the work VBA does I mentioned above

    After slogging through the preceding CSink code, you'll find that intercepting events in Visual Basic is almost dishearteningly easy. You simply use the WithEvents keyword when you declare an object variable, and Visual Basic dynamically creates a sink object that implements the source interface supported by the connectable object. Then you instantiate the object using the Visual Basic New keyword. Now, whenever the connectable object calls methods of the source interface, Visual Basic's sink object checks to see whether you have written any code to handle the call.

    EDIT: Actually, mulling my example code you could simplify and abolish the intermediate interface class if you do not want to replicate the way COM does things and you are not bothered by coupling. It is after all just a glorified callback mechanism. I think this is an example of why COM got a reputation for being overly complicated.

提交回复
热议问题