Invalid use of AddressOf operator VB6 - vb6

My first post here.
I have c# .dll, to use with a scanner.
I intend to use it with some legacy vb6 applications.
The .dll raises an event with the scanned code using RaiseArgs.
I am trying to write an .ocx library for use with VB6 apps.
To catch this event in an .ocx library I am trying to adapt this code:
Sub TestEvents()
Dim Obj As New Class1
' Associate an event handler with an event.
AddHandler Obj.Ev_Event, AddressOf EventHandler
' Call the method to raise the event.
Obj.CauseSomeEvent()
' Stop handling events.
RemoveHandler Obj.Ev_Event, AddressOf EventHandler
' This event will not be handled.
Obj.CauseSomeEvent()
End Sub
Sub EventHandler()
' Handle the event.
MsgBox("EventHandler caught event.")
End Sub
Public Class Class1
' Declare an event.
Public Event Ev_Event()
Sub CauseSomeEvent()
' Raise an event.
RaiseEvent Ev_Event()
End Sub
End Class
But I am getting
Invalid use of AddressOf operator error when I call: AddHandler Obj.Ev_Event, AddressOf EventHandler
What could be probable cause of this error?
I suspect, that i am not going in the right direction with solving this task, so would it be a better way to approach this problem?

You have a mix of VB6 and VB.NET I think. In VB6, if you want to add an event handler for event on an object that you create yourself (as opposed to being added with the Forms designer) you would do it something like this:
Private WithEvents mObjectThatHasEvents As Class1
Private Sub StartListeningToEvents()
If mObjectThatHasEvents Is Nothing Then
Set mObjectThatHasEvents = New Class1
End If
End Sub
Private Sub TriggerEvent()
mObjectThatHasEvents.CauseSomeEvent
End Sub
'Assumes that Class1 has an event called "MyEvent".
Private Sub mObjectThatHasEvents_MyEvent()
MsgBox("EventHandler caught event.")
End Sub
Private Sub StopListeningToEvents()
Set mObjectThatHasEvents = Nothing
End Sub
If you need to keep the event source object alive while not listening to its events, you will need a second reference to it via a variable that is not "WithEvents".
I should point out that the VB6 IDE will recognize the event source. In the code pane, use the dropdowns at the top to select the object and event. The IDE will stub them out for you automatically (just like buttons or anything else).

tcarvin pretty much explained what you have to do but here's your code, adjusted:
Private WithEvents mObjWithEvents As Class1
Sub TestEvents()
Dim Obj As New Class1
Set mObjWithEvents = Obj
' Call the method to raise the event.
Obj.CauseSomeEvent
' Stop handling events.
Set mObjWithEvents = Nothing
' This event will not be handled.
Obj.CauseSomeEvent
End Sub
Private Sub mObjWithEvents_EvEvent()
' Handle the event.
MsgBox ("EventHandler caught event.")
End Sub
And your Class1:
Public Event EvEvent()
Public Sub CauseSomeEvent()
' Raise an event.
RaiseEvent EvEvent
End Sub
Notice that Ev_Event was renamed to EvEvent.

Related

Outlook VSTO: Cancel out of Send event for appointment items

I am creating an Outlook VSTO that allows users to enter information in an additional form-region on the New appointment form.
When sending the appointment I would like to capture the 'Send' event and perform some checks on the data the user provided. When this data is OK the appointment can be send, otherwise the Send action must be cancelled.
My code is like this:
Dim apptItem as Outlook.AppointmentItem
Private Sub Test_FormRegionShowing(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.FormRegionShowing
apptItem = OutlookItem
AddHandler apptItem.Send, AddressOf sendAppt
End Sub
Private Sub sendAppt()
If some_test = False then
else
Cancel=True
End If
End Sub
How do I pass the event arguments to my SendAppt function so that I can cancel out of the routine and prevent the meeting from being send?
The appointment itself is never sent - Outlook creates a brand new MeetingItem object and send it. You need to use the Application.ItemSend event and check if the Item parameter points to a MeetingItem object. You can then check which appointment it corresponds to using MeetingItem.GetAssociatedAppointment.
Well, the answer was rather simple and the solution is like this:
Private sub SendAppt (ByRef Cancel as Boolean)
If [some test] = False Then
Cancel = True
End If
End Sub
This will cancel out of the send routine and holds the meeting item from being send.

Passing timer value to next form

I developing an application that require pass the timer value to next form. For example Form A time out is 30 seconds, If the user dont click on the screen, it will back to the Main Screen. Sames go to the Form B. Please help. Thanks
You need to modify the constructor(s) of the page(s) you want to accept the timer value, like this:
Public Class FormA Inherits Form
Private timerValue As Integer
Public Sub New(_timerValue As Integer)
timerValue = _timerValue
End Sub
Public Sub MethodA
' Do something with timerValue here
End Sub
End Class

Attempting to learn polymorphism, etc. in VB6, but my code doesn't do what I want it to

Here's what I've got on a command button; it's just creating variables and attempting to output their ID (which should be an instance variable inherited from the base class.)
Private Sub Command1_Click()
Dim ball1 As Ball, ball2 As Ball
Dim cube1 As Cube, cube2 As Cube
Set ball1 = New Ball
Set cube1 = New Cube
Set cube2 = New Cube
Set ball2 = New Ball
MsgBoxTheID (ball1) 'errors; should be 0
MsgBoxTheID (ball2) 'errors; should be 3
MsgBoxTheID (cube1) 'errors; should be 1
MsgBoxTheID (cube2) 'errors; should be 2
Call ball1.MsgBoxID ' works; displays 0
Call ball2.MsgBoxID ' works; displays 3
Call cube1.MsgBoxID ' works; displays 1
Call cube2.MsgBoxID ' works; displays 2
End Sub
Modeul1.bas:
Global globalID As Integer
Public Sub MsgBoxTheID(theObj As BaseObj)
' this function is meant to accept objects of type Ball, Cube, and BaseObj
MsgBox theObj.ID
End Sub
BaseObj Class Module:
Public ID As Integer
Public isVisible As Boolean
Public Sub setVisiblity(newVis As Boolean)
isVisible = newVis
End Sub
Public Sub MsgBoxID()
MsgBox ID
End Sub
Private Sub Class_Initialize()
ID = globalID
globalID = globalID + 1
End Sub
Ball Class Module:
Implements BaseObj
Private theObj As BaseObj
Public radius As Double
Private Property Let BaseObj_ID(ByVal RHS As Integer)
End Property
Private Property Get BaseObj_ID() As Integer
End Property
Private Property Let BaseObj_isVisible(ByVal RHS As Boolean)
End Property
Private Property Get BaseObj_isVisible() As Boolean
End Property
Public Sub MsgBoxID()
Call theObj.MsgBoxID
End Sub
Private Sub BaseObj_MsgBoxID()
Call theObj.MsgBoxID
End Sub
Public Sub BaseObj_setVisiblity(newVis As Boolean)
End Sub
Private Sub Class_Initialize()
Set theObj = New BaseObj
End Sub
Cube Class Module:
Implements BaseObj
Private theObj As BaseObj
Public sideLength As Double
Private Property Let BaseObj_ID(ByVal RHS As Integer)
End Property
Private Property Get BaseObj_ID() As Integer
End Property
Private Property Let BaseObj_isVisible(ByVal RHS As Boolean)
End Property
Private Property Get BaseObj_isVisible() As Boolean
End Property
Public Sub MsgBoxID()
Call theObj.MsgBoxID
End Sub
Private Sub BaseObj_MsgBoxID()
Call theObj.MsgBoxID
End Sub
Public Sub BaseObj_setVisiblity(newVis As Boolean)
End Sub
Private Sub Class_Initialize()
Set theObj = New BaseObj
End Sub
There are several things I don't like about this, two of which I am of the impression are unavoidable: (1) the fact that it's a mess compared to C++, and (2) the fact that the Ball and Cube classes merely contain an object of BaseObj type. They are not actually inheriting anything from BaseObj; they are only being forced to implement the same interface (whoopty doo.)
To make matters worse, and this is the one that I am truly hoping is rectifiable, they do not seem to be able to fill in for an object of the base class when it comes to parameter passing.
Am I doing something wrong?
Visual Basic 6 is not the ideal language with which to learn the "purer" form of OOP. VB6 was designed to implement a very much hybridized version of object-based programming that orbited the Microsoft Component Object Model (COM) world, with its interface inheritance orientation. VB6 does not support implementation inheritance, which tends to make the kind of polymorphism you're looking for hard to do.
There are a few tricks I recall from the VB6 era to "get around" (sort of) the implementation inheritance problem, particularly when it comes to substituting an object of a base class for a subclass. One trick I remember is to declare a property procedure of the type of the base interface that returns a reference to "Me" as the return type. That "tricks" the runtime into providing the conversion into the desired interface. There's another magic trick to make a property the "defaut" property by setting its "procedure number" to -4 in one of VB6's design dialogs.
The point? If you're really wanting to get into conventional OO programming, don't try to learn it with VB6 if you don't have to. Move up to (at least) VB.NET, C#, or Java. I don't say that as a VB6 hater - heck, knowing these stupid details paid the bills for a long time - but its a tough nut to crack to translate its own little idiosyncrasies into a good, fundamental understanding of OOP.
Good luck!
You've figured out how to fix the error, but I'll contribute the "why".
In VB6 (and VB5, etc), there are two syntaxes for invoking a method/function/subroutine/statement. The first is this:
MySubName arg1, arg2, arg3, arg4
Blech, I know it is my bias from C and Java, but I like to see parenthesis around my argument list. That sytax is this:
Call MySubName(arg1, arg2, arg3, arg4)
So those two are equivilent.
What you ran into is not the effect of the Call. What you ran into is the effect of the unneeded parenthesis in the non-Call version. The parenthesis force the statement/arg inside them to be evaluated before the rest of the statement (think math order of operation).
So this:
SomeSub (arg1)
Is like this:
temp = (arg1)
SomeSub temp
Further, objects in VB6 can have a "default property". This lets you write code like this:
Dim name as String
name = txtName
Instead of assigne the textbox object reference to name, the default property of .Text is used, and the result is like this:
Dim name as String
name = txtName.Text
So when you tried to evaluate SomeSub (arg1) I think it would attempt to locate and execute the default property of your object and pass that value to SomeSub.
Well, I figured it out, sort of.
MsgBoxTheID (ball1) 'errors; should be 0
MsgBoxTheID (ball2) 'errors; should be 3
MsgBoxTheID (cube1) 'errors; should be 1
MsgBoxTheID (cube2) 'errors; should be 2
...needs to be changed to...
Call MsgBoxTheID (ball1) 'errors; should be 0
Call MsgBoxTheID (ball2) 'errors; should be 3
Call MsgBoxTheID (cube1) 'errors; should be 1
Call MsgBoxTheID (cube2) 'errors; should be 2
... even though MsgBoxTheID has no return type, which is weird because I always thought Call was simply something that you could use to discard the return value without having to declare a variable, like so:
dim unneededVar as Integer
unneededVar = FunctionNameThatReturnsAnInteger()
But I guess not. So... I'll have to go read up on exactly what the Call statement is doing to make this example program work, but it's definitely working now. (I also had to add BaseObj_ID = theObj.ID to the Property Get BaseObj_ID() As Integer methods in the classes that were implementing BaseObj.)

How to raise an event from vb6 module?

I have developed a custom visual basic 6 control and declared a few custom events. Is it possible in vb6 to raise these events from a module or I need to implement a special "proxy" methods in my control to do this?
RaiseEvent:
Compile error:
Only valid in object module.
(Which makes sense.)
Yes, you need a Friend method on your class that you would call to raise events from your module:
Class:
Public Event Click()
Friend Sub OnClick()
RaiseEvent Click
End Sub
Module:
someVar.OnClick
Perhaps not entirely the answer you are looking for, but it is possible to use Event-like procedures from plain Modules:
First define a Callback Interface:
IEventsClient (Class Module):
Option Explicit
Public Sub PropertyChanged(sender As Object, property As String)
End Sub
MyModule:
Option Explicit
Public EventClients As Collection
Public Sub OnPropertyChanged(property As String)
Dim eventsClient As IEventsClient
Dim element As Variant
For Each element In EventClients
Set eventsClient = element
eventsClient.PropertyChanged MyControl, property
Next
End Sub
Public Sub RaiseSomePropertyChanged()
OnPropertyChanged "SomeProperty"
End Sub
The main Form:
Option Explicit
Implements IEventsClient
Private Sub Form_Load()
'Entry point of the application'
Set MyModule.EventClients = New Collection
MyModule.EventClients.Add Me
End Sub
Private Sub IEventsClient_PropertyChanged(sender As Object, property As String)
If TypeOf sender Is MyControl Then
Select Case property
Case "SomeProperty"
' DoSomething'
End Select
End If
End Sub

VB6 Collection Remove Doesn't Fire Class_Terminate

I apologize in advance; this is a long question. I've tried to simplify as much as I can but it's still a bit more long-winded than I'd care to see.
In some legacy code, we've got a VB6 collection. This collection adds objects via the .Add method and removes them via the .Remove method. However, via tracing I can see that sometimes when the .Remove is called it appears that the class terminate for the object isn't called. But it's not consistent; it happens only rarely and I can't isolate the circumstances under which it fails to fire the class terminate.
Consider the following demonstration code:
Option Explicit
Private Const maxServants As Integer = 15
Private Const className As String = "Master"
Private Sub Class_Initialize()
Debug.Print className & " class constructor "
Set g_coll1 = New Collection
Dim i As Integer
For i = 1 To maxServants
Dim m_servant As Servant
Set m_servant = New Servant
m_servant.InstanceNo = i
g_coll1.Add Item:=m_servant, Key:=CStr(i)
Debug.Print "Adding servant " & m_servant.InstanceNo
Next
End Sub
Private Sub Class_Terminate()
Dim i As Integer
For i = maxServants To 1 Step -1
g_coll1.Remove (CStr(i))
Next
Debug.Print className & " class terminator "
Set g_coll1 = Nothing
Exit Sub
End Sub
and
Option Explicit
Private Const className As String = "Servant"
Private m_instanceNo As Integer
Private Sub Class_Initialize()
m_instanceNo = 0
Debug.Print className & " class constructor "
End Sub
Public Property Get InstanceNo() As Integer
InstanceNo = m_instanceNo
End Property
Public Property Let InstanceNo(newInstanceNo As Integer)
m_instanceNo = newInstanceNo
End Property
Private Sub Class_Terminate()
Debug.Print className & " class terminator for " & CStr(Me.InstanceNo)
End Sub
and this is the test harness code:
Option Explicit
Global g_coll1 As Collection
Public Sub Main()
Dim a As Master
Set a = New Master
End Sub
Now, for every run, the class_terminate of Servant is always invoked. And I can't see anything in the production code which should keep the object in the collection referenced.
1.) Is there any way to force the class terminate on the Remove? That is, can I call Obj.Class_Terminate and be assured it will work every time?
2.) On my production code (and my little test app) the classes are marked "Instancing - 5 MultiUse". I realize this may be some sort of threading issue; is there an effective way to prove (or disprove) that multi-threading is the cause of this issue--some sort of tracing I might add or some other sort of test I might perform?
EDIT: Per MarkJ's insightful comment below, I should add that the test posted above and the production code are both ActiveX exe's--part of the reason I ask about mulit-threading.
We had a similar issue, but where we could trace the non-termination of the objects to be down to an instance being held elsewhere in our application.
In the end, we had to write our Termination method like this:
Private Sub Class_Terminate()
Terminate
End Sub
Public Sub Terminate()
'Do real termination in here'
End Sub
So whenever you really wanted the class to be terminated (i.e. when you call g_coll1.Remove), you can also call Terminate on the held object.
I think that you could also make Class_Terminate public, but that's a bit ugly in my opinion.
Re your point (2), I think it's very unlikely to be a threading issue, but I can't think of a good proof/test off the top of my head. I suppose one very quite thing you can consider is: do you manually use threading in your application? VB6 doesn't do much threading automatically... (see edit below)
[Edit] MarkJ tells us that apparently building as an ActiveX application means that VB6 does automatically do threading. Someone else will have to explore the implications of this, since I wasn't familiar with it!

Resources