The message class - RAISING events (3 Viewers)

jwcolby54

Member
Local time
Today, 12:02
Joined
May 19, 2025
Messages
128
I read with interest a thread which described the machinations that folks went through to do something that is simple with RaiseEvent.
The OP wanted a form with a ton of buttons on it. She created a clsBtn (good job) which the form used to encapsulate each buttons. As many buttons as the OP wanted. Saved into a collection. Good Job.

BUT... she wanted the button click to do something back on the form. Hmmmm... without a variable dimmed Withevents, there is no direct way to do this. But it can be done with a very useful singleton message class. Get a pointer to this clsMsgPD and store it in the class header Withevents... and the magic begins.

This is mostly just aircode, written in the middle of the night as my daughter slept. But I will work with anyone who wishes to actually try this.

First the clsCmd. Store this in a class named clsCmd. I acttually wrote this class and it compiles.

Option Compare Database
Option Explicit

Private WithEvents mCmd As CommandButton 'Holds a pointer to the command button

Private Sub Class_Terminate()
Set mCmd = Nothing 'Clean up behind our Self
End Sub

Public Function fInit(lCmd As CommandButton) 'Get a pointer to a command button
Set lCmd = mCmd 'and store it in the header of this class
End Function

'
'Now sink the click event
'
Private Sub mCmd_Click()
'
'call the message class to send a message
'Say who we are in the first parameter
'And pass a pointer to the actual command button in the last parameter
'
clsMsgPD.Send mCmd.Name, "", "", mCmd
End Sub

Let's create a simple clsMsgPD (PreDeclared). It doesn't do much except raise an event to send a message. Suppose we create a singleton clsMsgPD.
I have used clsMsgPD for years. It functions as described.

Place the following in a new class and save it as clsMsgPD. Export it to a text file.
Change 'Attribute VB_PredeclaredId = True
Import back into the database.

Option Compare Database
Option Explicit
'
'clsMsgPD is a "PreDeclared" class which does not have to be instantiated.
'This has been done by exporting clsMsg, editing it in an editor setting
'and pulling it back in.

'Attribute VB_PredeclaredId = True
'
'And then importing the class back into Access.
'I then renamed clsMsg1 to clsMsgPD.
'
Public Event Message(varFrom As Variant, varTo As Variant, _
varSubj As Variant, varMsg As Variant)
Public Event MessageSimple(varMsg As Variant)

Function Send(varFrom As Variant, varTo As Variant, _
varSubj As Variant, varMsg As Variant)
RaiseEvent Message(varFrom, varTo, varSubj, varMsg)
' Debug.Print "From: " & varFrom & vbCrLf & "To: " & varTo & vbCrLf & "Subj: " & varSubj & vbCrLf & "Msg: " & varMsg
End Function

Function SendSimple(varMsg As Variant)
RaiseEvent MessageSimple(varMsg)
' Debug.Print varMsg
End Function

Now anybody from anywhere can raise a message. The key here is that RaiseEvent can pass variants. And variants can contain anything including pointers to objects such as a button. So the varMsg parameter can be the actual button if so desired. OR it can simply be the name of the button. OR it can be the back color of the button. You get the idea.

In a new form place a text box on the form called txtSomeMsg. And BUNCH of buttons named Cmd1, Cmd2, Cmd3 etc.

Now in the header of the form with all these buttons on it:

'Get an instance of that singleton or "predeclared" class
Dim Withevents mClsMsgPD as clsMsgPD.

'And a collection to hold all the clsMsg instances
Dim mcolClsButtons as collection

Private Sub Form_Open(Cancel As Integer)
Set mcolClsButtons = new Collection
Set mClsMsgPD = new clsMsgPD
lclsCmdFactory Cmd1
lclsCmdFactory Cmd2
lclsCmdFactory Cmd3
'... as many times as you want
End Sub
'
'NEVER forget to clean up behind ourselves
'
Private Sub Form_Close()
Set mClsMsgPD = Nothing
Set mcolClsButtons = Nothing
End Sub
'
'this is a factory for creating instances of clsCmd and storing them in the collection
'
function clsCmdFactory(lCmd As CommandButton )
dim lclsCmd as clsCmd
Set lclsCmd = new clsCmd
lclsCmd.fInit(lCmd)
'
'And store the new clsCmd instance in the collection
'
mcolClsButtons.add lclsCmd
end function

'
'This is the event sink for the message class which the form is listening to
'In here we will expect the clsButton to send itself (the actual button object)
'as the varMsg parameter.
'
Private Sub mClsMsgPD_Message(varFrom As Variant, varTo As Variant, varSubj As Variant, varMsg As Variant)
dim lBtn as CommandButton

'varMsg will be a pointer to a button which is sending a message
Set lBtn = varMsg 'This code coerces the variant message into a command button

txtSomeMsg = lBtn.Name & " is calling back to the form which contains me."

End Sub
 
I had a client that wanted a bound form with a bunch of smallish text boxes which could contain paragraphs or even pages of text. The user couldn't actually see more than a couple of lines of whatever was in the text box. This is a problem...

I created an unbound form frmDataEntry with a huge text box on it. It listened to the message class described in the post above.

The form with all of the smallish text boxes used a clsTxt to wrap each instance of the little text boxes and clsTxt listened for the dblclick event. The clsTxt would open the frmDataEntry and call clsMsgPD, passing a pointer to the text box where the data should eventually be stored.

frmDataEntry listened to clsMsgPD.

In the frmDataEntry, as the form opened, it would grab the pointer to the text box and store it in a variable in the header of the form. It would grab any existing text already in the text box passed in and place it into the huge text box so the user could see and edit the data in the text box passed in. The user could then edit the data to his / her heart's content. When finished, the user would simply close the form. The form closing would take the text in the huge data entry text box and place it into the text box passed in. When the user found themselves back in the original form, all the data entered into the data entry form would be neatly placed in the text box that they had double clicked in.

A somewhat difficult problem easily handled with events.

See my book on the subject being discussed elsewhere in the forums.
 
The demo for Long Data Entry is working in EventDrivenProgrammingDemoDB. My blog is in my signature. Download both the EventDrivenProgrammingDemoDB.ACCDB as well as the EventDrivenProgrammingInVBA.PDF.

Place the demodb in a directory specially built for EventDrivenProgrammingInVBA. <Grin> Or anywhere that you allow code to run.

Open the demodb and then open frmTestEditField. You should see this:

1748393448683.png
 
Now edit the text in the text box with the label Enter Data Below. You can edit in any way you see fit. The field behind the first form uses long text data types which means that you can put very long stuff in there if you desire.
When you close the form, whatever you edited in this form will appear back in the original field.
Now edit Edit Fld 2. This frmLongDataEntry will open again, now containing the text from Edit Fld 2. Edit it however you see fit. Close the form and the edits you performed for that field will appear back in the form labeled tblTestEditField.
 
I did this of course with Events. frmTestEditField contains the following in the code behind form:

Option Compare Database
Option Explicit

Private mclsFrm As clsFrm

Private Sub Form_Open(Cancel As Integer)
'
Set mclsFrm = New clsFrm
mclsFrm.fInit Me
End Sub
'
'Clean up behind ourself
'
Private Sub Form_Close()
Set mclsFrm = Nothing
End Sub

Notice that there are no text box events in this form's code behind form The only thing happening here is that the form loads clsFrm and keeps a pointer to it.
 
The code behind form for frmLongDataEntry is as follows:

Private WithEvents mclsMsgPD As clsMsgPD
Private mtxtPassedIn As TextBox
Private mtxtDataEntry As TextBox

Private Sub Form_Open(Cancel As Integer)
Set mtxtPassedIn = txtPassedIn
Set mtxtDataEntry = txtDataEntry
'
'Clear out a--ny text from before
'
mtxtDataEntry = ""
'
'Get a pointer to clsMsgPD so we can sink it's events
'
Set mclsMsgPD = clsMsgPD
End Sub

Private Sub Form_Close()
'
'When the form closes, place the edited text from txtDataEntry
'back into the text that was passed in.
'
mtxtPassedIn = txtDataEntry
'clean up behind ourself
Set mclsMsgPD = Nothing
Set mtxtPassedIn = Nothing
Set mtxtDataEntry = Nothing
End Sub

'
'If VarTo is addressed to this form then
'we are expecting the txtDataEntry to be a text box
'so coerce txtDataEntry into a text box by saving it in mtxtPassedIn
'
Private Sub mclsMsgPD_Message(varFrom As Variant, varTo As Variant, varSubj As Variant, varMsg As Variant)
'
'Check to see if the message is intended for this form
'
If varTo = "frmLongDataEntry" Then
'
'If so then coerce varMsg into a text box control.
'It BETTER BE a text box control or this will fail
'
Set mtxtPassedIn = varMsg
'
'Grab the contents of the text box passed in and put it into txtDataEntry
'
mtxtDataEntry.Text = mtxtPassedIn.Text
txtPassedIn = mtxtPassedIn.Text

'
'When we do this the user suddenly sees the contents from the control back on the form
'contained in txtDataEntry on this form.
'They start to edit it.
'
End If
End Sub

'############################
 
Nothing magic here.

Private WithEvents mclsMsgPD As clsMsgPD is a pointer to a message class - dimmed Withevents - which simply means that it can SINK the events from clsMsgPD.

The following code sinks the mclsMsgPD_Message event raised by the clsMsgPD It checks the varTo parameter to make sure the message is directed at this form. If so then it takes varMsg and sets it into a text box variable mTxtPassedIn which we defined in the header. BTW this is called coercion meaning that the varMsg is a variant, and we are placing that into a text control variable. We "Coerce" the variant into a type textbox. The cool thing about clsMsgPD is that all of the parameters can contain anything. Variants can contain a string, a double, a recordset, a form, or in this case a textbox. Anything at all AFAICT can go into a variant.

Finally, once we have a pointer to the text box passed in to us in varMsg, we can get the text value out of it and place it into the big text box so that you can edit it.

Code:
'
'If VarTo is addressed to this form then
'we are expecting the txtDataEntry to be a text box
'so coerce txtDataEntry into a text box by saving it in mtxtPassedIn
'
Private Sub mclsMsgPD_Message(varFrom As Variant, varTo As Variant, varSubj As Variant, varMsg As Variant)
    '
    'Check to see if the message is intended for this form
    '
    If varTo = "frmLongDataEntry" Then
        '
        'If so then coerce varMsg into a text box control.
        'It BETTER BE a text box control or this will fail
        '
        Set mtxtPassedIn = varMsg
        '
        'Grab the contents of the text box passed in and put it into txtDataEntry
        '
        mtxtDataEntry.Text = mtxtPassedIn.Text
        txtPassedIn = mtxtPassedIn.Text
       
        '
        'When we do this the user suddenly sees the contents from the control back on the form
        'contained in txtDataEntry on this form.
        'They start to edit it.
        '
    End If
End Sub[END CODE]
 
Last edited:
And finally, when the form closes, we need to copy the edited data back into the control passed in. And cleanup behind ourself.

Private Sub Form_Close()
'
'When the form closes, place the edited text from txtDataEntry
'back into the text box that was passed in.
'
mtxtPassedIn = txtDataEntry
'clean up behind ourself
Set mclsMsgPD = Nothing
Set mtxtPassedIn = Nothing
Set mtxtDataEntry = Nothing
End Sub
 
Study clsMsgPD, frmTestEditField and frmLongDataEntry to watch it all happen.

Oh yea, and clsCtlText. TheDblClick event in there launches frmLongDataEntry. Place a breakpoint in there and step through the code to really understand this. It isn't hard, but watching the events is critical to understanding this event programming stuff.

'
'Double click will open an edit form for long text entry
'
Private Sub mctlTxt_DblClick(Cancel As Integer)
On Error GoTo mctlTxt_DblClick_Error
'
'Set up a naming convention for this text control wrapper to recognize
'The text box name has to start with txtDE
'for "text data entry"
'
'This allows the developer to create a user interface to
'edit long text fields easily and consistently
'
If Left$(mctlTxt.Name, 5) = "txtDE" Then
'
'If it starts with that "txtDE" then
'Open the data entry form
'
DoCmd.OpenForm "frmLongDataEntry"
'
'Send a message to the form, passing in this text box control
'Pass in "frmDataEntry" as the to: in the message
'and mctlTxt in the Msg:
'
clsMsgPD.Send "", "frmLongDataEntry", "", mctlTxt
'
End If


Exit_mctlTxt_DblClick:
On Error GoTo 0
Exit Sub

mctlTxt_DblClick_Error:
Dim strErrMsg As String
Select Case Err
Case 0 'insert Errors you wish to ignore here
Resume Next
Case Else 'All other errors will trap
strErrMsg = "Error " & Err.Number & " (" & Err.Description & ") in procedure EDPDemoDB.clsCtlTxt.mctlTxt_DblClick, line " & Erl & "."
Beep
#If boolELE = 1 Then
WriteErrorLog strErrMsg
#End If
assDebugPrint strErrMsg
Resume Exit_mctlTxt_DblClick
End Select
Resume Exit_mctlTxt_DblClick
Resume 0 'FOR TROUBLESHOOTING
End Sub
 
The real point is that by creating something like clsMsgPD and clsCtlTxt, I can now have this functionality in any form I want in my app. I can have it in several forms. Or every form. All I have to do is name my txtbox to start with txtDE and it "inherits" the behavior to open frmLongDataEntry on the dblclick. And yes I know this is not inheritance, but I for one find it useful to do stuff like this.

It must be bed time now!

BTW I am going fishing with my daughter tomorrow. We are camping in Bandit's Roost national campground and it has been cold and rainy for the last two days. Tomorrow we get a break from the weather. And so we will go feed worms to the fishies. We are supposed to catch the fishies but mostly they just steal the worms.

OTOH if we actually caught a fish we would have to do something with it.:eek:(n):cool:
 
Last edited:
Ok, branching off on my own at the moment to see if I undertsand all of this.

In clsCtlCmd, why is fInit Public?

Code:
Option Compare Database
Option Explicit
'
Private WithEvents mCmd As CommandButton

Private Sub Class_Terminate()
    Set mCmd = Nothing
End Sub

Public Function fInit(lCmd As CommandButton)
    Set lCmd = mCmd
End Function

Private Sub mCmd_Click()
    clsMsgPD.Send mCmd.Name, "", "", mCmd
End Sub
 
The keywords Public and Private are called scope. Public means that it can be seen by entities outside of the class, Private means that it cannot be seen outside of the class. Public is the default, IOW if you don't specifically say private, then it is public. At least that is true for methods and subs. I believe that for variables in the header of a class, these are private unless specifically tagged as public.

To your question specifically, this is just another case of me being inconsistent. If I had left off the Public, the function would have been Public anyway.

The things such as Private Sub Class_Terminate() are declared private by the vba editor which normally creates these statements for us. However we can, if we so desire, modify the scope to public. If we wanted one of these to be public we would have to come back after the editor created these and change the keyword Private to Public, at which point that event sink sub would be visible from outside of the class.

However these event sinks are generally not of interest to entities outside of the class.

function syntax

1748433391811.png



1748433437159.png
 
Last edited:
The only scope modifiers used by most of us devs is public and private. For functions, Public is the default unless private is specified. Static and friend are very special case. The friend modifier is another scope keyword. The static keyword discusses whether the values of variables inside of the function are kept between calls to the function. As such it has nothing to do with scope of the function and can (probably, I haven't tried) be used in a manner such as:

Function Static Private MyFunctionName()
Dim MyVar as string
end function

This function would be private to the class, not accessible from outside of the class AND would keep the value for MyVar when the function exits, and whatever that value was when the function exits, the value would still be there when the function is called the next time.

Clear as mud?
 
@jwcolby54

Please use code tags ( </> button on the text editor toolbar ) when posting huge great gobs of code - it makes it much easier for everyone to read and understand. Thanks. (y)
 
@jwcolby54

Please use code tags ( </> button on the text editor toolbar ) when posting huge great gobs of code - it makes it much easier for everyone to read and understand. Thanks. (y)
Sorry, I didn't even know that existed. BTW it is a bit of a PITA as it inserts a blank line between every line of code. Can that be overridden somehow?
And wouldn't ya know, VB / VBA is not even included as a language type. Damned snobs!
 
Shouldn't do. It does that over on UA sometimes.

You can also just enter [ c o d e ] and [ / c o d e ] (without the spaces and your code in between.
 
Can that be overridden somehow?
Are you pasting code from Word/LibreOffice Writer?

If so, it may have extra hidden characters for end of line? Do you get the same if you paste in to a proper text editor?

Really, all code should be kept as far away as possible from word processors!

One good trick, however, is to use Ctrl+Shift+V to paste (instead of simple Ctrl+V), or select 'Paste as plain text' from the right-click shortcut menu.
 

Users who are viewing this thread

Back
Top Bottom