How can I call UserForm_Initialize() in a Module instead of the UserForm code object?
From a module:
UserFormName.UserForm_Initialize
Just make sure that in your userform, you update the sub like so:
Public Sub UserForm_Initialize() so it can be called from outside the form.
Alternately, if the Userform hasn't been loaded:
UserFormName.Show will end up calling UserForm_Initialize because it loads the form.
IMHO the method UserForm_Initialize should remain private bacause it is event handler for Initialize event of the UserForm.
This event handler is called when new instance of the UserForm is created. In this even handler u can initialize the private members of UserForm1 class.
Example:
Standard module code:
Option Explicit
Public Sub Main()
Dim myUserForm As UserForm1
Set myUserForm = New UserForm1
myUserForm.Show
End Sub
User form code:
Option Explicit
Private m_initializationDate As Date
Private Sub UserForm_Initialize()
m_initializationDate = VBA.DateTime.Date
MsgBox "Hi from UserForm_Initialize event handler.", vbInformation
End Sub
SOLUTION
After all this time, I managed to resolve the problem.
In Module:
UserForms(Name).Userform_Initialize
This method works best to dynamically init the current UserForm
Related
I've been moving some of my code to utilize UserForms utilizing Modeless so the user can copy data from the worksheet. I finally "generalized" this answer and got it working. But, I want to avoid having to create a class for each UserForm and truth is I don't 100% understand what is going on in the code. Is there a way I can easily migrate all my UserForms to utilize the below functions of "Event Driven Modeless UserForms". Basically, I'd like to pass the UserForm as a variable and have a bunch of subs in the class, mostly general and a specific or perhaps just using If/Then to call the correct exit module after the _Closed event.
Hope that makes sense, let me know if you require further clarification.
Generalized Code:
Module Name doesn't Matter
UserForm Name = UserForm1
Class Name = Class1
Module Code:
Private UserFormNameStr As Class1
Public Sub DoStuff()
Set UserFormNameStr = New Class1
UserFormNameStr.ClassSubNameStrSubName
End Sub
Public Sub CallMeWhenUserFormClosed()
Debug.Print "Module Code Run"
End Sub
Class Code:
Private WithEvents UserFormNameStr As UserForm1
Private Sub Class_Initialize()
Set UserFormNameStr = New UserForm1
End Sub
Public Sub ClassSubNameStrSubName()
UserFormNameStr.Show vbModeless
End Sub
Private Sub UserFormNameStr_Closed()
'_Closed is required syntax
Debug.Print "Closed Event"
Call CallMeWhenUserFormClosed
End Sub
UserForm Code:
Public Event Closed()
Private Sub UserForm_Initialize()
'
End Sub
Sub OkButton_Click()
Debug.Print "Raising Events from OK Button!"
RaiseEvent Closed
Unload Me
End Sub
Private Sub CancelButton_Click()
Unload Me
End
End Sub
Update:
Perhaps I'm looking for something on Workbook_Open with a "hook"?
What's making it all work is the WithEvents declaration. Instance variables declared with the WithEvents modifier will appear in the editor's left-side dropdown.
To create an event handler procedure for an event provider, select the variable from the left dropdown, then pick an event to handle in the right-side dropdown.
Ultimately the module would look something like this, i.e. with a WithEvents declaration for each modeless form you want to handle events for:
Private WithEvents UserFormNameStr As UserForm1
Private WithEvents SomeOtherUserForm As UserForm2
Private WithEvents AnotherUserForm As UserForm3
Private Sub Class_Initialize()
Set UserFormNameStr = New UserForm1
Set SomeOtherUserForm = New UserForm2
Set AnotherUserForm = New UserForm3
End Sub
Public Sub ClassSubNameStrSubName() 'weird name, consider methods that begin with a verb
UserFormNameStr.Show vbModeless
End Sub
Public Sub ShowSomeOtherForm()
SomeOtherUserForm.Show vbModeless
End Sub
Public Sub ShowAnotherForm()
AnotherUserForm.Show vbModeless
End Sub
Private Sub UserFormNameStr_Closed() 'select "UserFormNameStr" from the left-side dropdown: NEVER hand-write event handler signatures.
Debug.Print "Closed Event (UserFormNameStr)"
CallMeWhenUserFormClosed
End Sub
Private Sub SomeOtherUserForm_Closed() 'select "SomeOtherUserForm" from the left-side dropdown: NEVER hand-write event handler signatures.
Debug.Print "Closed Event (SomeOtherUserForm)"
CallMeWhenSomeOtherUserFormClosed
End Sub
Private Sub AnotherUserForm_Closed() 'select "AnotherUserForm" from the left-side dropdown: NEVER hand-write event handler signatures.
Debug.Print "Closed Event (AnotherUserForm)"
CallMeWhenAnotherUserFormClosed
End Sub
One Handler, Many Forms?
If all _Closed handlers need to do exactly the same thing, then we can get interfaces and polymorphism involved and have one class exist in 3 instances that each do their own thing for their respective form - but VBA does not expose Public Event declarations on a class' default interface, so the paradigm is a little bit different here, and because it doesn't involve Event and WithEvents, it's arguably simpler that way, too.
Define an IHandleClosingForm interface: add a new class module to your project, but give no attention to the implementation - just a very high-level abstraction of the functionality you want (here with Rubberduck annotations):
'#ModuleDescription "An object that handles a form being closed."
'#Interface
'#Exposed
Option Explicit
'#Description "A callback invoked when a form is closed."
Public Sub Closing(ByVal Form As UserForm)
End Sub
In each form module, hold a reference to that interface, and invoke its Closing method in the form's QueryClose handler:
Option Explicit
Public CloseHandler As IHandleClosingForm
`...
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = VbQueryClose.vbFormControlMenu Then
'user clicked the [X] button, form instance is going to be destroyed!
Cancel = True 'prevents a self-destructing form instance.
Me.Hide
End If
'don't assume the caller set the CloseHandler:
If Not CloseHandler Is Nothing Then CloseHandler.Closing(Me)
'...
End Sub
Now implement that interface in the presenter class:
Option Explicit
Implements IHandleClosingForm
'...rest of the module...
Private Sub IHandleClosingForm_Closing(ByVal Form As UserForm)
'NOTE: procedure exits to the still-closing form's QueryClose handler
If TypeOf Form Is UserForm1 Then
CallMeWhenForm1Closes
Else
CallMeWhenAnyOtherFormCloses
End If
End Sub
The final step is to introduce a circular reference between the form and the presenter, by setting the public CloseHandler property before showing the form:
Set theForm.CloseHandler = Me
theForm.Show vbModeless
This will work, but then there's a memory leak and neither the form nor the presenter instance would terminate (handle Class_Terminate to find out!), and you will want to strive to avoid that (Excel will/should clean it all up when it shuts down the VBA environment though).
The solution is to untie the knot at the first opportunity, so make sure your forms' QueryClose handler sets the IHandleClosingForm reference to Nothing as soon as it is no longer useful:
'don't assume the caller set the CloseHandler:
If Not CloseHandler Is Nothing Then CloseHandler.Closing(Me)
Set CloseHandler = Nothing 'release the handler reference
The next time the form is shown and the handler is set, it's going to be on another instance of the form.
If you need the state of the form to persist between it being shown and closed, then you must separate the state from the form (and keep the state around but still properly destroy the form object), ...but that's another topic for another day :)
This question already has answers here:
VBA: Using WithEvents on UserForms
(2 answers)
Create event handlers for multiple dynamic controls
(2 answers)
Closed 4 years ago.
Untill now I have created one event for each control in my userform.
Private Sub TextBox_Integrate_Indexes_Change()
Call LabView.textBoxChange(TextBox_Integrate_Indexes)
End Sub
Private Sub TextBox_Integrate_InputFile_Change()
Call LabView.textBoxChange(Me.TextBox_Integrate_InputFile)
End Sub
Private Sub TextBox_Integrate_OutputFile_Change()
Call LabView.textBoxChange(Me.TextBox_Integrate_OutputFile)
End Sub
As seen these events all just send its object to my method which then handles the event(check if it has changed its value, and if so store the updated value in a config.json file)
However instead of making an event for all my userform textboxes, optionbuttons listboxs,checkboxes and comboxes, I wasa wandering if there is a way to detect if any event happens to that userform, get the item that triggered the event and if this one of the above type, then send itself to my method.
Yes, you can create a custom class that holds a private textbox variable.
You can then capture the event in that class and pass it to your Labview class.
In the UserForm you can just create a collection of the custom class, and set the userform's textboxes as the private variables in the custom class.
Example code:
cTextBox Class:
Private WithEvents p_TextBox As MSForms.TextBox
Public Property Let txtBox(value As MSForms.TextBox)
Set p_TextBox = value
End Property
Public Property Get txtBox() As MSForms.TextBox
Set txtBox = p_TextBox
End Property
Private Sub p_TextBox_Change()
Call Labview.textboxchange(p_TextBox)
End Sub
Labview class:
Public Sub textboxchange(val As MSForms.TextBox)
MsgBox val.Name
End Sub
Userform code:
Private t As MSForms.Control
Private ctb As cTextBox
Private cTextBoxes As Collection
Private Sub UserForm_Initialize()
Set cTextBoxes = New Collection
For Each t In Me.Controls
If TypeName(t) = "TextBox" Then
Set ctb = New cTextBox
ctb.txtBox = t
cTextBoxes.Add ctb
End If
Next t
End Sub
And a routine to test the whole thing:
Public Labview As New Labview
Sub test()
UserForm1.Show
End Sub
Where should I put Load and Unload frm1 (Userform name is frm1) and where should I put Me.Show and Me.Hide?
The (x) button within the UserForm doesn't work.
My Load and Unload is within the Active-X command button code on Sheet1:
Private Sub cmdb1_Click()
Load frm1
Unload frm1
End Sub
This way my UserForm is initialized and I can run the code
Private Sub Userform_Initialize()
'Some other code that Works...
frm1.Show
End Sub
that shows my Userform. Now I have a command button in my Userform that has the code
Private Sub cmdbClose_Click()
Me.Hide
End Sub
which I use to hide the sub, upon which the last line within cmdb1_Click() is executed and UserForm is unloaded. This Works.
However when I press the (x) button in my UserForm, the following error appears
Debugger says error lies within cmdb1_Click(). I've tried adding a sub called UserForm_QueryClose(), but the error persists. If I'd have to guess, I'd say the error is caused by the way I handle Load and Unload, thus by cmdb1_Click().
EDIT:
My problem is solved. ShowUserform and cmdbClose_Click contain the code CallumDA suggests. My command button now has:
Private Sub cmdb1_Click()
Load frm1
Call ShowUserform
End Sub
I recommend that you create an instance of your userform:
Dim MyDialog As frm1
Set MyDialog = New frm1 'This fires Userform_Initialize
You can then easily check whether the form is loaded before attempting to unload it:
If Not MyDialog Is Nothing Then
Unload MyDialog
End If
I also recommend that you don't call the Show method from the form's Initialize event. Think of your userform as just another object in Excel and manage it from your main code body.
Also, I wouldn't unload it in the cmdbClose_Click event as suggested by CallumDA (though that works fine to solve the current issue). Often you will need to be able to refer to values on your form from your main code body, and if you unload it they won't be available. Instead, keep it like you had it in the first place:
Private Sub cmdbClose_Click()
Me.Hide
End Sub
So your main code body (in your activeX button) will look something like this:
Dim MyDialog as frm1
Set MyDialog = New frm1 'This fires Userform_Initialize
'Place any code you want to execute between the Initialize and Activate events of the form here
MyDialog.Show 'This fires Userform_Activate
'When the close button is clicked, execution will resume on the next line:
SomeVariable = MyDialog.SomeControl.Value
'etc.
If Not MyDialog Is Nothing Then
Unload MyDialog
End If
You can also catch the event that fires when a user clicks the "X" on the form, and prevent the form from being unloaded:
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = VbQueryClose.vbFormControlMenu Then
Cancel = True
Me.Hide
End If
End Sub
Lastly, often you need a Cancel button on the form. The way I handle this is to create a "Cancelled" property in the form's code-behind:
Public Cancelled as Boolean
'(Note You can create additional properties to store other values from the form.)
In the Cancel button's click event:
Private Sub cmdbCancel_Click()
Me.Cancelled = True
Me.Hide
End Sub
And in the main code body:
Dim MyDialog as frm1
Set MyDialog = New frm1
MyDialog.Show
If Not MyDialog.Cancelled Then
SomeVariable = MyDialog.SomeControl.Value
SomeOtherVariable = MyDialog.SomeOtherProperty
'etc.
End If
If Not MyDialog Is Nothing Then
Unload MyDialog
End If
(I know the above is not strictly the correct way to declare a property, but this will work fine. You can make it read-only in the usual way if you wish.)
Put this in a standard module and link it up to the button you use to show the userform
Sub ShowUserform
frm1.Show
End Sub
And then in the userform
Private Sub cmdbClose_Click()
Unload Me
End Sub
I struggled for years with forms in Excel. I could never understand how an object (a form) could be destroyed but still have it's code running.
I now use this code on every form that has [OK] and [Cancel] buttons, as it allows you to control the [OK], [Cancel] and [X] being clicked by the user, in the main code:
Option Explicit
Private cancelled As Boolean
Public Property Get IsCancelled() As Boolean
IsCancelled = cancelled
End Property
Private Sub OkButton_Click()
Hide
End Sub
Private Sub CancelButton_Click()
OnCancel
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = VbQueryClose.vbFormControlMenu Then
Cancel = True
OnCancel
End If
End Sub
Private Sub OnCancel()
cancelled = True
Hide
End Sub
You can then use the form as an instance as follows:
With New frm1
.Show
If Not .IsCancelled Then
' do your stuff ...
End If
End With
or you can use it as an object (variable?) as noted above such as:
Dim MyDialog As frm1
Set MyDialog = New frm1 'This fires Userform_Initialize
Then similar instance noted above.
This is all based on an excellent article by RubberDuck which goes into more detail and explains the code given above.
I have four modules, three buttons and one userform, a progressbar, in my Excel workbook. I would like to show a progressbar during the runtime of all four modules.
Example:
I click on a button which executes the following code and makes my progressbar visible:
Private Sub GWPCClearDataButton_Click()
ProgressBar.Show
End Sub
In my userform i have the following code:
Private Sub UserForm_Activate()
GWPCClearData
End Sub
This calls one of my four modules which works fine so far.
Now here's where I'm stuck.
Of course, I would like to use the same progressbar for all modules but how can I determine in the userform code block which button was clicked and then depending on that call another module?
Example:
Private Sub GWPCClearDataButton_Click()
ProgressBar.Show
End Sub
Private Sub GSEPClearDataButton_Click()
ProgressBar.Show
End Sub
UserForm:
Private Sub UserForm_Activate()
If "BUTTON_NAME" = "GWPCClearDataButton" Then
GWPCClearData
ElseIf "BUTTON_NAME" = "GSEPClearDataButton" Then
GSEPClearData
End if
End Sub
How could I do that? I have yet to find a possibility to pass a parameter to my userform which would contain the button name.
Thanks for any help.
In your form put
Sub Start_Form(Called_From as string)
'Process Called_From as needed
End Sub
When you call your form just call the sub first like below
<form_name>.Start_Form "Button Called From"
<form_name>.Show
Basically you have to pass the variable via another sub(can be called anything) in the userform and then show the form after
User Form Code
Dim test
Private Sub UserForm_Activate()
MsgBox test
End Sub
Sub start(tt)
test = tt
End Sub
Module code
Sub t()
UserForm1.start "Hello World!"
UserForm1.Show
End Sub
I have a thousands of cells in an Excel worksheet which are ComboBoxes. The user will select one at random and populate it.
How do I get the selected ComboBox value? Is there a way to trigger a function (i.e. an event handler) when the ComboxBoxes has been selected?
You can use the below change event to which will trigger when the combobox value will change.
Private Sub ComboBox1_Change()
'your code here
End Sub
Also you can get the selected value using below
ComboBox1.Value
If you're dealing with Data Validation lists, you can use the Worksheet_Change event. Right click on the sheet with the data validation and choose View Code. Then type in this:
Private Sub Worksheet_Change(ByVal Target As Range)
MsgBox Target.Value
End Sub
If you're dealing with ActiveX comboboxes, it's a little more complicated. You need to create a custom class module to hook up the events. First, create a class module named CComboEvent and put this code in it.
Public WithEvents Cbx As MSForms.ComboBox
Private Sub Cbx_Change()
MsgBox Cbx.Value
End Sub
Next, create another class module named CComboEvents. This will hold all of our CComboEvent instances and keep them in scope. Put this code in CComboEvents.
Private mcolComboEvents As Collection
Private Sub Class_Initialize()
Set mcolComboEvents = New Collection
End Sub
Private Sub Class_Terminate()
Set mcolComboEvents = Nothing
End Sub
Public Sub Add(clsComboEvent As CComboEvent)
mcolComboEvents.Add clsComboEvent, clsComboEvent.Cbx.Name
End Sub
Finally, create a standard module (not a class module). You'll need code to put all of your comboboxes into the class modules. You might put this in an Auto_Open procedure so it happens whenever the workbook is opened, but that's up to you.
You'll need a Public variable to hold an instance of CComboEvents. Making it Public will kepp it, and all of its children, in scope. You need them in scope so that the events are triggered. In the procedure, loop through all of the comboboxes, creating a new CComboEvent instance for each one, and adding that to CComboEvents.
Public gclsComboEvents As CComboEvents
Public Sub AddCombox()
Dim oleo As OLEObject
Dim clsComboEvent As CComboEvent
Set gclsComboEvents = New CComboEvents
For Each oleo In Sheet1.OLEObjects
If TypeName(oleo.Object) = "ComboBox" Then
Set clsComboEvent = New CComboEvent
Set clsComboEvent.Cbx = oleo.Object
gclsComboEvents.Add clsComboEvent
End If
Next oleo
End Sub
Now, whenever a combobox is changed, the event will fire and, in this example, a message box will show.
You can see an example at https://www.dropbox.com/s/sfj4kyzolfy03qe/ComboboxEvents.xlsm
A simpler way to get the selected value from a ComboBox control is:
Private Sub myComboBox_Change()
msgbox "You selected: " + myComboBox.SelText
End Sub
Maybe you'll be able to set the event handlers programmatically, using something like (pseudocode)
sub myhandler(eventsource)
process(eventsource.value)
end sub
for each cell
cell.setEventHandler(myHandler)
But i dont know the syntax for achieving this in VB/VBA, or if is even possible.