Hide VBA procedures from Excel application but not from other projects - vba

I know this is a long shot, but with the limitations in "Option Private Module" and even worse "Private Sub/Function", does anyone know if there is a way of hiding VBA procedures from the Excel application but not from other projects?
I have an XLAM with a subset of reusable functionality that I like to include and reference from new Excel projects, but using "Option Private Module" hinders this and if I omit it, a bunch of unusable or obscure functions and subs become visible and available to the application.

Convert your standard modules in the XLAM to class modules (set to
Public Not Creatable);
Create an additional Class Module that returns an instance (with a
bit of additional work, the instance) of each such module; and
Create a single standard module with one property that returns the instance of the main class-entry module.
Class1:
Option Explicit
Public Sub IAmInvisible()
End Sub
ModuleEntry:
Option Explicit
Private mClass As New Class1
Public Property Get TheClass() As Class1
Set TheClass = mClass
End Property

Related

Public vs Private/Dim in Excel VBA

I could use some help in understanding using Public vs Dim in a module in Excel 2013 VBA.
First I want to say I did find this great post with excellent definitions (see link below), but no examples and I could use some examples of how I could apply the Public Variables to my project. Also I am a little confused on when I would need to use the Option Private Module; would I need to use that on each module I have or just the module that holds the below code?
stackoverflow descriptions difference between Public/Private
What I would like to do is set this up in a Standard Mod so I dont have to continue setting variables for worksheets through all of my UserForms that use the same naming convention for Worksheets they reference.
Sub PubVar()
Public wb As Workbook
Public wsSI As Worksheet
Public wsRR As Worksheet
Public wsCalcs As Worksheet
Public wsNarr As Worksheet
Public wsEval As Worksheet
Public wsUW As Worksheet
Public wsLVBA As Worksheet
Set wb = Application.ThisWorkbook
Set wsSI = wb.Sheets("SavedInfo")
Set wsCalcs = wb.Sheets("Calcs")
Set wsNarr = wb.Sheets("Narrative")
Set wsEval = wb.Sheets("EvalCL")
Set wsUW = wb.Sheets("UWCL")
Set wsLVBA = wb.Sheets("ListsForVBA")
End Sub
Thank you for your assistance.
Option Private Module
Option Private Module should be used in any standard module that doesn't mean to expose its public members to Excel as macros (i.e. Public Sub procedures) or User-Defined-Functions (i.e. Public Function procedures).
Without this option, a standard module's public parameterless Sub procedures appear in Excel's list of available macros, and public Function procedures appear in Excel's cell "intellisense" as available worksheet functions.
Note that this merely hides a module's members from the macros list: if you type the exact name of a "hidden" procedure, Excel will still run it.
Dim vs Private vs Public
Dim is a keyword you use for declaring local variables, inside a procedure scope. The keyword is also legal for declaring private, module-level variables, but then you might as well use Private.
When used for declaring module-level variables, Private makes that variable only accessible from within the module it's declared in.
When used for declaring module-level variables, Public makes that variable accessible from anything that has access to the module it's declared in - in a standard module, that means the variable is effectively Global. In a class (/document/userform/anything else) module, it means the variable holds instance state and is accessible from anything that has access to an instance of that class. Classes that have a predeclaredId, such as UserForm classes, all have an instance that's globally accessible: avoid storing instance state in this default instance.
Use Worksheet.CodeName
Set wb = Application.ThisWorkbook
Set wsSI = wb.Sheets("SavedInfo")
Set wsCalcs = wb.Sheets("Calcs")
Set wsNarr = wb.Sheets("Narrative")
Set wsEval = wb.Sheets("EvalCL")
Set wsUW = wb.Sheets("UWCL")
Set wsLVBA = wb.Sheets("ListsForVBA")
ThisWorkbook is the workbook you're looking at - the one that contains your VBA code. The ThisWorkbook identifier is globally accessible, and Application.ThisWorkbook is merely a pointer to that object.
Use ThisWorkbook over Application.ThisWorkbook, unless you've declared a local variable and named it ThisWorkbook - then that local variable would be shadowing the global identifier; don't do that. There shouldn't be any reason to need to qualify ThisWorkbook with Application.
Now, if any of these worksheets exist at compile-time in ThisWorkbook, then you don't need any of these variables. Find each sheet in the Project Explorer (Ctrl+R), then hit F4 and give its (Name) property a meaningful identifier name.
So if you rename Sheet1 to SavedInfoSheet, then you can access SavedInfoSheet from anywhere in the code, and you don't ever need to dereference it from the Workbook.Sheets (or better, Workbook.Worksheets) collection. The reason for this is that VBA automatically creates a global-scope identifier by the name of whatever identifier you put as the (Name) property of a Worksheet module.
If the sheets don't exist at compile-time (i.e. they're created at run-time), then you don't need these variables either, because the code that created them should already have that reference:
Set theNewSheet = theBook.Worksheets.Add
Then you can (and should) pass these worksheet object references around, as parameters, as needed.
There is no worksheet.
What I would like to do is set this up in a Standard Mod so I dont have to continue setting variables for worksheets through all of my UserForms that use the same naming convention for Worksheets they reference
Your forms are running the show. The code that fires them looks like this:
UserForm1.Show
Like any UI, forms are responsible for collecting user input, and showing data to the user. If you find yourself writing userform code-behind that accesses a dozen worksheets (and/or worse, makes them public fields), you're making your form much, much more complicated than it needs to be, and you're treating a full-fledged object as a mere container for procedures, by making its default instance stateful.
This article goes in details about how to fix that. This article pushes the concept further and allows back-and-forth communication between the view and the presenter, and has a download link with a simple example to study (disclaimer: I wrote these articles, and the accompanying example code).
UserForm code done right, looks extremely simple, and is responsible for so little logic, it's boring. In fact, it's not responsible for any logic beyond presentation - all a UserForm should do, is respond to control events, relay control state to some model, and if application logic needs to be executed before the form is closed (e.g. if a command button is clicked but the form should remain open), then it fires an event, and the calling code ("presenter") handles it by triggering the logic that needs to run.
When the dialog is okayed, or when it relays an event to the presenter, code outside the form's code-behind is executed to to the work: the form never needs to know anything about any worksheet.
You really should only need Option Private Module if you are making your own Excel Add-In (*.XLAM file). It allows a module's Public variables to be visible to other modules within the Add-In's own project but keeps them from being visible/callable by other worksheets that use your AddIn.
As for the rest, it's not clear what you are asking. The question you linked has a good explanation of Public vs Dim/Private. Private/Dim variables cannot be seen by the VBA code in different Modules, Forms or Classes in the same VBA project. You use public if you want all of your VBA code to be able to see/call it. You use private/dim if you want it to only be visible/callable from within it's own module.
But generally you want to be judicious with what you make public, both because it can give you too many global names (confusing), can cause problems with duplicate names (problematic, can be addressed with naming standard) and most of all because it can lead to confusing/obscure bugs and horrendous debugging problems (because any public variable change could cause any other code that can see it to behave differently). And some things are worse than others:
Public Subs OK in a module, but better in a Class
Public Function Usually fine, especially if its a true function
Public Const No problem, belongs in modules
Public Variables Very Bad. *UNLESS ...*
Public variables are the ones that good coders worry about. They are to be avoided, UNLESS they are what we call "Read-Only Variables". These are variables that you set only once, and never change again. As long as you follow that rule, they are effectively constants and are OK (though read-only static properties would be better, but VBA has too many limitations in this regard).
Finally If you want to use named variables for all of your worksheets that are visible/useable from all code, but you only have to setup once, that is what Public is for, but the declaration ("Public ") needs to be at "the Module Level" which means, in a module, but outside of any subroutine or function. Like this:
Public wb As Workbook
Public wsSI As Worksheet
Public wsRR As Worksheet
Public wsCalcs As Worksheet
Public wsNarr As Worksheet
Public wsEval As Worksheet
Public wsUW As Worksheet
Public wsLVBA As Worksheet
' sets all of the Worksheet variables
Sub PubVar()
Set wb = Application.ThisWorkbook
Set wsSI = wb.Sheets("SavedInfo")
Set wsCalcs = wb.Sheets("Calcs")
Set wsNarr = wb.Sheets("Narrative")
Set wsEval = wb.Sheets("EvalCL")
Set wsUW = wb.Sheets("UWCL")
Set wsLVBA = wb.Sheets("ListsForVBA")
End Sub

VB.NET: no warning when returning object that is not an instance of function's return type

We have this:
Friend NotInheritable Class ConcreteGraphFactory
Inherits AbstractGraphFactory
Public Shared ReadOnly Instance As New ConcreteGraphFactory()
Private Sub New()
MyBase.New()
End Sub
Friend Overrides Function Create() As AbstractGraph
Return New ConcreteGraph()
End Function
Private NotInheritable Class ConcreteGraph
Inherits AbstractGraph
Private ReadOnly Question1 As New Question("Why isn't this showing a warning?")
Public Overrides Function GetRoot() As IRoot
Return Question1 '<---HERE
End Function
Public Sub New()
End Sub
End Class
End Class
And I have IRoot:
Friend Interface IRoot
Inherits IQuestion
Function GetContainer() As AbstractGraph
End Interface
And finally Question:
Public Class Question
Implements IQuestion
' code....
End Class
Why would VS not show a warning? Question does not implement IRoot...
If you want the compiler to give an error there, then you need to set Option strict to On. You can do that on the Compile tab of the project's Properties. Or add Option Strict On to the top of the file that contains this code.
Here are a few pages that have more details about what Option Strict means.
http://support.microsoft.com/en-us/kb/311329
https://msdn.microsoft.com/en-us/library/zcd4xwzs.aspx
Option Strict Off means that the Visual Basic compiler doesn't enforce strict data typing. It will try to do implicit type conversions and throw run time errors if that can't be done.
I didn't think it had anything to do with IRoot being an interface, but after trying it out it looks like it does. If GetRoot returned a class that Question didn't inherit from, then you would get a compiler error even with Option Strict off.
Running with Option Strict off actually makes some things easier, especially when dealing with late bound COM objects. For the most part, you don't have to worry about type casts when writing code.
However, it's also one of the reasons many people don't like VB.NET. Personally, I liked it when I was working with it, but it's been long enough now that it does seem strange that the compiler wouldn't be doing all the strict type checking for you. I could always tell when some VB code had been generated via a conversion tool from C# because it would have a bunch of DirectCast calls that you wouldn't see in code that a VB developer had written.
When C# came out with the dynamic keyword in 2009, the VB.NET developers were thinking, "Meh. We've always been able to write code without worrying about types." Of course, VB.NET wasn't the same as dynamic in C#, but many of the early dynamic code examples were showing things that you could already do in VB with option strict turned off.

Add Public Methods to a Userform Module in VBA

Is it possible to call a public sub located in a UserForm from a Class Module? I want to put a callback in the Form Module but I can't seem to get it to expose.
Is this a fundamental limitation of UserForms in VBA?
It is exposed inside the UserForm Code Module, I can see it in the intelisense for the Me object, but I can't seem to access it from outside the Form Module.
The real answer to my question is to have a better understanding of UserForms and since I could not find a good reference for that I thought I would answer my own question to share my learnings.
Thanks to #Dick Kusleika for the key insight!
First of all, this is not a UserForm:
It is no more a Form than a Class Module is a variable.
UserForm1 is a Class Module with a GUI and with the following default, inherited properties
These properties are like a standard Interface that is common to all Form Class Modules and therefore instances. The Name property is in parentheses because it is not the name of the object, it is the name of the the Type that is used to declare variables to instantiate the particular Form Class.
More properties and methods can be added by the user at design time and this is done in exactly the same way as a Class Module.
For example, in a Form Module...
Option Explicit
Dim mName As String
Property Let instName(n As String)
mName = n
End Property
Property Get instName() As String
If Len(mName) = 0 Then mName = Me.Name
instName = mName
End Property
In this example, the Form Class Name is used as the default Instance Name.
When you add Controls to the form, its like graphically adding
Public WithEvents controlName As MSForms.ControlType
...in a Class Module.
The Methods inherited in the standard interface include one called Show.
You can create an instance of a form using UserForm1.Show and this is very confusing and misleading. To me it implies that you are showing the Object called UserForm1 but you are not. I don't know why you would want to use this method because, apart from being confusing, it does not deliver any direct reference to the object created. Its a bit like Dim v as New Type only worse, because there is no referencing variable.
You can instantiate a Form Class in exactly the same way you can a Custom Class object and then use the show method to deploy it...
Dim f As UserForm1
Set f = New UserForm1
f.Show
For me, this is the preferred method.
You can add custom properties and controls to the UserForm1 Class and you can give it a meaningful name when creating it, but you can also reference it using the standard UserForm interface.
For example
'In a Class Module
Dim mForm as UserForm1
Property let Form(f as MSForms.UserForm)
Set mForm = f
End Property
For me, after understanding the above, all of my confusion about UserForms and my frustration at not being able to find a decent reference disappears. I just treat them as Class Modules and its fine.
The only difference between a Userform and Class Module is that a Userform has a UI element that a Class Module doesn't. So a Userform is just a special type of Class Module. That means that Public Subs inside a Userform behave just as they do in any other class - as a method of the class.
To access a Public Sub inside a class module (such as a userform), you need to instantiate the class, then call the method.
Dim uf1 As UserForm1
Set uf1 = New UserForm1
Uf1.MyPublicSub

.net assembly does not appear in Excel's References

I'm following Rich Newman's guide to using .net code assemblies in Excel. I have made a small test class called MyPro[p in a project called MyProperty that looks like this:
Imports System.Runtime.InteropServices
Public Class MyProp
Public Function GetData() As String
Return "Hello World"
End Function
End Class
It compiles fine, puts a CLSID into regedit, and (after browsing to find the TLB) allows itself to be added to Excel's References. However, I can't actually use it. I tried this in VBA:
Private Sub test()
Dim test As New MyProperty.MyProp
MsgBox test.GetData()
End Sub
Which returns:
"Class does not support Automation or does not support the expected interface"
I assume that the error means that it can't find GetData or I'm calling it incorrectly. I have re-added the TLB, with no effect.
Any ideas?
The problem has to do with the order of operations in the bindings. You can make this work by DIMming the object then Newing it on a separate line.

Public variables are not REALLY public in VBA in Forms

Below is a question that I will answer myself, however it caused a GREAT deal of frustration for me and I had a lot of trouble searching for it on the web, so I am posting here in hopes of saving some time & effort for others, and maybe for myself if I forget this in the future:
For VBA (in my case, MS Excel), the Public declaration is supposed to make the variable (or function) globally accessible by other functions or subroutines in that module, as well as in any other module.
Turns out this is not true, in the case of Forms, and I suspect also in Sheets, but I haven't verified the latter.
In short, the following will NOT create a public, accessible variable when created in a Form, and will therefore crash, saying that the bYesNo and dRate variables are undefined in mModule1:
(inside fMyForm)
Public bYesNo As Boolean`
Public dRate As Double
Private Sub SetVals()
bYesNo = Me.cbShouldIHaveADrink.value
dRate = CDec(Me.tbHowManyPerHour.value)
End Sub
(Presume the textbox & checkbox are defined in the form)
(inside mModule1)
Private Sub PrintVals()
Debug.Print CStr(bYesNo)
Debug.Print CStr(dRate)
End Sub
However, if you make the slight alteration below, it all will work fine:
(inside fMyForm)
Private Sub SetVals()
bYesNo = Me.cbShouldIHaveADrink.value
dRate = CDec(Me.tbHowManyPerHour.value)
End Sub
(Presume the textbox & checkbox are defined in the form)
(inside mModule1)
Public bYesNo As Boolean`
Public dRate As Double
Private Sub PrintVals()
Debug.Print CStr(bYesNo)
Debug.Print CStr(dRate)
End Sub
mModule1 will work perfectly fine and, assuming that the fMyForm is always called first, then by the time the PrintVals routine is run, the values from the textbox and checkbox in the form will properly be captured.
I honestly cannot possibly fathom what MS was thinking with this change, but the lack of consistency is a huge suck on efficiency, learning idiosyncracies like these, which are so poorly documented that a Google search in 2013 for something that has likely been around for a decade or more is so challenging to search.
First comment:
Userform and Sheet modules are Object modules: they don't behave the same way as a regular module. You can however refer to a variable in a userform in a similar way to how you'd refer to a class property. In your example referring to fMyForm.bYesNo would work fine. If you'd not declared bYesNo as Public it wouldn't be visible to code outside of the form, so when you make it Public it really is different from non-Public. – Tim Williams Apr 11 '13 at 21:39
is actually a correct answer...
As a quick add-on answer to the community answer, just for a heads-up:
When you instantiate your forms, you can use the form object itself, or you can create a new instance of the form object by using New and putting it in a variable. The latter method is cleaner IMO, since this makes the usage less singleton-ish.
However, when in your userform you Call Unload(Me), all public members will be wiped clean. So, if your code goes like this:
Dim oForm as frmWhatever
Set oForm = New frmWhatever
Call oForm.Show(vbModal)
If Not oForm.bCancelled Then ' <- poof - bCancelled is wiped clean at this point
The solution I use to prevent this, and it is a nice alternative solution for the OP as well, is to capture all IO with the form (i.e. all public members) into a separate class, and use an instance of that class to communicate with the form. So, e.g.
Dim oFormResult As CWhateverResult
Set oFormResult = New CWhateverResult
Dim oForm as frmWhatever
Set oForm = New frmWhatever
Call oForm.Initialize(oFormResult)
Call oForm.Show(vbModal)
If Not oFormResult.bCancelled Then ' <- safe
There are other limitations to Public within Excel VBA.
MSoft documentation in learn.microsoft.com states that public variables are global to the VBA project - it's not true.
Public variables are only global to the workbook within which they are declared, and then only across standard modules. Public variables declared within workbook code are not visible in standard modules, even though standard module sub's are - which are defined to be public.
Public variables declared in one workbook's standard modules are certainly not accessible from other workbooks in the same VBA project, contrary to the MSoft documentation.