We help IT Professionals succeed at work.

vb6 byref withevents not working

AmeriSciences
AmeriSciences asked
on
I am having an issue with by ref in vb6.

I have a private form with events object declared in a form.  If I pass the private form to a function as by ref, the object is not returned to the parent routine.  here is a code and debug snippet.

Public WithEvents o_frmBusy As frmVBBusy

Private Sub Command9_Click()
    Debug.Print o_frmBusy Is Nothing
   
    rtnSetForm o_frmBusy
   
    Debug.Print o_frmBusy Is Nothing
End Sub

Public Sub rtnSetForm(ByRef r_frmBusy As frmVBBusy)
    If r_frmBusy Is Nothing Then
        Debug.Print r_frmBusy Is Nothing
   
        Set r_frmBusy = New frmVBBusy
       
        Debug.Print r_frmBusy Is Nothing
    End If
End Sub

Debug is
True
True
False
True

if I remove the withevents it works.  any thoughts
Comment
Watch Question

Top Expert 2010
Commented:
The object needs to the same as defined in the WithEvents call otherwise no event will be wired.
When you make the call ( Set r_frmBusy = New frmVBBusy ) you set a reference to r_frmBusy but it should be o_frmBusy. Your code would need an additional call to wire the event in this situation.
Set r_frmBusy = New frmVBBusy
Set o_frmBusy = r_frmBusy '// wire the events.
x77

Commented:
Test To Validate WithEvents Influence on Fnc(ByRef O as AnyObject)
I am ver surprised ,    I think it is a bug ???

Private WithEvents x As Class1
Private z As Class1

Private Sub Form_Load()
   f x
   f z
   Debug.Print (x Is Nothing) 'Return True
   Debug.Print (z Is Nothing) 'Return false
   
   Dim y As Class1
   Set y = x
   f y
   Set x = y
   'Set x = y
   Debug.Print (x Is Nothing) 'Return True
End Sub
Sub f(ByRef a As Class1)
  If a Is Nothing Then Set a = New Class1
End Sub



Author

Commented:
If it is a bug MS is not going to fix it at this point.  lol

Author

Commented:
egl1044

I have been thinking how I would even do this in the app that I have written.  This is a set of common forms and modules that indicate to the user that the app is busy.  The withevents part is to allow the user the ability to tell the app to cancel what it is doing.

In the example I posted, I have the o_frmBusy object as public, but it is really a private in the app.  I was trying the Public type to see if that would fix the problem.  

I played around with it a little and it looks like I am going to have to create the form object in the parent form and then pass it into the routine to show it and close it.

Thanks for your help.
Top Expert 2010

Commented:
I don't see a bug :( you just need to explicitley reference the same object name when you use WithEvents keyword.
Top Expert 2010

Commented:
BTW: I understand where your concern is for this but if you use the WithEvents keyword it expects the object to be referenced specifically so it can do stuff internally to wire the events. When you don't use the WithEvents keyword it doesn't expect that you want to wire any events so it works in that case.

Author

Commented:
How would I pass the private variable instance to the public routine to use in the new instanciation?

Or in the set statement to force the object that was created in the routine to be passed back.  

I guess I could have the routine return the new object....

any other thoughts
Top Expert 2010

Commented:
If you could provide a "real time" example of what your application is doing it would help with a better solution. Can you post example source code directly from your application?

Author

Commented:
Here it is

The Form Code.

Private o_colIsDirty As Collection  'This collection holds the names of the objects that are on this object that are dirty

Private WithEvents o_frmBusy As frmVBBusy


Private Sub Form_Unload(Cancel As Integer)
    Const cstRoutineName = "Form_Unload"

    If prcGetRegionValue("TrapOnErrorGoTo") Then On Error GoTo RoutineError

   'here the o_frmBusy is nothing

    Cancel = basVBBusy.rtnShowBusyForm(o_colIsBusy, Me, o_frmBusy)

   'here the o_frmBusy is nothing

    If Cancel = 0 Then
        If o_blnIsDirty Then
            If MsgBox("Are you sure you want to cancel?  All unsaved work will be lost.", vbYesNo + vbDefaultButton2) = vbNo Then
                Cancel = -1
            End If
        End If

        If Cancel = 0 Then
            Call basVBForm.rtnSaveFormSetting(Me)
        End If
    End If

    Call basVBForm.rtnSaveFormSetting(Me)
   
    If Cancel = 0 Then
        End
    End If

RoutineExit:
    Exit Sub

RoutineError:
    Call gsubError(Err.Number, Err.Description, o_cstObjectName, cstRoutineName, "", True)
    Resume RoutineExit
End Sub

in basVBBusy

Public Function rtnShowBusyForm(ByRef r_colIsBusy As Collection, ByRef r_frm As Form, ByRef r_frmBusy As frmVBBusy) As Integer
    Const cstRoutineName = "rtnShowBusyForm"

    If prcGetRegionValue("TrapOnErrorGoTo") Then On Error GoTo RoutineError
   
    Dim intReturn As Integer
   
    If rtnCheckIsBusy(r_colIsBusy) Then
        'GWS 4/23/2009: can not do a msgbox here, because it will stop the running process.
       
        If r_frmBusy Is Nothing Then
            Set r_frmBusy = New frmVBBusy
        End If
       
        Call r_frmBusy.rtnShowForm(r_frm)
       
        r_frm.Enabled = False
       
        intReturn = -1
    Else
        intReturn = 0
    End If

RoutineExit:
    rtnShowBusyForm = intReturn
    Exit Function

RoutineError:
    Call gsubError(Err.Number, Err.Description, o_cstObjectName, cstRoutineName, "", True)
    Resume RoutineExit
End Function

Public Function rtnCheckIsBusy(ByRef r_colIsBusy As Collection) As Boolean
    Const cstRoutineName = "rtnCheckIsBusy"

    If prcGetRegionValue("TrapOnErrorGoTo") Then On Error GoTo RoutineError

    Dim blnReturn As Boolean

    blnReturn = (r_colIsBusy.Count <> 0)

RoutineExit:
    rtnCheckIsBusy = blnReturn
    Exit Function

RoutineError:
    Call gsubError(Err.Number, Err.Description, o_cstObjectName, cstRoutineName, "", True)
    Resume RoutineExit
End Function


let me know if i missed something.
Top Expert 2010

Commented:
Well that helps to see what your doing but it's unclear if you want to open a new instance of frmVBBusy every call or just want to load it once and only show when your main application is still busy. In that case don't unload the form just Hide and Show until your ready to Unload the form. I don't know how your calling this line calling ( Private WithEvents o_frmBusy As frmVBBusy ) unless frmVBBusy is actually a class and not a form. Can you be more specific.. I am little confused yet, I have an idea what your doing but yet I think what you really need can be accomplished without doing all this extra work.
For example in your code your calling ( Set r_frmBusy = New frmVBBusy ) if that is a form a NEW form will be loaded each time. If it's a class a new reference will be created to the class but it's not associated with the "real" o_frmVBBusy you have declared using withevents. You need to make additional call to reference that object. Now if frmVBBusy is a Class then you need to expose the reference to the form from the class using a property. I don't know exactly what your looking for so you will need to explain in more detail...

 


        

Open in new window

Author

Commented:
These items are in the declarations section of the form.  (Sorry I had a typeo in the first example.  o_colIsDirty = o_colIsBusy)

Private o_colIsBusy As Collection   'This collection holds the names of the objects that are on this object that are busy

Private WithEvents o_frmBusy As frmVBBusy


frmVBBusy is a Form

If there is an easier way I would love to here it.  The goal here is the keep the user from ending the program when it is still busy, and also keep them from moving to another section of the program while it is still busy.

When an action starts it adds a value to the o_colIsBusy Collection and when it is removed when the action is done.  When the collection is empty the program is no longer busy.  I know that is a bit combersome, but it was what I could do with what I had.  I knew of no easier way to know if the app was busy in vb6.  

I hope this helps, if not please ask and I will try to answer additional questions.  You have given me a few new ideas.  Thanks for that.
Top Expert 2010

Commented:
Can you start a new project and keep Form1 by default and add one more form called frmVBBusy. Then paste this code below into Form1. Run project and try to close Form1... It won't close until you set the collection to no items. I see what your issue is where you doing this from a module... You might want to consider using a Public variable in a form or creating a custom class.
Option Explicit

Private WithEvents o_frmBusy  As Form
Private col As Collection

Private Sub Form_Load()

  Set o_frmBusy = frmVBBusy '// wire events to frmVBBusy
  Set col = New Collection
  col.Add "Test" ' 1 item
  
End Sub

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
  
  If col.Count <> 0 Then
    o_frmBusy.Show
    Cancel = True
  Else
    Unload frmVBBusy
  End If

End Sub

Private Sub o_frmBusy_QueryUnload(Cancel As Integer, UnloadMode As Integer)

  o_frmBusy.Hide
  Cancel = True
  
End Sub

Open in new window

Top Expert 2010
Commented:
I wrote an example that you can look at to see if it's what your looking for it might be because of the way your design logic is happening. I don't really know how your application design logic is so it makes this difficult. The example just does what you asked doesn't exit the Main UI until all items have been completed. This assumes you only have 2 forms.. But you can apply the same logic to each additonal form, I don't see a reason to use the new keyword if you already have a busy dialog form to display anytime the collection isn't empty. Reference your Workitem collection in a module and just add / remove items when neccesary. Make the decision of what will be your Main UI form and start to add the design logic from there.
https://filedb.experts-exchange.com/incoming/ee-stuff/7954-frmExample.zip 

Author

Commented:
Well that is a very simple and elegant approach.  I usually try to stear clear of public variables, but in this case it may be the best solution.

Thanks