"Include" in Excel VBA? - excel

I have a user-form which is made up of many subs, this is assigned as a macro to a button on the worksheet. When the user is finished with this user-form they can press a button on it which causes its visibility to become false and when entered again everything appears how it was left resulting in a save like feature.
I now need to apply this to multiple buttons on the worksheet and each user form needs to have the exact same code and same buttons but be a separate form as each individual button requires it's own save like feature. The way I was planning on doing this was to copy the existing user form and paste it many times with different names however, if a modification is required it will take a long time to carry out therefore, is there a method such as "include" which could use a base module from which all the code is accessed so that if I ever need to change anything I just do it on that one module and everything else updates via the include?
EDIT:
I now have a public function called costing() and am getting an error when I used:
Private Sub material_Change()
Call costing
End Sub

You can have multiple instances of the same form. You can use this to retain multiple sets of form values
Try this:
Create your form, as usual. Let's call it MyForm
Create several buttons on your sheet. My example uses ActiveX buttons, but Form Control buttons can be used too. Let's call them CommandButton1 and CommandButton2
In your form module, include a Terminate Sub, which includes this code
Private Sub UserForm_Terminate()
' any other code you may need...
Unload Me
End Sub
The Form buton to save/Hide the form needs to be
Private Sub btnSaveAndHide_Click()
Me.Hide
End Sub
The Sheet Button code is as follows
The code is identical for each button (and calls a common Sub), and each button has its own Static form variable.)
The Error handler is needed to deal with the case a form is not properly closed. In this case the instance no longer exists, but the local Static variable is also not Nothing
Example shows form shown as Modeless, you can change this to Modal if you want.
Private Sub CommandButton1_Click()
Static frm As MyForm
ShowMyForm frm
End Sub
Private Sub CommandButton2_Click()
Static frm As MyForm
ShowMyForm frm
End Sub
Private Sub ShowMyForm(frm As MyForm)
If frm Is Nothing Then Set frm = New MyForm
On Error GoTo EH
frm.Show vbModeless
Exit Sub
EH:
If Err.Number = -2147418105 Then
On Error GoTo 0
Set frm = Nothing
Set frm = New MyForm
frm.Show
End If
On Error GoTo 0
End Sub
End result: multiple copies of the same form, each with their own values
In responce to comment How would I access the variables inside of each user form externally
In the example above the Form instances are only accessable in the Command Button Click Handler routines, or within the Form module itself. If you can write your code in the form module, then no change is needed.
To make the Form instances available elsewhere, consider moving their declaration to Module Scope of a standard Module. You could declare them as, eg individual variables, an array (either static or dynamic), a Collection, a Dictionary. Which structure is best will depend on how you want to manage and access your form instances.
For example, a Static Array: Code in a standard Module
Option Explicit
Global MyForms(1 To 2) As MyForm
Update the CommandButton code to
Private Sub CommandButton1_Click()
ShowMyForm Module1.MyForms(1)
End Sub
Private Sub CommandButton2_Click()
ShowMyForm Module1.MyForms(2)
End Sub
Private Sub ShowMyForm(frm As MyForm) no change, same as before
The code works the same as before, but you can now access the Global variable in a standard Module
Sub Demo()
Dim i As Long
For i = LBound(MyForms) To UBound(MyForms)
If Not MyForms(i) Is Nothing Then
MsgBox "Form " & i & " Value = " & MyForms(i).TextBox1.Value
End If
Next
End Sub

You don't need an "Include" (none exists in VBA); all you need to do is create a module and make the common methods public.
For example, if you create a module and have a function like this:
Public Function Add(first As Integer, second As Integer) As Integer
Add = first + second
End Function
Then you can access it like this from another module/form/class module:
Sub test()
MsgBox Add(3, 6)
End Sub

Related

How can I run a userform from a macro?

I developed many UDFs and macros in VBA for Excel 2016. One of my macros uses an Inputbox to get data used subsequently by the macro. I want to replace the Inputbox with a user form. I have created the user form, with one text box. I want to activate the user form, populating the text box with the default data, and return the text box data to the macro when OK is selected. I have searched extensively for an end-to-end example for all the the code needed to do this, with no luck. Does an example for this simple problem exist?
Add a Property to your user form. For this answer, let us use the following code within the user form.
Public Property Get MyResult() As String
' You may want to do any manipulation here
' including converting to a number, in which case the return type should be changed (*)
MyResult = TextBox1.Text
End Property
(*) If you are doing conversion, you can have another function in your user form to disable the "OK" button until they have valid convertible data in the text box.
You also want to know if they have hit "Cancel"
Public Property Get Cancelled() As Boolean
Cancelled = pCancelled ' Declare pCancelled as a Boolean in the scope of the form
End Property
Public Sub CancelButton_Click() ' Standard click event for the button
pCancelled = True
Me.Hide
End Sub
Public Sub OKButton_Click() ' Standard click event for the button
pCancelled = False
Me.Hide
End Sub
In your calling macro
MyForm.Show ' This is modal, so will block execution until a response is provided
If Not MyForm.Cancelled Then
Debug.Print MyForm.MyResult
'Do something with MyForm.MyResult
End If
UnLoad MyForm ' assuming you do not want to re-use this form as part of your logic.
There is an example of how you can pass the value to a form and get the result back. The approach uses Scripting.Dictionary object created within standard module scope and passed to userform to allow values to be changed. So it makes possible to send the default values to userform, and keep the result values in the dictionary even after the userform is closed and unloaded. You may have multiple values, just add the necessary quantity of keys to the dictionary, e. g. oData("property1"), oData("property2"), etc.
Add a standard module to the project and put the below code into it:
Option Explicit
Sub Test()
Dim oData
' Set default value and show form
Set oData = CreateObject("Scripting.Dictionary")
oData("") = "Some default text"
UserForm1.ShowForm oData
' Wait until user close form
Do While IsUserFormLoaded("UserForm1")
DoEvents
Loop
' Output returned value
MsgBox oData("")
End Sub
Function IsUserFormLoaded(UserFormName As String) As Boolean
Dim oUF As Object
For Each oUF In UserForms
If LCase(oUF.Name) = LCase(UserFormName) Then
IsUserFormLoaded = True
Exit Function
End If
Next
End Function
Add a userform module named UserForm1 to the project, place controls as shown:
And put the below code into the userform module :
Private opData
Public Sub ShowForm(oData)
Set opData = oData
Me.TextBox1.Value = opData("")
Me.Show
End Sub
Private Sub UserForm_Initialize()
If TypeName(opData) <> "Dictionary" Then Set opData = CreateObject("Scripting.Dictionary")
End Sub
Private Sub CommandButton1_Click()
Unload Me
End Sub
Private Sub CommandButton2_Click()
opData("") = Me.TextBox1.Value
Unload Me
End Sub

Mapping a variable from an Activex control - [VBA]

Hi I am trying to use a publicly declared variable that has a value assigned to it upon clicking one of two optionbuttons inside a userform.
The userform does show and I can click either of the two buttons i have in the frame I created, but the variable does not pick up the click or actually I don't know what I'm doing with the userform controls. And i don't think the form hides either, clicking the optionbutton seems to be a dummy click, it highlights but does nothing after that.
Plus i'm not sure if the code itself needs to be placed on the userform' code section or the module code section, does it make a difference? Currently running it on the module code section.
Option Explicit
Dim C As String
Public Sub OptionButton1_Click()
C = "Delivery"
End Sub
Public Sub OptionButton2_Click()
C = "Holiday"
End Sub
Public Sub Optionbutton()
Userform1.Show
Sheet1.Cells(1, 1).Value = C
Userform1.Hide
End Sub
********--Edit-*********
I guess Zack's method gets the job done, printing to Cell A1, but what I was really after is although the event handler needs to be private, running debug points out that the execution stops when the private sub ends, which could be optionbutton1 or optionbutton2's click event, which is what led me to believe the click was a dummy click.
But what I was really after is for the C variable to be available in the public sub that I execute from, is there a way for the execution to divert to the public sub after the C is assigned a value in the optionbutton private sub?
For example if the C was assigned in the private sub but my success criteria were defined by the successful execution of the public Sub defined above, because i have a larger macro where that variable will be used inside the public sub, and this is only part of the problem, or the problem I can't solve.
If I understand correctly, your form has two option buttons. Thus, your form's code-behind should look like this:
Option Explicit
Private Sub OptionButton1_Click()
C = "Delivery"
End Sub
Private Sub OptionButton2_Click()
C = "Holiday"
End Sub
This assumes there's a global variable C declared somewhere, in a standard module:
Option Explicit
Public C As String
Public Sub Optionbutton()
Userform1.Show
Sheet1.Cells(1, 1).Value = C
End Sub
Note that this displays the form modally, which means the next instruction only runs after the form is closed/destroyed. For that reason, there's no need for the subsequent Userform1.Hide call: the form is already gone by then.
Once you're comfortable with manipulating global variables from a form's code-behind, read UserForm1.Show (I wrote that article) for insight on a vastly more robust way to go about this.
Try this:
You will need to tell the control where to place the value after the button is clicked.
Option Explicit
Dim C As String
Private Sub OptionButton1_Click()
C = "Delivery"
Sheet1.Cells(1, 1) = C
Userform1.Hide
End Sub
Private Sub OptionButton2_Click()
C = "Holiday"
Sheet1.Cells(1, 1) = C
Userform1.Hide
End Sub
Public Sub Optionbutton()
Userform1.Show vbModeless

Call same SUB from multiple buttons in VBA

I have multiple buttons (active x) on a spreadhseet in same column but different rows. These buttons capture the start time of an activity.
If button 1 is pressed cell next to it should be populated by current time.
If button 2 is pressed cell next to it should be populated by current time. and so on.....
I have written a SUB in VBA as follows:
Private Sub StartTimer_Click()
Range("I4").Value = Now
End Sub
I do not want to repeat this code for each button action. Please let me know how it can be made dynamic.
A simple WithEvents example:
in a class (named clsButtons):
Private WithEvents Bt As MSForms.CommandButton
Property Set obj(b As MSForms.CommandButton)
Set Bt = b
End Property
Private Sub Bt_Click()
'uses the right of the name of the CommandButton
Cells(1 + Right(Bt.Name, 1) * 3, 9).Value = Now
End Sub
In the sheetcode (the one with the buttons):
Dim myButtons As Collection
Private Sub Worksheet_Activate()
Dim ctl As OLEObject
Dim ButtonClass As clsButtons
Set myButtons = New Collection
For Each ctl In Sheet1.OLEObjects
If ctl.progID = "Forms.CommandButton.1" Then
Set ButtonClass = New clsButtons
Set ButtonClass.obj = ctl.Object
myButtons.Add ButtonClass
End If
Next ctl
End Sub
Create a standard module and put the procedure in there.
While it is possible to share a procedure in a private module, it's best practice to put any shared procedures in a shared module.
In the VBA Editor click Insert > Module,
Paste into there, and give it a unique name. Using your example you could do something like:
Public Sub SetTimeValue()
Range("I4").Value = Now
End Sub
...then call this public stub from your other one, like:
Private Sub StartTimer_Click()
SetTimeValue
End Sub
...and from any other locations where you need to call your code.
I assume that you have more than one line of code for the actual procedure you're concerned about, otherwise copying it repeatedly isn't really a concern.
More Information:
MSDN : Understanding Scope and Visibility
Office Support : Scope of variables in Visual Basic for Applications
Chip Pearson : Understanding Scope Of Variables And Procedures
PowerSpreadsheets : Excel VBA Sub Procedures: The Complete Tutorial
MVP : Cut out repetition using subs and functions with arguments

User form can't pass value to module

I've inserted a user form into a project that already contains a bunch of modules. By playing around with the code in the user form, I've verified that I can return the value from a combo box.
User form code:
Public SelectedPacking As Integer
Private Sub CancelButton_Click()
UserForm1.Hide
End Sub
Private Sub OKButton_Click()
SelectedPacking = ComboBox1.ListIndex
Call DemoDialogOk
'Return list index value to cell C50
Worksheets("Inputs & Results").Range("C50") = SelectedPacking
Unload UserForm1
End Sub
My problem is that I can't pass this value on to any of the macros written in the modules.
Module code:
Public Sub ShowComboBox()
UserForm1.Show
End Sub
Public Sub DemoDialogOk()
ival = SelectedPacking
'Return value of ival (list index value from combo box) to cell C17
Worksheets("Packed bed (Random)").Range("C17") = ival
End Sub
Obviously the module contains more useful code, but I've commented out everything to try and figure out where I'm going wrong. I've been changing some things around, but I still can't get anything to appear in cell C17, so I think I'm missing something fundamental.
I think two options: 1) change DemoDialogueOK to accept variables:
Public Sub DemoDialogOk(SelPack as integer)
' ival = SelectedPacking
Worksheets("Packed bed (Random)").Range("C17") = SelPack
End Sub
Private Sub OKButton_Click()
SelectedPacking = ComboBox1.ListIndex
Call DemoDialogOk(SelectedPacking)
...
End Sub
Or option two: fully qualify the variable from the useform i.e:
Public Sub DemoDialogOk()
ival = ufYourForm.SelectedPacking
...
End Sub
Public variables in userforms don't appear to be as "public" as module level...
Tipping on top of Simon's answer, you could pass the entire userform if you'd like. This would give you access to all the pieces of it and is especially useful if you need to do some validation on, say, different checkboxes being checked or not.
Sub inOurUserForm()
Call inADifferentModule(Me) 'Passes this userform
End Sub
Sub inADifferentModule(ourForm As UserForm1)
'Passed the form, and using it like a class (As whatever the form is called)
If ourForm.chkYes = True Then
'Do something
Else
'Do something else, like
ourForm.chkYes = False 'Because we are passing the object itself _
rather than a copy, at least in my understanding
End If
End Sub
And you don't necessarily need to pass the userform, as you could just reference it as an object itselft e.g.
UserForm1.chkYes
A very easy solution to this would be to declare a variable within the Userform (UserForm1 in this example)
Public Pass as string
This Pass would contain the string where you store the password. Once you store the password, you can hide the form
Me.Hide
Within the module, you can open the Form as modal
UserForm1.Show vbModal
Now after all the code inside the userform is run, the password can be retrieved within the module -
UserForm1.Pass
You can then unload the hidden form
unload UserForm1

Get Selected value of a Combobox

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.

Resources