VBA Calling a private function inside of a userform - vba

I have a userform defined as "SingleListForm".
I have a private sub named "loadSingleListForm(ID As Double)"
I am attempting to pass a variable into the form and I was implementing it in the fashion that loadSingleListForm would set the form up based on ID, basically re-using one form to show variable data from variable sources in the listbox.
But calling a standard Intersect from outside the form (Worksheet_SelectionChange) these two options compile but do not work.
Application.Run "SingleListForm.loadSingleListForm", ID 'ID already declared and assigned
This doesn't work either
Call ActiveWorkbook.UserForm("SingleListForm").loadSingleListForm(ID)
Where it says UserForm I have also tried SingleListForm.
Here the runtime error is:
I am trying hard not to use a Global Variable here to pass to the form.
Perhaps I should go to Initialize and try something there.
I am trying to pass the variable to the form and then of course set up the form based on this case and then show the form. you can't pass with show so you have to find another way to set up.
I just realized I have not called a userform private function from outside of the form before, but I do it with modules all the time. The first case works in that instance.
Cheers,
-WWC

The better way to do this is to declare a property to the form. In the form's module enter
Option Explicit
Private myID as double
Property Set ID(i as double)
myID = i
End Property
Then your function
Private Sub loadSingleListForm()
can refer to myID with in it's code
To Use this from outside modules you use
Load SingleListForm
SingleListForm.ID = ID 'ID variable already declared

Declare your sub in the form as Public Public Sub loadSingleListForm(ID As Double) and then call it like this SingleListForm.loadSingleListForm ID

Just to cover this. Empty workbook, one button.
The button calls to a private function in the form that does nothing but open a message box. Testing concept here.
This is all there is:
Doesn't work:
UserForm1.you_made_it
Error at compile, method or data member not found
Same if this:
With ThisWorkbook
UserForm1.you_made_it
End With
Then try this:
Application.Run "UserForm1.you_made_it"
Error: Cannot run macro . . . .
Try this from first comment:
ActiveWorkbook.UserForm("UserForm1").you_made_it
Error: Object doesn't support this property or method
So this is the winner from above. Not sure I wanted to go Public but it works.
Doesn't solve how to use a private member in the form but it gets the coding going forward.
Public Sub you_made_it()
MsgBox ("you made it")
End Sub
So far:
1) Move the private to a module and then call it
2) make the function Public and it works
Thank you,
-WWC

Related

Calling Public variable from userform

In a userform, I have this at the top:
Public DelMonth As Variant
The value of DelMonth is read from a ComboBox, and I can call it from different subroutines within that userform just fine. But when I call it from a separate module, it doesn't read it. It doesn't even throw an error. If I do a MsgBox DelMonth, it doesn't do anything.
A form is an object; a public field in an object module belongs to an instance of that object. UserForms are little more than class modules with a default instance (i.e. a VB_PredeclaredId = True attribute) and a designer.
If you're using the form's default instance (a rather bad idea), then you can do this:
MsgBox UserForm1.DelMonth
Note that storing state in global objects is bug-prone, and will end up causing issues.
If you're treating the form like the full-fledged class it is, then you'll have something like this:
With New UserForm1
.Show
MsgBox .DelMonth
End With
Note that the field being Public means anyone, anywhere can go and write to it. What you mean is for the form to determine its value, and for the caller to be able to read that value. You do this by encapsulating the field with a Property Get member - start by making the field Private:
Option Explicit
Private DelMonth As Variant ' wouldn't Integer or Long be more appropriate?
Public Property Get DeliveryMonth() As Long
DeliveryMonth = DelMonth
End Property
Now the callers don't get to see the private DelMonth, and all they can do with DeliveryMonth is call the Get accessor, which doesn't let them tamper with the encapsulated value.
It doesn't even throw an error.
That's worrying. You're allowing VBA to happily compile typos and otherwise illegal code. Specify Option Explicit at the top of every module. Always.

Passing a value from UserForm to sheet

I created a userform called Level with a button, "beginner". When it's clicked "beginner" is set as levelinput as a string to be used in coding sheet 1
--------------Sheet1(Code)------------
Public levelInput as string
Public Sub Player()
Level.show
Msgbox levelInput
Lev=levelInput
.....
end sub
--------------Below is for Userform, Levels------------------
Private Sub beginner_Click()
levelInput= "beginner"
Levels.Hide
end sub
Currently, the msgbox shows nothing and the value doesn't seem to be passed to the Sheet(Code).
Where did I go wrong?
levelInput= "beginner"
That variable isn't declared. I know, you think it is, but I'll get to it in a moment.
The thing you've fallen pray of, is called scoping.
Public levelInput as string
By declaring the levelInput public field in the code-behind of Sheet1, you're saying "any instance of Sheet1 will have a levelInput public variable".
So you can change your code to this:
Sheet1.levelInput= "beginner"
And it will work.
But the most important thing to do is this:
Option Explicit
Always turn that option on, in the declarations section of every module (i.e. at the top, before any Sub or Function or Property). Had you done that, the VBA compiler would have told you levelInput isn't declared, and would have refused to run anything until you fixed the problem - either by declaring a locally-scoped levelInput variable, or by properly qualifying the Sheet1.levelInput public field.
Without Option Explicit, VBA happily lets you assign and refer to variables that aren't declared, which leads to unexpected bug, as you've experienced.
PS - Once you get your code to work as intended, I'd recommend you post it on Code Review, where you'll learn things like why Level.Show is bad code, and how you can use functions, parameters and return values instead of global variables, for more robust, maintainable and easier-to-follow code.

Declare a "type" object globally

I want to pass objects between form modules.
I have made a simple database to explain the problem I am having.
I have two forms --> form1, form2 and one module --> Module1
Based on the threads I have read, I believe if I want to define a global object, it should be done in the module so I placed the declaration there;
Option Compare Database
Public Type Name
First As String
Last As String
Phone As String
End Type
On form1 I have a cmd button with the following code behind it;
Option Compare Database
Dim My_Name As Name
Private Sub cmd_Button_Click()
My_Name.First = "MyFirstName"
My_Name.Last = "MyLastName"
MsgBox ("In Form1 " & My_Name.Last)
DoCmd.OpenForm "Form2"
End Sub
on the second form I have;
Option Compare Database
Dim my_Name As Name
Private Sub Form_Load()
MsgBox (my_Name.First)
End Sub
In the first form, I see the my_Name object variable fine, when I pass control to the second form, I lose the variable.
I have tried a number of variations of Public, Static declarations all over the place on both the object variable and the sub's but I cannot figure out how to preserve the object across modules.
You are re-declaring my_name. So it is in -effect two different things. Like george forman and george forman.
I am sorry I can't be more help it's been like a a million years since I have had to deal with this. However, it seems that your 'froms' are actually declared in an 'module' like block -name from1. That from global scope you can reference a variable in your form1.
In this case form1.my_name. However, this can lead to issues if form1.my_name has not yet been instanciated.
My recommendation is to find the module with global scope ( I dont remember exactly what it is, if you have to create it or now, and when it gets life). Declare and initicialize your variables in it. Then access those from within you event handlers ("cmd_Button_Click" for example.)

VBA UserForm running twice when changing .Caption

I'm running a VBA macro from SolidWorks. The form doubles as an input for two types of document. In the UserForm.Initialize subroutine I'm changing the name of the UserForm's Caption depending on which document type is open. Whenever I do this though, the program reruns UserForm.Initialize, and when it's all done, it carries on from where it left of, effectively running twice.
Does anyone know a way around this bizarre behaviour? I tried putting the FormName.Caption command into its own Sub but the result is the same.
Many thanks.
I can't replicate the problem and I don't know what SolidWorks is, so that may have something to do with it. Perhaps you can post a made-up example that shows Initialize being called twice.
My guess would be that it's related to auto-instantiating variables. When you use UserForm1, you are instantiating an object variable called UserForm1 that points to an object, also called UserForm1. It's similar to using the New keyword in a Dim statement. You never defined UserForm1 (the variable), but VBA did and the first time you use it, it instantiates automatically.
You should try to use the Me keyword when working inside the userforms class module (userforms are classes just like other objects except that they have a user interface element). In the Initialize event, say
Me.Caption = "blah"
instead of
UserForm1.Caption = "blah"
It could be (just a theory that I wasn't able to prove) that the flag that gets set to say "I'm pointing to a real instance" isn't set by the time you change the Caption property, and that by using the auto-instantiating variable UserForm1, you are forcing another instantiation.
Even better, don't use auto-instantiating variables, convenient though they are (and don't use the New keyword in a Dim statement either). You can control when your variables are created and destroyed and it's a best practice. Something like this in a standard module
Sub uftst()
Dim uf As UserForm1
Set uf = New UserForm1 'you control instantiation here
'Now you can change properties before you show it
uf.Caption = "blech"
uf.Show
Set uf = Nothing 'overkill, but you control destruction here
End Sub
Note that if the ShowModal property is set to False that the code will continue to execute, so don't destroy the variable if running modeless.
As Dick suggested, you should be able to stop the behavior by making sure to use me.caption instead of Userform1.caption.
Here's a way you can replicate the issue for those who are curious:
Create a Userform (Userform1) make sure you set ShowModal to false or you won't be able to see this.
In a module add the following:
Option Explicit
Sub ShowUserForm()
Dim uf As UserForm1
Set uf = New UserForm1
End Sub
In UserForm1 list the following code:
Option Explicit
Private Sub UserForm_Initialize()
UserForm1.Caption = "I'm UserForm1!" 'This will call the Initialize method of Userform1 not Me.
Me.Caption = "I'm Me!"
Me.Show
End Sub
Run ShowUserForm. You now have two Userforms with different captions.
Incidentally, if you have an Initialize method like I displayed adding Set uf = Nothing to the ShowUserForm sub actually fails to close either form.

Is it possible to declare a public variable in vba and assign a default value?

I want to do this but it won't compile:
Public MyVariable as Integer = 123
What's the best way of achieving this?
.NET has spoiled us :)
Your declaration is not valid for VBA.
Only constants can be given a value upon application load. You declare them like so:
Public Const APOSTROPHE_KEYCODE = 222
Here's a sample declaration from one of my vba projects:
If you're looking for something where you declare a public variable and then want to initialize its value, you need to create a Workbook_Open sub and do your initialization there.
Example:
Private Sub Workbook_Open()
Dim iAnswer As Integer
InitializeListSheetDataColumns_S
HideAllMonths_S
If sheetSetupInfo.Range("D6").Value = "Enter Facility Name" Then
iAnswer = MsgBox("It appears you have not yet set up this workbook. Would you like to do so now?", vbYesNo)
If iAnswer = vbYes Then
sheetSetupInfo.Activate
sheetSetupInfo.Range("D6").Select
Exit Sub
End If
End If
Application.Calculation = xlCalculationAutomatic
sheetGeneralInfo.Activate
Load frmInfoSheet
frmInfoSheet.Show
End Sub
Make sure you declare the sub in the Workbook Object itself:
Just to offer you a different angle -
I find it's not a good idea to maintain public variables between function calls. Any variables you need to use should be stored in Subs and Functions and passed as parameters. Once the code is done running, you shouldn't expect the VBA Project to maintain the values of any variables.
The reason for this is that there is just a huge slew of things that can inadvertently reset the VBA Project while using the workbook. When this happens, any public variables get reset to 0.
If you need a value to be stored outside of your subs and functions, I highly recommend using a hidden worksheet with named ranges for any information that needs to persist.
Sure you know, but if its a constant then const MyVariable as Integer = 123 otherwise your out of luck; the variable must be assigned an initial value elsewhere.
You could:
public property get myIntegerThing() as integer
myIntegerThing= 123
end property
In a Class module then globally create it;
public cMyStuff as new MyStuffClass
So cMyStuff.myIntegerThing is available immediately.
Little-Known Fact: A named range can refer to a value instead of specific cells.
This could be leveraged to act like a "global variable", plus you can refer to the value from VBA and in a worksheet cell, and the assigned value will even persist after closing & re-opening the workbook!
To "declare" the name myVariable and assign it a value of 123:
ThisWorkbook.Names.Add "myVariable", 123
To retrieve the value (for example to display the value in a MsgBox):
MsgBox [myVariable]
Alternatively, you could refer to the name with a string: (identical result as square brackets)
MsgBox Evaluate("myVariable")
To use the value on a worksheet just use it's name in your formula as-is:
=myVariable
In fact, you could even store function expressions: (sort of like in JavaScript)
(Admittedly, I can't actually think of a situation where this would be beneficial - but I don't use them in JS either.)
ThisWorkbook.Names.Add "myDay", "=if(isodd(day(today())),""on day"",""off day"")"
Square brackets are just a shortcut for the Evaluate method. I've heard that using them is considered messy or "hacky", but I've had no issues and their use in Excel is supported by Microsoft.
There is probably also a way use the Range function to refer to these names, but I don't see any advantage so I didn't look very deeply into it.
More info:
Microsoft Office Dev Center: Names.Add method (Excel)
Microsoft Office Dev Center: Application.Evaluate method (Excel)
As told above, To declare global accessible variables you can do it outside functions preceded with the public keyword.
And, since the affectation is NOT PERMITTED outside the procedures, you can, for example, create a sub called InitGlobals that initializes your public variables, then you just call this subroutine at the beginning of your statements
Here is an example of it:
Public Coordinates(3) as Double
Public Heat as double
Public Weight as double
Sub InitGlobals()
Coordinates(1)=10.5
Coordinates(2)=22.54
Coordinates(3)=-100.5
Heat=25.5
Weight=70
End Sub
Sub MyWorkSGoesHere()
Call InitGlobals
'Now you can do your work using your global variables initialized as you wanted them to be.
End Sub
You can define the variable in General Declarations and then initialise it in the first event that fires in your environment.
Alternatively, you could create yourself a class with the relevant properties and initialise them in the Initialise method
This is what I do when I need Initialized Global Constants:
1. Add a module called Globals
2. Add Properties like this into the Globals module:
Property Get PSIStartRow() As Integer
PSIStartRow = Sheets("FOB Prices").Range("F1").Value
End Property
Property Get PSIStartCell() As String
PSIStartCell = "B" & PSIStartRow
End Property
there is one way to properly solve your question. i have the same concern with you for a long time. after searching and learning for a long time, finally i get a solution for this kind of question.
The solution is that no need to declare the variable and no need to set value to the variable, and even no need VBA code. Just need the "named range" in excel itself.
For example, the "A1" cell content is "hello, world". and we define the "A1" cell a name as "hello", that is, the "A1" cell have a name now, it's called "hello".
In VBA code, we just need use this method [hello], then we can get the "A1" value.
Sub test()
msgbox [hello]
end sub
the msgbox will show "Hello, word".
this way, we get a global variable without any declaration or assignment. it can be used in any Sub or Function.
we can define many named range in excel, and in VBA code we just use [] method to get the range value.
in fact, the [hello] is a abbreviation of the function Evaluate["Hell"], but it's more shorter.
It's been quite a while, but this may satisfy you :
Public MyVariable as Integer: MyVariable = 123
It's a bit ugly since you have to retype the variable name, but it's on one line.