Global variable not accessible in different sub? - excel

I declared projname globally at the top of the module 1. It is assigned in the userform, and is successfully accessed in the createWB sub. However, when I go to access it in the addWindow sub also in module 1, it becomes empty (""). I'm unsure of why this is happening because I thought that since the variable was globally declared I should be able to access it in any sub.
Module 1
Option Explicit
Public outputWorkbook As Workbook
Public globalcounter As Integer
Public projname As String
Public projnum As String
createWB()
Dim uf2 As New UserForm2
uf2.Show
Set outputWorkbook = Workbooks.Add(xlWBATWorksheet)
outputWorkbook.SaveAs Filename:=Environ("userprofile") & "\Desktop\" &
Replace(projname, " ", "") & ".xlsx"
outputWorkbook.Activate
Range("B3") = projname
Range("B4") = projnum
End Sub
addWindow()
Workbooks(Replace(projname, " ", "") + ".xlsx").Activate
End Sub
Userform Code
Public Sub CommandButton1_Click()
projname = Me.TextBox1.Text
projnum = Me.TextBox2.Text
Me.Hide
End Sub
Cells B3 and B4 are assigned the correct value, but the addWindow() line causes a subscript out of range error. When I test it with Debug.Print, I see that projname = "". I also simply tried outputWorkbook.Activate, which did not work either.

Avoid Global Pollution
Unless there is a really good reason to use them, try to avoid global variables. We want to avoid polluting the global namespace. Captain Planet warned us of that.
Instead, try passing your parameters through your various methods as they are needed. This helps prevent errors, makes your code easier to follow, and utilizes composition.
Using your userform to store and expose your properties
Try to instantiate your userform using a With statement so that you have a captured instance of it where you have access to its various properties that you expose. In your case ProjectName and ProjectNumber.
Additionally, there should be a property to check if the userform was canceled or the X button was pressed.
You userform would look something like this:
Option Explicit
Private cancelled As Boolean
Public Property Get ProjectName() As String
ProjectName = TextBox1.Value
End Property
Public Property Get ProjectNumber() As Long
ProjectNumber = TextBox2.Value
End Property
Public Property Get IsCancelled() As Boolean
IsCancelled = cancelled
End Property
Private Sub CommandButton1_Click()
Me.Hide
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
Instantiating the userform
Here is the example of now calling your userform (P.S. Change the name from Userform2). Notice we are capturing our instance of your userform using the With block. Within this block, we have access to the properties we exposed: ProjectName, ProjectNumber, IsCancelled.
Private Sub createWB()
With New UserForm2
.Show
If Not .IsCancelled Then
' Do neccessaray steps here...
' You have access to ProjectName and Project number.
' Pass this to your addWindow method.
addWindow .ProjectName
End If
End With
End Sub
The ProjectName now can be accessed from your userform and passed as a parameter to you addWindow method.
Private Sub addWindow(ByVal projName As String)
Workbooks(Replace(projName, " ", "") + ".xlsx").Activate
End Sub
For more information on using userforms in this way see this helpful Rubberduck Blog Post.

could you try using Module1 as prefix? , jus like in this code
Public Sub CommandButton1_Click()
Module1.projname = Me.TextBox1.Text
Module1.projnum = Me.TextBox2.Text
Me.Hide
End Sub

Related

How to add a textbox value to variable from a userform?

I've created a userform named UIAutotestHeader and textbox named pypath. And on button click I'm trying to pass a value to a variable but getting runtime error 424. Any help please.
Sub LoopThroughFiles()
Dim Path As String
UIAutotestHeader.Show
Path = pypath.Value
If pypath.Value = "" Then
MsgBox "Please add a path having .py files."
End If
End sub
Button click code:
Private Sub CommandButton1_Click()
UIAutotestHeader.Hide
End Sub
First, see this helpful RubberDuck Blog on working with UserForms, very helpful and applicable. This is what I'm basing my answer on.
Try to instantiate your userform using a With statement so that you have a captured instance of it where you have access to its various properties that you expose.
Note, in this case, you don't have to store your variables, as you still have access to them in your instance of your userform. Here is an example below.
Sub LoopThroughFiles()
With New UIAutotestHeader
.Show
If Not .IsCancelled Then
If .PyPath = "" Then
MsgBox "Please add a path having .py files."
End If
End If
End With
End Sub
In your Userform, you can expose the properties that you want to have access to. I also added the IsCancelled method to make sure the user didn't press cancel.
Option Explicit
Private cancelled As Boolean
Public Property Get PyPath() As String
PyPath = pypath.Value
End Property
Public Property Get IsCancelled() As Boolean
IsCancelled = cancelled
End Property
Private Sub CommandButton1_Click()
Hide
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
Try this code
'In Standard Module
'------------------
Public sPath As String
Sub LoopThroughFiles()
Load UIAutotestHeader
sPath = UIAutotestHeader.pypath.Value
UIAutotestHeader.Show
End Sub
'In UserForm Module
Private Sub pypath_AfterUpdate()
If sPath = "" Then
MsgBox "Please add a path having .py files."
End If
End Sub
Private Sub CommandButton1_Click()
If sPath <> "" Then MsgBox sPath
sPath = ""
Unload UIAutotestHeader
End Sub

write One code for two controls? For example Two CommandButton

In my userform, there are CommandButton1, 2, 3, 4, 5, 6........,and they all do the same task。 For example: Range("A1").value = "Good"。
Here comes the question, is there any way that I can just write one code for all of them?
If the answer is yes, can I make range variable? For example: CommandButton1: range("A1").value = "Good", CommandButton2: range("A2").value = "Good"。
Many thanks for your reply.
VBA does not have a sophisticated event model. Instead, you can create, and call, a common sub procedure, passing the particular argument(s) that the sub needs to determine its outcome:
Private Sub CommandButton1_Click()
Call Clicker("A1")
End Sub
Private Sub CommandButton2_Click()
Call Clicker("A2")
End Sub
Private Sub Clicker(sRange As String)
'MsgBox sRange
Range(sRange).Value = "Good"
End Sub
The parameter does not have to be a string, it could be a Range.
Or the value "A1", etc., could be stored, and retrieved, as a property of the form (rather than passing it as an argument).
A possible alternative is to check the ActiveControl and then behave accordingly:
Private Sub Clicker()
MsgBox ActiveControl.Name
'do something according to the name
End Sub
I don't like this and prefer the first alternative, as the value(s) are likely to be specific to the clicked button anyway (and the button name may change). The code may also be called without a relevant button being active.
A third alternative is to create your own custom classes and event model, which would require some research.
Here is an example using a custom property of the UserForm:
Private sCellOfInterest As String
Private Property Get CellOfInterest() As String
CellOfInterest = sCellOfInterest
End Property
Private Property Let CellOfInterest(ByVal sRange As String)
sCellOfInterest = sRange
End Property
Private Sub CommandButton1_Click()
CellOfInterest = "A1"
Call Clicker2
End Sub
Private Sub CommandButton2_Click()
CellOfInterest = "A2"
Call Clicker2
End Sub
Private Sub Clicker2()
MsgBox CellOfInterest
End Sub
Again, the property could be an object rather than a string, then Property Set would be used rather than Property Let.

VBA Excel, Returning a Variable between Forms

I need some help. I am writing VBA script for an Excel macro. I have two forms, the first has a button calling the second. On the second I want it to return a variable (tempdate, this is declared as public type Date in the main Worksheet).
Main form button code:
Private Sub SetDueButton_Click()
UserForm1.Show
DueDate.Value = tempdate 'display tempdate in the DueDate text box
End Sub
UserForm1 'ok' button:
Private Sub CommandButton53_Click()
tempdate = TextBox1.Value
Unload Me
End Sub
What am I missing to get this variable to load whatever is in TextBox1.Value?
I would use a property in the userform
Your main form code would change to look like this:
Option Explicit
Private Sub SetDueButton_Click()
Dim UF As UserForm1 'you can declare a user form as an independent object
UF.Show
DueDate.Value = UF.tempdate 'get the property
Unload UF 'now you can unload or set to Nothing
End Sub
Your UserForm1 code would be this:
Option Explicit
Public Property Get tempdate() As String
tempdate = Me.TextBox1.Value 'Me refers to the form itself
End Property
Private Sub CommandButton53_Click()
Me.Hide 'hide don't unload yet or you can't get the data.
End Sub
You need to declare the variable in a module, not the worksheet, and the declaration needs to be:
Global tempDate as Date
or
Public tempDate as Date
The variables in the worksheet are not available in the forms.
It would be possible to make a function in the worksheet and call this function, but this is a much simpler solution.

Passing variable from Form to Module in VBA

I have the following button on a Form:
Private Sub CommandButton1_Click()
Dim pass As String
pass = UserForm1.TextBox1
Unload UserForm1
End Sub
I then have a Module called Module1:
Public Sub Login()
...
UserForm1.Show
driver.findElementByName("PASSWORD").SendKeys pass
...
End Sub
The idea is whatever password the users enters into the input box will be assigned to the variable pass. What I'm having trouble doing however is passing pass from UserForm1 into Module1's Login sub.
I would of thought adding something like Module1.Login (pass) to my form before I unload it would work, however that doesn't seem to pass anything. Any help would be much appreciated. Thanks.
Don't declare the variable in the userform. Declare it as Public in the module.
Public pass As String
In the Userform
Private Sub CommandButton1_Click()
pass = UserForm1.TextBox1
Unload UserForm1
End Sub
In the Module
Public pass As String
Public Sub Login()
'
'~~> Rest of the code
'
UserForm1.Show
driver.findElementByName("PASSWORD").SendKeys pass
'
'~~> Rest of the code
'
End Sub
You might want to also add an additional check just before calling the driver.find... line?
If Len(Trim(pass)) <> 0 Then
This will ensure that a blank string is not passed.
Siddharth's answer is nice, but relies on globally-scoped variables. There's a better, more OOP-friendly way.
A UserForm is a class module like any other - the only difference is that it has a hidden VB_PredeclaredId attribute set to True, which makes VB create a global-scope object variable named after the class - that's how you can write UserForm1.Show without creating a new instance of the class.
Step away from this, and treat your form as an object instead - expose Property Get members and abstract away the form's controls - the calling code doesn't care about controls anyway:
Option Explicit
Private cancelling As Boolean
Public Property Get UserId() As String
UserId = txtUserId.Text
End Property
Public Property Get Password() As String
Password = txtPassword.Text
End Property
Public Property Get IsCancelled() As Boolean
IsCancelled = cancelling
End Property
Private Sub OkButton_Click()
Me.Hide
End Sub
Private Sub CancelButton_Click()
cancelling = True
Me.Hide
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = VbQueryClose.vbFormControlMenu Then
cancelling = True
Cancel = True
Me.Hide
End If
End Sub
Now the calling code can do this (assuming the UserForm was named LoginPrompt):
With New LoginPrompt
.Show vbModal
If .IsCancelled Then Exit Sub
DoSomething .UserId, .Password
End With
Where DoSomething would be some procedure that requires the two string parameters:
Private Sub DoSomething(ByVal uid As String, ByVal pwd As String)
'work with the parameter values, regardless of where they came from
End Sub

Calling a userform and returning a value

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.

Resources