Scoped to be visible to any module but not outside the VBAProject - vba

I have these function:
Function funcX() As Integer
funcX = 2
End Function
Public Function funcY() As Integer
funcY = 3
End Function
In a separate workbook I can run the following to access both of the above:
Sub xxx()
Excel.Application.Run "'myBook.xlsm'!funcX"
Excel.Application.Run "'myBook.xlsm'!funcY"
End Sub
How can I create a function that is scoped so it is available in all modules of its workbook but is not available outside that workbook?

To simply make the routines in a Standard Module private to the particular VBA Project, declare them as Public and add the statement:
Option Private Module
to the header of the Module.
For other considerations on controlling the visibility of VBA routines see here.

Related

Custom Function Disable in Excel

I have created a custom function that I am using excplicitly in another module in VBA. Function looks something like this:
Function Coverts(ByVal inputString As String) As String
'Coverts code here
End Function
It works perfectly fine in both VBA and Excel UI. However, I do not want it to work or appear in Excel UI as I only want to use it in VBA. Is there a way to do this?
Thanks
Put
Option Private Module
at the top of the module containing your function.
From MSDN:
When a module contains Option Private Module, the public parts, for example, variables, objects, and user-defined types declared at module level, are still available within the project containing the module, but they are not available to other applications or projects.
you can add the keywords Public or Private to your functions, subs or global variable to have that specified.
so if you want to only want this function to be accessible by your code and not in excel sheets you can add private:
Private Function Coverts(ByVal inputString As String) As String
'Coverts code here
End Function
You can make the function inoperable if called from the worksheet by identifying the Application.Caller. If called as a function from the XL UI, this will be Range (i.e. the cell the function is within).
Function Coverts(ByVal inputString As String) As String
If TypeName(Application.Caller) = "Range" then
Coverts = cverr(xlerrna)
exit function
end if
'Coverts code here
End Function

Can you put VBA code in a bare module, outside of Function or Sub?

I'm trying to keep a Collection Class of Classes persistent while a Userform is running so that the form objects they create can still have event handlers.
But if I create any classes for these in subs or functions, their respective classes and event handlers would be cleared at the end of whatever subroutine created it.
I should specify that user input determines how many classes there will be, so I can't just hard code the event handlers into the userform module.
You can use a publicly declared dictionary to hold instances of your class that will be available to your project. You declare variables outside of a function or sub and declare them as Public for other modules and their subs/functions to be able to use them. They stay resident in memory between calls while the application is open.
Consider a class called c_gumball:
Public color As String
Public diameterInches As Double
Public Function getSize(unit As String) As Double
Select Case unit
Case "mm"
getSize = diameterInches * 25.4
Case "cm"
getSize = diameterInches * 2.54
Case "yd"
getSize = diameterInches / 36
End Select
End Function
And then a new module called m_gbmachine:
Public gumballMachine As Dictionary
Public Sub createGumbalMachine()
gumballMachine = New Dictionary
End Sub
Public Sub addGumball(color As String, sizeInInches As Double, nameKey As String)
Dim gb As c_gumball
Set gb = New c_gumball
gb.color = "green"
gb.diameterInches = 1.2
gumballMachine.Add Key = nameKey, gb
End Sub
Public Sub removeGumball(nameKey As String)
gumballMachine.Remove (nameKey)
End Sub
Any module can now use m_gbmachine.gumballMachine dictionary and see what's in it. They can add gumballs using it's functions.
Perhaps in your userform you create a gumball called "gumball2" in your dictioanry and then want to get the color property of "gumball2" in the gumballMachine dictionary, you could do:
Public Sub button_Click()
'add gumball 2 to the machine
m_gbmachine.addGumball "green", 1.2, "gumball2"
End Sub
Public Sub someFormRoutine()
'msgbox the color of gumball 2
MsgBox m_gbmachine.gumballMachine("gumball2").color
End Sub
You can go deeper and change this module over to a class of it's own and have many gumball machine instances as well.

Declaring a Sub/Function in another module accessible by other modules but not by user

I have a module ModuleA containing a Public function that the user will use on the spreadsheet:
Public Function UserCanSeeThis() As String
UserCanSeeThis = "Hello, " & UserCannotSeeThisButModuleACan() & " user"
End Function
I have a module ModuleB which contains a function that I want to use in ModuleA, but I don't want the user to see in the list of available functions when writing = into a cell or when reading the formulae dictionary:
Function UserCannotSeeThisButModuleACan() As String
UserCannotSeeThisButModuleACan = "my dear"
End Function
How should I declare the function in ModuleB so that ModuleA can see it, but the user cannot?
p.s. I've searched the site and only found the "solution" for Sub, I can declare the Sub as Private and then call it from the other module with Application.Run "mySub".
But I was hoping there was some more developed concept of "friendship" in VBA though.
Specify Option Private Module at the top of ModuleB; public members won't be (visibly) exposed to the user, but will be readily available from anywhere inside the VBA project.
Your Private+Application.Run hack is.. well, a hack. Don't do that - make/leave public members Public, and hide the module from the macros list with Option Private Module.

VBA global class variable

My obstacle is trying to get multiple subs to recognize class variables. When I try to declare them globally, I get a compile error: "Invalid outside procedure". Then, when I run a public function or sub to declare the variables, they remain undefined in the other subs. I want multiple subs to recognize the variables because their values are supposed to be altered via UserForm, and then utilized in a different sub.
If it could work in this manner, great, but I understand that my design could fundamentally be flawed. Please advise!
This is my Class definition, inserted as a Class module named "cRSM":
Option Explicit
Private pName As String
Private pDesiredGrowth As Double
'Name of RSM
Public Property Get Name() As String
Name = pName
End Property
Public Property Let Name(Value As String)
pName = Value
End Property
'Growth property
Public Property Get DesiredGrowth() As Double
DesiredGrowth = pDesiredGrowth
End Property
Public Property Let DesiredGrowth(Value As Double)
If Value > 0 And Value < 1 Then
pDesiredGrowth = Value
End If
End Property
This is invalid procedure error (which I put in the Global Declarations section):
'Bedoya
Dim Bedoya As cRSM
Set Bedoya = New cRSM
Bedoya.Name = "Bedoya"
And this is the "variable not defined error" (within a private sub):
Private Sub Add_Click()
**Bedoya.DesiredGrowth** = Txt2.Value
Thank you for your time
In a standard module (I name mine MGlobals), put
Public Bedoya As cRSM
Then in another standard module (I name mine MOpenClose), put
Sub Initialize()
If Not Bedoya Is Nothing Then
Set Bedoya = New cRSM
End If
End Sub
Any default properties you want set should be set in the Class_Initialize procedure. In any procedure that you want to use Bedoya, use
Initialize
and it will instantiate the global variable if necessary. The only difference between this and the New keyword is that you can't accidentally instantiate the variable with this method. You either call Initialize or you don't. A lot of VBA developers use New, but almost never do for that reason.
If I understood well You want a global object.
You can put the declaration in module like
public Bedoya As cRSM
then you have create the object ... you can use a global event inside the Workbook like
Private Sub Workbook_Open()
Set Bedoya = New cRSM
Bedoya.initialize("Bedoya") 'a method to initialize private variables
End Sub
Now you can use the global object. You have to restart the excel file or run this method manually.
Is not good style to use global variables, but sometimes is the more easy to do :P
What you want to do nowadays is done using singleton Software Pattern, but this is for other day hehehe

VBA event raise only once (intermittent)

Context
My VBA code often replace worksheets inside the Workbook. Therefore I can't use code directly in the worksheet module as it would be eventually deleted in the process.
I use a user-defined class to handle my events (strongly inspired from Chip Pearson's withevents article)
Public WithEvents ws As Worksheet
Private Sub ws_Activate()
If ActiveSheet.Name = FREEBOM_SHEET_NAME Then
Call FREEBOM_Worksheet_Activate_Handler
End If 'ActiveSheet.Name = FREEBOM_SHEET_NAME
End Sub
Private Sub ws_Change(ByVal Target As Range)
'MsgBox Target.Parent.Name
If Target.Parent.Name = FREEBOM_SHEET_NAME Then
Call FREEBOM_Worksheet_Change_Handler(Target)
End If 'Target.Parent.Name = FREEBOM_SHEET_NAME
If Target.Parent.Name = BOM_SHEET_NAME Then
Call BOM_Worksheet_Change_Handler(Target)
End If 'Target.Parent.Name = BOM_SHEET_NAME
End Sub
The class is being instantiated when the workbook is opened.
Private Sub Workbook_Open()
Dim WSObj_FreeBOM As FreeBOM_CWorkSheetEventHandler
Dim WSObj_BOM As FreeBOM_CWorkSheetEventHandler
If Freebom_EventCollection Is Nothing Then
Set Freebom_EventCollection = New Collection
End If
Set WSObj_FreeBOM = New FreeBOM_CWorkSheetEventHandler
Set WSObj_FreeBOM.ws = Sheets(FREEBOM_SHEET_NAME)
Set WSObj_BOM = New FreeBOM_CWorkSheetEventHandler
Set WSObj_BOM.ws = Sheets(BOM_SHEET_NAME)
Freebom_EventCollection.Add Item:=WSObj_FreeBOM, Key:=Sheets(FREEBOM_SHEET_NAME).Name
Freebom_EventCollection.Add Item:=WSObj_BOM, Key:=Sheets(BOM_SHEET_NAME).Name
End Sub
During my reading on the subject, I saw that linking your object to a public collection (the declaraiton is in another module (an ordinary module - not a Worksheet module and not a Class module). : Public Freebom_EventCollection As Collection would keep my instance alive even if the execution leaves the scope of the current initianlization function.
Problem Description
In most scenario, I will get only one ws_change event being raised. After that, the sheet behave as if there is no event handler in my code. Nothing is being raised, not just the worksheet events.
I have look at Application.EnableEvents but it is always set to True after the first run.
Also, when I use the build in Private Sub Worksheet_Change(ByVal Target As Range) function it worked well.
To me it is probably linked to the fact that I use a class and it is not staying alive after the first run. But then, I do not know what I am doing wrong.
Thank you in advance for you time and help in this matter.
you need to declare a module level Public instance of the Collection in an ordinary module (not a Worksheet module and not a Class module). You may as well put the code to manage the collection there as well and simply have calls from the event handlers of the worksheet modules. You may need to re-initialise the collection whenever you delete a sheet as this will probably trigger a re-compile and reset your project, which will terminate your objects.
Once you have the collection in the standard module, you can monitor its life cycle by adding a watch (SHIFT-F9 in VBE). Then you can keep track of exactly what is going on.