Passing class object instance between forms? (1 Viewer)

mdlueck

Sr. Application Developer
Local time
Today, 08:21
Joined
Jun 23, 2011
Messages
2,631
Greetings: I seem to have slammed into a brick wall... does anyone know a solution?

In my Access 2007 application, I created a custom VBA class. The intent was to have that class be global to this application, shared among the various forms the UI incorporates.

I cannot seem to find a way to pass my custom class object to the next form.

I have found sample code showing VB.net able to do what I want. However the only examples I have found for Access VBA have been to pass individual simple variables, not custom objects.

I was thinking to create a new form, called MAIN, have that create the instance of my custom class, then pass that instance off to the first UI form. That way the real first form would not have to create a new instance when that form is revisited in the course of using the application, and then just replace the new shared object with the copy from the form calling the "main" form. So have the new "main" form be the only place that an instance of the class is created, passed off, and from there the forms pass the object one to the next.

Suggestions?
 

stopher

AWF VIP
Local time
Today, 13:21
Joined
Feb 1, 2006
Messages
2,395
One option would be to make your object public. Put the following in a module:

Code:
Public myCustomObject As New clsCustomObject

In this way myCustomObject will always be available to you while the database is open.
 

mdlueck

Sr. Application Developer
Local time
Today, 08:21
Joined
Jun 23, 2011
Messages
2,631
All right, that is a slight enough change to quickly try. However I still get a run-time error '2498'... An expression you entered is the wrong data type for one of the arguments.

Here is the code I have in the MAIN form:
Code:
Option Compare Database
Option Explicit
Public ObjectCUR As New clsObjectCUR
 
Private Sub Form_Load()
  Dim stDocName As String
 
  'Open the orders dialog and exit
  stDocName = "orders"
  DoCmd.OpenForm stDocName, , , , , , ObjectCUR
  'DoCmd.Close acForm, Me.Name
End Sub
 
Private Sub Form_Unload(Cancel As Integer)
  Set ObjectCUR = Nothing
End Sub
 

spikepl

Eledittingent Beliped
Local time
Today, 14:21
Joined
Nov 3, 2010
Messages
6,142
I doubt you can pass an object in OpenArgs. That bit is defined as Variant in the docs, fair enuff, but it also says "string expression" .
 

stopher

AWF VIP
Local time
Today, 13:21
Joined
Feb 1, 2006
Messages
2,395
The Public statement needs to go in a module. Just create a new module and put it in there.

Don't try passing ObjectCUR as an argument. Since Public is public it will be available to any code in any form - no need to pass it.

OpenArgs only accepts string datatype in any case.


hth
Chris
 

mdlueck

Sr. Application Developer
Local time
Today, 08:21
Joined
Jun 23, 2011
Messages
2,631
All right, so if not this way, how is it possible to create an instance of a custom class such that all forms in this application may share the common object?

One form loads attributes into the object, the next form needs to access the attributes the previous form set.
 

mdlueck

Sr. Application Developer
Local time
Today, 08:21
Joined
Jun 23, 2011
Messages
2,631
The Public statement needs to go in a module. Just create a new module and put it in there.

So how does the additional module get invoked? And what exactly do I put as far as code into the Module window?

I can understand Form and Class code, Modules seem a bit vague. Thanks! :)
 

stopher

AWF VIP
Local time
Today, 13:21
Joined
Feb 1, 2006
Messages
2,395
So how does the additional module get invoked? And what exactly do I put as far as code into the Module window?
Think of modules at the "project" level (the project being your access database - the code part at least). So code in a module can be made available across your whole project.

The code is available whenever the project is available, even when no forms are running. It's invoked when you open the database.

You only need to add the one line I gave you for what you are doing. But a module would typically be used for functions and subroutines that you want to be available anywhere.

Suppose you put the following in a module. Then the function becomes immediately available.
Code:
Public Function fiveFive()
fiveFive = 55
End Function

So in the Immediate window you could type:

Debug.print fiveFive

And 55 would be returned.

hth
Chris
 
Last edited:

ChrisO

Registered User.
Local time
Today, 22:21
Joined
Apr 30, 2003
Messages
3,202
Objects can be passed via OpenArgs, but not directly…

When we have a line of code like:-
Private ObjectCUR As New clsObjectCUR
then clsObjectCUR is a pointer to the Class not the actual Class.

Similarly, when we have something like:-
Me
then Me is a pointer to the Form not the actual Form.

And it also applies to other Objects like Text Boxes, List Boxes, Charts and so forth.

So a pointer is not the actual data structure in memory but just a pointer to the data structure.

------------------------------

But OpenArgs can not accept a pointer, as such, so we must convert it to a Long data type before passing.
OpenArgs can then accept the Long data type for passing.
(Long in 32 bit systems and (probably, not tested) Long Long in 64 bit systems.)

At the receiving end of OpenArges we reverse the process; we convert the Long data type back to a pointer.

------------------------------

The two Public Functions to perform the conversion of Object pointer to Long and Long to Object pointer are:-
Code:
Option Explicit
Option Compare Text


Private Const POINTERSIZE As Long = 4
Private Const ZEROPOINTER As Long = 0

Private Declare Sub RtlMoveMemory Lib "kernel32" (ByRef Destination As Any, _
                                                  ByRef Source As Any, _
                                                  ByVal Length As Long)


Public Function GetPointer(ByRef objThisObject As Object) As Long
    Dim lngThisPointer As Long

    RtlMoveMemory lngThisPointer, objThisObject, POINTERSIZE
    GetPointer = lngThisPointer

End Function


Public Function GetObject(ByVal lngThisPointer As Long) As Object
    Dim objThisObject As Object

    RtlMoveMemory objThisObject, lngThisPointer, POINTERSIZE
    Set GetObject = objThisObject
    RtlMoveMemory objThisObject, ZEROPOINTER, POINTERSIZE

    Set objThisObject = Nothing

End Function
They both work by transferring the 32 bit Object pointer value to a Long value and transferring the Long value back to an Object pointer value.

------------------------------

Now when the value of the Object pointer is received at the destination it can be handled it two ways.

Let’s assume we pass a Form pointer to another Form by passing Me as OpenArgs.

The first method to receive the pointer would be something like this:-
Code:
Option Explicit
Option Compare Text


Private Sub Form_Load()
    
    If Len(Me.OpenArgs) Then
        [color=green]' Change the Caption of the calling Form.[/color]
        GetObject(Me.OpenArgs).Caption = "Caption set to 'Sam' by frmTest"
    End If

End Sub

The second method to receive the pointer would be something like this:-
Code:
Option Explicit
Option Compare Text


Private frm As Access.Form


Private Sub Form_Load()
    
    If Len(Me.OpenArgs) Then
        [color=green]' Get the passed Object.[/color]
        Set frm = GetObject(Me.OpenArgs)
        
        [color=green]' Change the Caption of the calling Form.[/color]
        frm.Caption = "Caption set to 'Sam' by frmTest"
    End If

End Sub

In the first and second methods both do the same thing, they both set the Caption of the calling Form.

However, in the first method the type of Object is not defined and the code is slightly smaller.
In the second method the type of Object is defined as Access.Form and is slightly longer.

But the second method has a distinct advantage; Access ‘knows’ the pointer is to a Form object and therefore has Intellisense.

------------------------------

Note:
This is true Object pointer passing (ByRef).
Any assignment to the passed Object reflects directly on the data structure pointed to by the pointer.
Any error during the assignment to the passed Object is raised immediately at assignment time.
The data has already been ‘sent’ to the data structure; there is no need to pass back anything.

------------------------------

Conclusion:
1. We can pass Objects via OpenArgs but we need to pass the value of the pointer to the Objects.
2. If the received value of the pointer is assigned to the same object type we also get Intellisense.
3. The method is:
a. Convert the Object pointer value to a Long data type.
b. Pass the Long.
c. Convert the Long data type value to an Object pointer.
d. Optionally, assign the Object pointer to a known Object to get Intellisense for that Object.

Access 2003 demo attached.

This might raise some eyebrows…

Chris.
 

Attachments

  • PassObjectViaOpenArgs.zip
    24 KB · Views: 299

mdlueck

Sr. Application Developer
Local time
Today, 08:21
Joined
Jun 23, 2011
Messages
2,631
I realized last evening that Modules are within "main()'s" scope, thus the place to stash functions and subroutines which are global to the application. :D

Code:
Option Compare Database
Option Explicit
 
Public ObjectCUR As New clsObjectCUR
 
Public Function Main_getObjectCUR() As ObjectCUR
  Main_getObjectCUR = ObjectCUR
End Function
 
Public Sub Main_uninitObjectCUR()
  Set ObjectCUR = Nothing
End Sub

However compiling this I get:
Compile error: User-defined type not defined

So if I use a module to provide global visibility to this class object, then how do I return to the form code the shared instance of my class?
 

mdlueck

Sr. Application Developer
Local time
Today, 08:21
Joined
Jun 23, 2011
Messages
2,631
Seems I was very close, this works:

Code:
Public Function Main_getObjectCUR() As Object
  Main_getObjectCUR = ObjectCUR
End Function

Evidently VBA does not really want to know exactly what type of object it will be. Off to see if I can successfully retrieve my ObjectCUR instance in forms further into this application and always retain the attributes of the one shared instance.
 

mdlueck

Sr. Application Developer
Local time
Today, 08:21
Joined
Jun 23, 2011
Messages
2,631
OK, I am having troubles receiving the shared object in the first form. I get the error:

"Object Variable or with block variable not set"

Code:
Option Compare Database
Option Explicit
 
Private ObjectCUR As clsObjectCUR
 
Private Sub Form_Load()
  On Error GoTo Err_Form_Load
 
  'Fetch shared instance of ObjectCUR object
  ObjectCUR = Main_getObjectCUR()
 
Exit_Form_Load:
  Exit Sub
 
Err_Form_Load:
  MsgBox Err.Description
  Resume Exit_Form_Load
 
End Sub

I have tried Private / Public / commenting out the form declaration of the ObjectCUR object.

The clode blows up when the module code returns the shared instance of ObjectCUR. "So close..." Thank you!
 

stopher

AWF VIP
Local time
Today, 13:21
Joined
Feb 1, 2006
Messages
2,395
Does the attached example help?
 

Attachments

  • globalObject.zip
    25.3 KB · Views: 481

mdlueck

Sr. Application Developer
Local time
Today, 08:21
Joined
Jun 23, 2011
Messages
2,631
Thank you, Chris! Seems I was thinking things had to be more complicated than they actually need to be. Nice when some things just work... easilly! :D

My code is now working much better. Off to testing the various forms to see if now they work properly.
 

stopher

AWF VIP
Local time
Today, 13:21
Joined
Feb 1, 2006
Messages
2,395
Thank you, Chris! Seems I was thinking things had to be more complicated than they actually need to be. Nice when some things just work... easilly! :D

My code is now working much better. Off to testing the various forms to see if now they work properly.
Good to hear you've got it sorted.
 

stopher

AWF VIP
Local time
Today, 13:21
Joined
Feb 1, 2006
Messages
2,395
Conclusion:
1. We can pass Objects via OpenArgs but we need to pass the value of the pointer to the Objects.
2. If the received value of the pointer is assigned to the same object type we also get Intellisense.
3. The method is:
a. Convert the Object pointer value to a Long data type.
b. Pass the Long.
c. Convert the Long data type value to an Object pointer.
d. Optionally, assign the Object pointer to a known Object to get Intellisense for that Object.
Nice trick :)
 

Users who are viewing this thread

Top Bottom