Results 1 to 5 of 5

Thread: Classic VB - Why can't I use WithEvents on arrays of objects?

  1. #1

    Thread Starter
    I'm about to be a PowerPoster!
    Join Date
    Jan 2005
    Location
    Everywhere
    Posts
    13,647

    Arrow Classic VB - Why can't I use WithEvents on arrays of objects?

    Note: To answer this question fully would require that you have an indepth knowledge of how events work in the COM world, and then you'd already know the answer. Regardless, I'm not going to require that of anyone
    During the course of your programming, if you have used classes at all you will probably have also used the keyword WithEvents. WithEvents is used when declaring an object variable, to indicate that you also wish to receive events raised by that object. It is nice because you can then use the object variable just like a control on a form, except that it can be used in a class.
    Private WithEvents myObject As SomeClass

    You have probably also used arrays of object variables too. This comes in handy when you want to manage multiple objects cleanly, as you can loop through them and carry out the same actions to each in turn (See this thread for an explanation of arrays).
    Private myObjects() As SomeClass

    Now the trouble comes when you, as happens eventually, want to combine the two. You need to handle events from your objects, but you also need to have an array of them, and handle events from all of the object instances. Since programmers are inherently logical, you'd try this:
    Private WithEvents myObjects() As SomeClass
    ... and VB will give you a nice, helpful, error message that tells you exactly why it isn't allowed.

    Now really the reason why you cannot use WithEvents on an array of objects is quite simple - if you had a single event handler for each object, you would have no way of knowing which object fired the event. You will probably have noticed that event handlers for controls that are part of control arrays will have an extra Index parameter - this is because control arrays were invented before the days of COM and it required some fair hacking on Microsoft's part to add the Index property to control events under the COM system. This same hacking could not be done for arrays of objects, because the objects do not have Index properties - your array simply holds references to each object.

    So do we give up? No... of course there is always a solution In this case it's one that sounds complex, but in reality (and you'll see an example of it) it's not too hard. I am going to offer two solutions - one for classes that you create yourself, and one for those that you do not have the source to.

    If you have the source it's easy. First you need to get rid of all events - since we can't use them. You can replace the RaiseEvent calls with calls to named methods in your event-handling class or form, that will form the new event handlers. Now you run into the first potential problem which is that these methods may not exist. To ensure that they do, we will have to require that the event-handling class/form Implements an interface class which defines all of our events and their parameters. Implementing an interface means that the class/form will have to contain all the event handling methods, so we eliminate the possibility that they do not exist when we are trying to fire an event.

    You define an "event" in the interface class like this:
    Sub MyEvent(AnyParams)
    End Sub
    Note that it is not necessary for the function body to be empty, but whatever you put in there we are not going to use anyway.

    Once you've defined all events you then need to know where to send them. For that purpose we can define a property in our custom class. Note that we type this property as the type of our event interface - that forces potential event handling classes to contain the proper event handlers and eliminates any possibility of errors on our side - it is up to "them" to ensure that they meet the requirements
    Private mCallback As IMyEventInterface

    Property Set Callback(newObj As IMyEventInterface)
        Set mCallback = newObj
    End Property

    Property Get Callback() As IMyEventInterface
        Set Callback = mCallback
    End Property

    Now to raise an event we can simply call the appropriate event handler in our "callback" class. Of course, we must first check to see if it exists - if no callback class has been set, we cannot call anything (obviously)
    ' raise an event
    If (Not mCallback Is Nothing) Then _
        mCallback.MyEvent anyParams

    And finally we want to implement an array of the classes - and now you can see the final solution that does NOT use WithEvents
    Implements IMyEventInterface

    Private myObject() As SomeClass

    Private Sub Form_Load()
        ReDim myObject(5)
        For i = 0 To 5
            Set myObject(i) = New SomeClass
            Set myObject(i).Callback = Me
        Next i
    End Sub

    Private Sub IMyEventInterface_MyEvent(AnyParams)
        ' our event handler
    End Sub



    Now you might recall I mentioned a solution for classes for which you do not have the source code for, such as classes in DLLs. Well here you can apply a bit of creative adaptation of the first solution. My SomeClass class is now going to a "wrapper" class for the closed-source class - so called because it will contain a reference to an instance of this class, but we will be using the wrapper class to access it. The instance of the closed-source class will need to be declared using WithEvents, because you can't change the way it raises events. However you still have the same setup in place for your callback and interface (you will need to include in your interface all events of the closed-source class that you handle in your wrapper class) - so you can declare an array of wrapper classes and use them to handle the events from the "wrapped" classes.

    You will need to also add a property so that you can access the wrapped class.
    Private WithEvents mWrapped As WrappedClass
    Private mCallback As IMyEventInterface

    ' Callback() property snipped...

    Property Set Wrapped(newObj As WrappedClass)
        Set mWrapped = newObj
    End Property

    Property Get Wrapped() As WrappedClass
        Set Wrapped = mWrapped
    End Property

    ' for each event handled, raise a new one by the callback method
    Private Sub mWrapped_SomeEvent()
        If (Not mCallback Is Nothing) Then _
            mCallback.SomeEvent
    End Sub
    Now you use the wrapper class, something like this:
    Implements IMyEventInterface
    Private myObject() As SomeClass

    Private Sub Form_Load()
        ReDim myObject(5)
        For i = 0 To 5
            Set myObject(i) = New SomeClass
            Set myObject(i).Callback = Me
            Set myObject(i).Wrapped = New WrappedClass
        Next i
    End Sub

    Private Sub IMyEventInterface_SomeEvent()
        ' event handler
    End Sub

    Hopefully that gives you the idea of how to handle events raised from arrays of objects, both for your own and for compiled classes. So that you can see I'm not spouting bilgewater I have also included a demonstration of the first technique. You will need an extraction program such as WinZip or 7-Zip to unzip the file, and then run the extracted project. I have commented the code so that you can (hopefully!) understand what's going on


    Attached Files Attached Files
    Last edited by penagate; Feb 17th, 2006 at 03:33 AM.

  2. #2
    VB6, XHTML & CSS hobbyist Merri's Avatar
    Join Date
    Oct 2002
    Location
    Finland
    Posts
    6,654

    Re: Classic VB - Why can't I use WithEvents on arrays of objects?

    Another code that doesn't require Implements, just one Class and a Form. This alternative solution relies on CallByName.

    By performance this is likely to be somewhat slower than Penagate's sample above, simply by judging by the use of CallByName and a Variant array.
    Last edited by Merri; Jan 15th, 2008 at 02:39 PM.

  3. #3
    Lively Member
    Join Date
    Jan 2009
    Posts
    90

    Re: Classic VB - Why can't I use WithEvents on arrays of objects?

    What about an Index parameter in the "events"?

    Would be nice in the tutorial, pretty easy to get it working but might not be for newbies.
    Last edited by Daniel Elkins; Jan 22nd, 2009 at 05:40 PM.

  4. #4
    "Digital Revolution"
    Join Date
    Mar 2005
    Posts
    4,471

    Re: Classic VB - Why can't I use WithEvents on arrays of objects?

    Would also be a lot less-confusing if you at least put the name of the file that the code goes in...

  5. #5
    Default Member Bonnie West's Avatar
    Join Date
    Jun 2012
    Location
    InIDE
    Posts
    4,060

    Re: Classic VB - Why can't I use WithEvents on arrays of objects?

    Here's another variation that combines the interface class and custom class into one:

    Option Explicit   'In Class1

    Private m_Callback As Class1
    Private m_Index    As Long

    Public Sub
    SomeEvent(ByVal Index As Long, ByVal Param As Long, ByRef RetVal As String)

    End Sub

    'The Friend scope modifier prevents the following properties and methods from
    'being Implemented yet still allows them to be accessible within the project


    Friend Property Get Callback(Optional ByVal Index As Long) As Class1
        Set Callback = m_Callback
    End Property
    Friend Property Set
    Callback(ByVal Index As Long, ByRef RHS As Class1)
        m_Index = Index
        Set m_Callback = RHS
    End Property

    Friend Sub
    TriggerAnEvent(ByVal Param As Long)
        Dim Frm As VB.Form, RetVal As String

        Debug
    .Assert Not m_Callback Is Nothing      'If code stops here, the Callback property must be set first!
        m_Callback.SomeEvent m_Index, Param, RetVal

        Set Frm = m_Callback
        Frm.Print RetVal
    End Sub

    Option Explicit   'In Form1

    Implements Class1

    Private m_Class1(1 To 3) As New Class1

    Private Sub Class1_SomeEvent(ByVal Index As Long, ByVal Param As Long, RetVal As String)
        Print Index, Param, ;
        RetVal = Switch(Index = 1&, "vbLeftButton", _
                        Index = 2&, "vbMiddleButton", _
                        Index = 3&, "vbRightButton")
    End Sub

    Private Sub
    Form_Load()
        Dim i As Long

        For
    i = 1& To 3&
            Set m_Class1(i).Callback(i) = Me
        Next

        AutoRedraw = True
        Caption = "Simulating WithEvents for Arrays of Objects via Implements Demo"
        Print "Index", "Button", "Const"
    End Sub

    Private Sub
    Form_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
        m_Class1(Choose(Button, 1&, 3&, , 2&)).TriggerAnEvent Param:=Button
    End Sub
    Attached Files Attached Files
    Last edited by Bonnie West; Feb 18th, 2015 at 03:35 PM.
    On Local Error Resume Next: If Not Empty Is Nothing Then Do While Null: ReDim i(True To False) As Currency: Loop: Else Debug.Assert CCur(CLng(CInt(CBool(False Imp True Xor False Eqv True)))): Stop: On Local Error GoTo 0
    Declare Sub CrashVB Lib "msvbvm60" (Optional DontPassMe As Any)

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width