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
Related
I have been wondering about this one for a while now.
Let's say I have a formula in A1, Worksheet("Main")
=IF(B2="English";"Good morning";"Guten Morgan")
Then I have userform with code:
Private Sub TextBox1_Change()
ThisWorkbook.Worksheets("Main").Range("A1").Value = Me.TextBox1.Text
End Sub
Private Sub UserForm_Initialize()
Me.TextBox1.Text = ThisWorkbook.Worksheets("Main").Range("A1").Value
End Sub
How can I make it work so, that if I don't input anything into textbox, it will keep displaying functions result. If I will start to type text into textbox it will input my typed text to A1. Now if I open the userform it will overwrite A1 with the text in textbox and there will be no formula anymore. So if I change language in B2 result will no longer be interfaced into textbox.
Can be also some other approach with VBA. Everything is acceptable as long as logic will work.
I have tried to use textbox properly, something like linkedsource or similar, but it is crashing excel workbook sometimes. That's why I am trying to avoid it.
EDIT:
Thank you for suggestions! I have tried to implement this somehow but still don't get it. I am creating variable where I want to store result from ThisWorkbook.Worksheets("Other Data").Range("L49").Value then I would like to use it in Userform Me.TextBox14.Text to be displayed. Then once it is changed in Me.TextBox14.Text and Enter button has been pressed it should change also in ThisWorkbook.Worksheets("Other Data").Range("L49").Value.
Here is my current code I am trying to play with:
Private ProjectClass As String
Private Sub TextBox14_Enter()
ThisWorkbook.Worksheets("Other Data").Range("L49").Value = ProjectClass
End Sub
Private Sub UserForm_Initialize()
Me.TextBox14.Text = ProjectClass
End Sub
The TextBox.Enter event isn't fired when the user presses Enter, but when the control is entered - that is, when it gets the focus and a caret/cursor starts blinking inside it. You'll want to update the backing variable when the value is modified:
Private Sub TextBox14_Enter()
'runs when the control gets focus
End Sub
Private Sub TextBox14_Exit()
'runs when the control loses focus
End Sub
Private Sub TextBox14_Change()
'runs whenever the value changes (real-time)
End Sub
So in this case I'd go with the TextBox.Change event handler, and make it update the variable (not the worksheet):
Private ProjectClass As String
Private Sub TextBox14_Change()
ProjectClass = TextBox14.Text
End Sub
Now the problem is that the ProjectClass value needs to be accessible from outside the form, so that the caller can set an initial value. One way to do this could be to expose it as a property - one property (get+let) for each field you want to seed a value for:
Public Property Get ProjClass() As String
ProjClass = ProjectClass
End Property
Public Property Let ProjClass(ByVal value As String)
ProjectClass = value
ApplyModelProperties
End Property
Private Sub ApplyModelProperties()
TextBox14.Text = ProjectClass
'...
End Sub
Now from outside the form, at the call site (the code that's showing this dialog), you can seed the value from the worksheet, and the form never needs to know or care that a worksheet was involved:
With New UserForm1
.ProjClass = ThisWorkbook.Worksheets("Other Data").Range("L49").Value
.Show
MsgBox .ProjClass
End With
Note that because the value is exposed as a property, the calling code doesn't need to know about TextBox14 anymore.
currently i am programming a excel macro. The macro shows a Userform.
In the Userform the User can Select something. After the User has selected something i call Userform.Hide to Hide the Userform and to read the Selection from the Form. After the selection was read i call Unload Userform. Now the Code interacts with the selection. I want to do this in a loop but when the Code trys to show the Userform the second time. I get a exception that the Form is already displayed. I cant understand it, because i called Unload Userform. When i do it in debug mode everthing works as it should.
Userform Code
Private Sub Image1_Click()
SelectCard 1
End Sub
Private Sub Image2_Click()
SelectCard 2
End Sub
Private Sub SelectCard(number As Integer)
SelectedNumber = number
Me.Hide
End Sub
Public Sub CardSelector_Activate(Cards As Cards)
Dim c As card
For Each Key In Cards.CardDictionary.Keys
Set c = Cards.CardDictionary.Items(Key - 1)
If c.value = 1 And c.played Then
Image1.Enabled = False
End If
If c.value = 2 And c.played Then
Image2.Enabled = False
End If
Next Key
number = SelectedNumber
CardSelector.Show
End Sub
Code in the ClassModule i call this in a loop
Sub Costum(Spalte As Integer, Zeile As Integer, SpalteBeginn As Integer, Cards As Cards, CardsOpponent As Cards)
CardSelector.CardSelector_Activate Cards
Dim c As card
Dim number As Integer
number = CardSelector.SelectedNumber
Set c = Cards.CardDictionary.Items(CardSelector.SelectedNumber - 1)
SetCardAsPlaced c, Zeile, Spalte, SpalteBeginn
Unload CardSelector
End Sub
Can someone help me here ?
I am not sure if I fully understand your issue, but this is how I invoke a form using VBA. This is assuming you have a Cancel and OK button:
In the form:
Option Explicit
Private m_ResultCode As VbMsgBoxResult
Private Sub btnCancel_Click()
Call CloseWithResult(vbCancel)
End Sub
Private Sub btnOK_Click()
' Store form control values to member variables here. Then ...
Call CloseWithResult(vbOK)
End Sub
Private Sub CloseWithResult(Value As VbMsgBoxResult)
m_ResultCode = Value
Me.Hide
End Sub
Public Function ShowMe(Optional bNewLayerOptions As Boolean = True) As VbMsgBoxResult
' Set Default to Cancel
m_ResultCode = vbCancel
' Execution will pause here until the form is Closed or Unloaded
Call Me.Show(vbModal)
' Return Result
ShowMe = m_ResultCode
End Function
Then, to call it (please note that frmLayers is my own VBA form object - you would use yours):
Dim dlgLayers As New frmLayers
If (dlgLayers.ShowMe(False) = vbOK) Then
' Proceeed
End If
Does this help you with your issue? I am sorry if I have misunderstood, and I will remove my answer if needed.
Things like xxxxx_Activate etc. are event handlers called by the framework. So, for example, there is an event for activate and an event for initialize. You don't normally have to directly call these yourself if you set your code up correctly. See https://support.microsoft.com/en-us/kb/138819.
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
I have a vba code thats Auto_Open. It does some checks then prompts a userform that asks for username and password. I called this userform with userform_name.show.
My issue is how can I return a Boolean to my Auto_Open sub from the userform code.
I linked the code that verifies if the credentials are correct to the "Login" button on the form. this is the code that produces the Boolean. I need to return it to the Auto_Open.
Private Sub loginbutton()
Dim bool As Boolean
Dim lrup
Dim r As Long
Dim pass As String
loginbox.Hide
'are fields empty
Do While True
If unBox.Text = "" Or pwBox.Text = "" Then
MsgBox ("You must enter a Username and Password")
Else
Exit Do
End If
loginbox.Show
Exit Sub
Loop
'find pw reated to username (if existant)
lrup = UserPass.Range("A1").Offset(UserPass.Rows.Count - 1, 0).End(xlUp).Row
If unBox = "b0541476" And pwBox = "theone" Then
bool = True
Else
MsgBox ("Invalid username or password. Please try again.")
loginbox.Show
Exit Sub
End If
For r = 2 To lrup
If unBox = Cells(r, 1) Then
pass = Cells(r, 2).Value
Exit For
End If
Next
If pass = "" Then
MsgBox ("Invalid username or password. Please try again.")
loginbox.Show
Exit Sub
Else
bool = True
End If
End Sub
You can manage to do this without the use of public variables.
There appears to be a difference between show/hide and load/unload.
If you hide a form while it's still loaded it won't be cleared out, so you can reference the state of the controls on the form.
For example I was using a date picker (called DTPicker1) on a form, my code in the module looks something like this:
Dim NewDay As Date
Load FrmDayPicker
FrmDayPicker.Show
NewDay = FrmDayPicker.DTPicker1.Value
Unload FrmDayPicker
Debug.Print NewDay
On your form you can just use Me.Hide insteaded of Unload Me and this should work
Remove Dim bool As Boolean from the userform code area and declare it in the module as shown below
This is how your Code in the module would look like
Public bool As Boolean
Sub Auto_Open()
'
'~~> Rest of the code
'
UserForm1.Show
If bool = True Then
'~~> Do Something
Else
'~~> Do Something
End If
'
'~~> Rest of the code
'
End Sub
How about using a function instead of a sub?
Function loginbutton()
' your code
loginbutton = bool
End Function
Now in your calling code you can test for true/false
if loginbutton() then
'true responce
else
'false responce
end if
Update:
I was to quick to dismiss public variables. While both methods can work, Pub Vars and directly accessing items, sometimes it's not ideal to access an item directly if say it's a list.
I now have modules specifically for calling UserForms which only declar the public variables and call the userform. I can then call these modules from UserForms or Modules and have access to the public variable after the userform is closed.
Eg: Here is a module I use now, very basic, and all my other needs can just call this module/sub.
Public ColSelectorDic As Object
Public Sub Col_Picker_Sub()
Col_Picker_UserForm.Show
End Sub
It's simplest IMO to use Public Variables declared in the Module calling the UserForm. But, this has the caveat if you wanted to call this userform from separate modules, you will get errors regarding duplicate declarations/ambiguous names.
So, if you know it's only going to be called be the one module, Pub Vars all the way. In my case I was using a "Column Picker" userform, which was very simple and I wanted to be able to utilize it again in unforseen future projects so I attempted to resolve the above caveat.
See this answer for Public Variables, no need to repeat information --> https://stackoverflow.com/a/18966341/5079799
And this answer related to Accessing the Form Variables directly -->
https://stackoverflow.com/a/47919465/5079799 but I felt it could use some expanding.
Also, here is a good article which goes deeper in depth about accessing userform variables directly --> https://gregmaxey.com/word_tip_pages/userform_pass_data.html
So my UserForm looks like this and is named ColPicker:
Private Sub UserForm_Initialize()
Dim i As Long
lCol = Get_lCol(ActiveSheet)
For i = 1 To lCol
ColumnLetter = Col_Letter(i)
Me.ComboBox1.AddItem ColumnLetter
Next
End Sub
Private Sub CommandButton1_Click()
Me.Hide
End Sub
Sub PassVarFromUserForm()
ColPicker.Show
Dim ColLetter As String
ColLetter = ColPicker.ComboBox1.Value
Unload ColPicker
Debug.Print ColLetter
End Sub
Notice how the "Run"/Command Button in the UserForm just hides the form, I then store the values in a variable, THEN unload the form, from the module, via utilizing it's name. (You can only use unload me from within the userform).
The variable is then available inside module and can be declared in the beginning as public, or inside module, it doesn't matter as it can be declared differently in each module, the userform has no idea/reference to what the variable name the information will be stored in.
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