access 2003 on windows 10 - RaiseEvent seems not to work - vba

A very simple mdb: form1 has just a button Command0, form2 has just a button Command0.
The button on form1 loads form2.
The button on form2 raises event "doit".
This event never gets triggered.
Why?
This is all the code there is in the form:
FORM1
Option Compare Database
Private WithEvents msg As Form
Private Sub Command0_Click()
DoCmd.OpenForm "form2"
Set msg = Forms("form2")
End Sub
Sub msg_doit()
Stop
End Sub
FORM2
Option Compare Database
Public Event doit()
Private Sub Command0_Click()
RaiseEvent doit
End Sub

You need to use the proper (specific) interface that contains your event.
For forms, the Access.Form interface is the general one (for all forms) and only contains the built-in methods and events, the Access.Form_MyFormName is the specific interface that contains all public methods and events you declared as well.
The only thing you need to change is:
Private WithEvents msg As Form_form2
Then it should just work.

Related

Changing "Private Sub Procedure1" in Form1 to "Public -" (Form1) from Form2

In Form1 Procedure1 is private. I need to change declaration in public from Form2 with VBA.
In Form2 I'm looking for a command like that:
Private Sub Procedure2_Click ()
Form_Form1.Declaration Public
End Sub
The previous code will change From1 from
Private Sub Procedure1 ()
blablabla
End Sub
To
Public Sub Procedure1 ()
blablabla
End Sub
much appreciated
You can't do such a thing. The access modifiers (Private/Public) are static. When you try to call a Private Sub from another object, the code cannot be complied and thus cannot be run. Since such a code cannot be run, there is no way to create a Sub that would change a Private Sub to a Public Sub.
Well, unless you are writing a tool as an add-in that you would call at design time. I once wrote a code formatter like this. But this code cannot be run at the run-time of your application.

MS Access VBA - Need help referring to a control on a multi-instance form, from another form

I have multiple instances of a form. I would like to click a button on Form1 (multi-instance) to open Form2 (single instance). I will then click a button on Form2 that should run some code that ends with requerying a control back on Form1. I am running into issues trying to reference the control on that form. From what I've researched, this can be accomplished by passing a for reference from Form1 to Form2 so that when the code runs, Form2 knows which control to requery.
If some kind soul with a better understanding of this scenario could elaborate on the thread below, I think it is exactly what I need:
Access 2007 / VBA - Multiple Instances of Form, Update controls on specific instance from Module
You could use the handle of the window:
In Form1:
Private Sub ButtonForm2_Click()
' Pass the handle of the current instance of Form1 to Form2.
DoCmd.OpenForm "Form2", , , , , , Me.Hwnd
End Sub
In Form2:
Private Sub ButtonForm1_Click()
Dim Form1 As Form
Dim Handle As Long
Handle = Nz(Me.OpenArgs, 0)
' Find the calling instance of Form1.
For Each Form1 In Forms
If Form1.Hwnd = Handle Then
Exit For
End If
Next
If Not Form1 Is Nothing Then
' Do something.
Form1!Textbox1.Value = Now
End If
DoCmd.Close acForm, Me.Name
End Sub

Access not firing custom event

I've been building testable MVC logic for my Access database using RubberDuck's answer to Best way to test a MS Access Application? but I'm stuck with the custom event handling. I can't figure out why the OnCreate event isn't firing.
Form_CreateStudents:
Option Compare Database
Private ctrl As ctrCreateStudent
Public Event OnCreate()
Private Sub btnCreate_Click()
Set ctrl = New ctrCreateStudent
ctrl.Run
RaiseEvent OnCreate
End Sub
Class module ctrCreateStudent:
Private WithEvents frm As [Form_Create Students]
Public Sub Run()
MsgBox "run called"
Set frm = New [Form_Create Students]
End Sub
Public Sub frm_OnCreate()
MsgBox "frm_oncreate event called"
End Sub
Run is being called, but frm_OnCreate is just ignored. I'm relatively new to VBA, what am I missing here?
Quite simple:
frm is a New [Form_Create Students], not the one calling it.
This new form doesn't raise the OnCreate event. In fact, this new form is not even visible, because you haven't set frm.Visible = True
If you want to set it to the form that just called Run, pass it:
On the form:
Private ctrl As ctrCreateStudent
Public Event OnCreate()
Private Sub btnCreate_Click()
Set ctrl = New ctrCreateStudent
ctrl.Run Me
RaiseEvent OnCreate
End Sub
On the class:
Private WithEvents frm As [Form_Create Students]
Public Sub Run(parentForm As [Form_Create Students])
MsgBox "run called"
Set frm = parentForm
End Sub
Public Sub frm_OnCreate()
MsgBox "frm_oncreate event called"
End Sub
A strong warning, though: this code contains a reference loop, and thus a memory leak.
The form has a reference to the class, and the class has a reference to the form, so neither will ever get destroyed. Every time you close and open the form, a new form and class object will get created, and none of them will ever get destroyed.
When closing the form, it turns invisible and looks gone, but it's still there and using memory.
There are many ways to work around this, but an easy one is:
In the class:
Public Sub frm_Close()
Set frm = Nothing 'Release form object, break reference loop
End Sub
And make sure the Form's On Close property is set to "[Event Procedure]" so the close event gets raised.

Monitor class events from userform

I have a userform which assembles itself at runtime, by looking in a folder and extracting all the pictures from it into image-controls on my form. What makes the process a little more complex is that I'm also using the image-controls' events to run some code.
As a simplified example - I have a form which creates a picture at runtime, the picture has an on-click event to clear its contents. To do this I have a custom class to represent the image object
In a blank userform called "imgForm"
Dim oneImg As New clsImg 'our custom class
Private Sub UserForm_Initialize()
Set oneImg.myPic = Me.Controls.Add("Forms.Image.1") 'set some property of the class
oneImg.Init 'run some setup macro of the class
End Sub
In a class module called "clsImg"
Public WithEvents myPic As MSForms.Image
Public Sub Init() 'can't put in Class_Initialise as it is called before the set statement - so myPic is still empty at that point
myPic.Picture = LoadPicture(path/image)
End Sub
Public Sub myPic_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
onePic.Picture = Nothing
End Sub
The problem is, this doesn't display the changes, and I realised I needed a imgForm.Repaint in there somewhere - the question is, where?
Attempts
First option is to put it in the Init() sub of clsImg. (ie. have a line imgForm.Repaint at the end of the click event) That works, but not ideal as the class can then only be used with the userform of the correct name.
A 2nd idea was to pass the userform as an argument to Init()
Public Sub Init(uf As UserForm) 'can't put in Class_Initialise as it is called before the set statement - so myPic is still empty at that point
myPic.Picture = LoadPicture(path/image)
uf.Repaint
End Sub
And called with
oneImg.Init Me
That works too, but would mean that wherever I require a repaint, I would have to pass the parameter which is also not ideal - the code is in reality a lot more complex than is shown here, so I don't want to have to add in this extra parameter unless necessary
The third option which I'm currently using is to pass the userform object to the class and save it there.
So with a Public myForm As UserForm at the top of my class module, I can pass the userform with the Init(uf As UserForm) and have a
Set myForm = uf 'Works with a private "myForm"/ class Property
Or I can set it directly from the userform code with a
Set clsImg.myForm = Me 'only if "myForm" is Public
But what does this do for memory - does saving the userform as a variable in my class take up a lot of memory? Bear in mind that in my real code I declare an array of clsImgs that can be of the order of >100 instances so I don't really want to be making copies of the UF in each class if that's what this method does. Also, it's ugly
What I really want...
... is a way of telling the userform that it needs to repaint, rather than directly repainting from within the class. To me this says I need an event to occur in my class, which the userform hears with some custom event handler. Exactly how Worksheet_Change works, the sheet object raises a change event, the sheet class code handles it.
Is such a thing possible (I suppose I would have to declare clsImg WithEvents - can you do that for an array?), or is there a better alternative. I'm looking for a method which does not impede performance with a large number of classes declared, as well as one which is portable and easily readable. This is my first use of Classes so I may be missing something really obvious!
Since good practice is that classes are self-contained (as you obviously know) the clsImg should indeed not have to be aware of the UserForm and thus shouldn't tell the UserForm to repaint.
What this calls for, is indeed that the clsImg raises an event that the UserForm hooks into, so it repaints based on that event, or, in your own words: "a way of telling the userform that it needs to repaint."
I replicated your Custom Class (clsImg) as follows (wanted to use a proper Setter / Getter, functionality doesn't really change)
clsImg Code:
Private WithEvents myPic As MSForms.Image 'Because we need the click event.
Public Event NeedToRepaint() 'Because we need to raise an event that the UserForm can hook into.
Public Property Let picture(value As MSForms.Image)
Set myPic = value
End Property
Public Property Get picture() As MSForms.Image
Set picture = myPic
End Property
Public Sub myPic_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
myPic.picture = Nothing
RaiseEvent NeedToRepaint
End Sub
Next, in the UserForm we hook into this NeedToRepaint Event that's raised during the Event Handler of the MouseDown of the picture.
UserForm1 Code:
Private WithEvents oneImg As clsImg 'Our custom class
Private Sub oneImg_NeedToRepaint() 'Handling the event of our custom class
Me.Repaint
End Sub
Private Sub UserForm_Initialize()
Dim tmpCtrl As MSForms.Image
Set oneImg = New clsImg
Set tmpCtrl = Me.Controls.Add("Forms.Image.1")
tmpCtrl.picture = LoadPicture("C:\Path\image.jpg")
oneImg.picture = tmpCtrl
End Sub
The second part of your question is whether you can use this in an array.
The short answer is "no" - Each object would have to have it's own Event Handler. However, there are ways to work around this limitation by using a Collection or some similar approach. Still, this wrapper will have to be "UserForm aware" since that's where you'll be repainting. The approach would be something like in this article
EDIT: A solution / workaround for not being able to use an Array:
Since I really liked this question - Here's another approach.
We can apply somewhat of a PubSub pattern as follows:
I did a quick build for CommandButtons, but no reason that it can not be made for other classes of course.
Publisher class:
Public Event ButtonClicked(value As cButton)
Public Sub RegisterButtonClickEvent(value As cButton)
RaiseEvent ButtonClicked(value)
End Sub
'Add any other events + RegisterSubs.
In a regular class, I setup a factory routine to keep this specific Publisher a singleton (as in: It will always be the very same in memory object that you're pointing at):
Private pub As Publisher
Public Function GetPublisher() As Publisher
If pub Is Nothing Then
Set pub = New Publisher
End If
Set GetPublisher = pub
End Function
Next, we have the UserForm (I just made one with 4 buttons) and the button class to utilize this Publisher. The Userform will just subscribe to the event it raises:
Userform code:
Private WithEvents pPub As Publisher 'Use the Publishers events.
Private button() As cButton 'Custom button array
Private Sub pPub_ButtonClicked(value As cButton) 'Hook into Published event.
MsgBox value.button.Caption
End Sub
Private Sub UserForm_Initialize()
Set pPub = GetPublisher 'Private publisher for getting it's event. Will be always the same object as long as you use "GetPublisher"
Dim i As Integer
Dim btn As MSForms.CommandButton
'Create an array of the buttons:
i = -1
For Each btn In Me.Controls
i = i + 1
ReDim Preserve button(0 To i)
Set button(i) = New cButton
button(i).button = btn
Next btn
End Sub
Last we have the cButton class, that centralizes the button events (through the array). Instead of handling each event individually, we just tell the publisher that an Event has been raised.:
Private WithEvents btn As MSForms.CommandButton
Private pPub As Publisher
Public Event btnClicked()
Private Sub btn_Click()
pPub.RegisterButtonClickEvent Me 'Pass the events to the publisher.
End Sub
Public Property Let button(value As MSForms.CommandButton)
Set btn = value
End Property
Public Property Get button() As MSForms.CommandButton
Set button = btn
End Property
Private Sub Class_Initialize()
Set pPub = GetPublisher
End Sub
With this approach we have one "Publisher" that can handle any event from specific classes that register the right event with it. You could also add image events, workbook events, etc.
The publisher itself raises the events we need based on what gets passed to it.
This way the UserForm can be agnostic of the button class and vice versa.
Based on what is supported in VBA, I'm quite confident this is the cleanest approach for your scenario. If anyone has a better idea, I'd love to see another answer.
I did the following, If you pass the control as a control, you can use the parent.
In my form
Public c As Collection
Private Sub UserForm_Initialize()
Dim ctl As Control
Dim cls As clsCustomImage
Set c = New Collection
For Each ctl In Me.Controls
If TypeName(ctl) = "Image" Then
Set cls = New clsCustomImage
cls.init ctl
c.Add cls, CStr(c.Count)
End If
Next ctl
End Sub
and in my class, clsCustomImage
Private WithEvents i As MSForms.Image
Private frm As UserForm
public event evtRepaint
Public Sub init(c As control)
Set frm = c.parent
Set i = c
End Sub
Private Sub Class_Initialize()
End Sub
Private Sub Class_Terminate()
Set frm = Nothing
Set i = Nothing
End Sub
'
Private Sub i_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
i.Picture = Nothing
frm.Repaint
raiseevent evtRepaint
End Sub
EDIT
To have a single handler, you'd need to look at something along these lines, in a class called say clsHoldAndHandle
Private c As Collection
Private f As UserForm
Private WithEvents cls As clsCustomImage
Public Sub AddControl(ctl As Control)
Set cls = new clsCustomImage
If f Is Nothing Then Set f = ctl.Parent
cls.init ctl
c.Add cls, CStr(c.Count)
End Sub
Private Sub Class_Initialize()
Set c = New Collection
End Sub
Private Sub cls_evtRepaint()
f.Repaint
End Sub

How to declare custom event in userform using vba

I have userform which collects some user input. Now what I'm trying to do, is to declare some event to throw from userform when OK button is clicked. I'm new to vba so I don't know how to do it. Any code or link to tutorial would be greatly appreciated.
Load UserForm1
UserForm1.Show
//here I want to capture UserForm1 OK button's click event and read the data
In child-form declare event and raise it at the certain moment:
Public Event clickOnChild(ByVal inputText As String)
RaiseEvent clickOnChild(Me.TextBox1.Value)
In a custom class module, worksheet class module or other user form you can catch the event. However you can't catch event in standard module because WithEvents variable are valid in object module only. To catch your event in e.g. other user form declare WithEvents variable of type childUserForm and
add event-handler where the event will be catched and handled:
Private WithEvents childForm As childUserForm
Private Sub childForm_clickOnChild(ByVal inputText As String)
Complete example:
Child user form:
Option Explicit
Public Event clickOnChild(ByVal inputText As String)
Private Sub CommandButton1_Click()
RaiseEvent clickOnChild(Me.TextBox1.Value)
End Sub
Parent user form:
Option Explicit
Private WithEvents childForm As childUserForm
Private Sub CommandButton1_Click()
childForm.Show
End Sub
Private Sub childForm_clickOnChild(ByVal inputText As String)
MsgBox "Input in child form was: " & inputText
End Sub
Private Sub UserForm_Initialize()
Set childForm = New childUserForm
End Sub
As I said in a comment, I don't think what you want to do is possible, but I thought of the following workarounds:
If your user input is very simple, like just entering a string, a messagbox could work:
Dim sUserInput As Variant
sUserInput = InputBox("Please enter something useful.", "Title", "Default")
Debug.Print "sUserInput=" & sUserInput
If you need the form to capture user input, making it modal and then exposing a value through a public method might work.
In the form:
Option Explicit
Private msFormString As String
Private Sub CommandButton1_Click()
msFormString = "Someone clicked on Button 1!"
'***** Note: if you use Unload Me, the string
'***** is unloaded with the form...
Me.Hide
End Sub
Public Function GetFormString() As String
GetFormString = msFormString
End Function
The calling code:
Load UserForm1
Call UserForm1.Show(vbModal)
Debug.Print "Value from UserForm1: " & UserForm1.GetFormString
Note: The function could return an object, class or array if you need to pass more data back.