How to change case of method name in VBA? - excel

While learning VBA I did a lot of copy-paste of code from the internet. At some point I have noticed that whenever I use a method which name is "Add", the VBA IDE automatically change it to "add". For example when I type ActiveSheet.PivotTables.Add it's automatically converted to ActiveSheet.PivotTables.add. The same happens with Worksheets.Add which is changed to Worksheets.add. How can I restore default case of a method?
I know that VBA IDE changes variable's case accordingly to how it was declared (see: How does one restore default case to a variable in VBA (Excel 2010)?). But I am not sure if it might be related to my issue.

This will happen because you have declared some variable or function or sub as add in your Project (or in some add-in you might have loaded).
The VBA environment will get the last declared case representation of your variable names/function names and apply them to every instance where they occur.
E.g.
Function Func1()
Dim ART As Double
ART = 1
' in one function
End Function
' Now if I type the following in another function
Function Func2()
Dim aRt as Double
End Function
' the original declaration in the first function becomes
Dim aRt As Double
aRt = 1
This behavior may be desirable or undesirable, but it's definitely by design.
If you want to declare variables and not want them to change case (useful for certain Enums, etc.) you need to do something like
#If False Then
Dim HeWhoseCaseShallNotBeChanged
Dim ADd ' Even methods can be "declared" here. It will have no effect on execution
#End If
This will work for Enums, etc. And you can declare this compiler directive at the top of your module page.

Related

Public variable loses value [duplicate]

I've got a workbook that declares a global variable that is intended to hold a COM object.
Global obj As Object
I initalize it in the Workbook_Open event like so:
Set obj = CreateObject("ComObject.ComObject");
I can see it's created and at that time I can make some COM calls to it.
On my sheet I have a bunch of cells that look like:
=Module.CallToComObject(....)
Inside the Module I have a function
Function CallToComObject(...)
If obj Is Nothing Then
CallToComObject= 0
Else
Dim result As Double
result = obj.GetCalculatedValue(...)
CallToComObject= result
End If
End Function
I can see these work for a bit, but after a few sheet refreshes the obj object is no longer initialized, ie it is set to Nothing.
Can someone explain what I should be looking for that can cause this?
Any of these will reset global variables:
Using "End"
An unhandled runtime error
Editing code
Closing the workbook containing the VB project
That's not necessarily an exhaustive list though...
I would suggest a 5th point in addition to Tim's 4 above: Stepping through code (debugging) and stopping before the end is reached. Possibly this could replace point number 3, as editing code don't seem to cause global variable to lose their values.

How to stop Excel VBA from renaming variables and parameters in unrelated modules and methods?

I noticed unbelievable destructive behavior of Excel VBA. It silently and automatically renames variables and function parameters in unrelated modules when I add, say, a new property to class. Observed on Office Professional Plus 2016 and Windows 10.
For concrete example, I have one module and one class. Module looks like this:
Private Function MyRequests() As Collection
Dim requests As Collection
Set requests = New Collection
Dim row As Integer
row = 3
Dim oRequest As New MyRequest
oRequest.Name = "SomeName"
requests.Add oRequest
MyRequests = requests
End Function
Class MyRequest looks like this:
Private sName As String
Property Let Name(sValue As String)
sName = sValue
End Property
Now the unbelievable part comes. I add new property to the MyRequest class:
Private iRow As Integer
Property Let Row(iValue As Integer)
iRow = iValue
End Property
I save code, go to module and its private function which now looks like this:
Private Function MyRequests() As Collection
Dim requests As Collection
Set requests = New Collection
Dim Row As Integer
Row = 3
Dim oRequest As New MyRequest
oRequest.Name = "SomeName"
requests.Add oRequest
MyRequests = requests
End Function
Notice that row became Row! This silent auto-renaming also happens throughout the VBA code in sheets... Basically everywhere, Excel renamed row to Row!
So, my question is what can I do to stop this insane behaviour?
Naming a variable with the same name as a keyword is not a good practice.
If a keyword is used within scope of [and] a variable with [have] the same name, then the keyword must be qualified with the appropriate VBA object model or the variable takes precedence and the keyword will be interpreted by the compiler as that variable. This results in code that is more error prone and significantly more difficult to maintain.
"So, my question is what can I do to stop this insane behaviour?"
The solution is to not declare a variable with the same name as a VBA keyword.
All variables of equal scope share the same casing. Changing the case at the declaration changes every instance of the variable within scope. Keywords are slightly different, in that they are global but their casing can be changed by a local declaration.
So in any scope, declaring a variable of the same name as a keyword will cause every instance of that word to share the same casing project wide and the change persists after the declaration is deleted.
Keywords are defined by the object model.
Link with list of keywords in VBA object model (the link in comments goes .NET):
https://learn.microsoft.com/en-us/office/vba/language/reference/keywords-visual-basic-for-applications
Link to Excel object model (I do not know where to find an condensed list, but if it is an object, property, or method then it is a keyword): https://learn.microsoft.com/en-us/office/vba/api/overview/excel/object-model
It can be challenging, and even impossible, to avoid every keyword and this is where scope comes into play and I can offer some easy to follow advice
When you find yourself naming a variable with a potential for conflict (like row, column, worksheet) etc.
in all cases, it is better to use a more descriptive name (indexRow, lastRow, rowCounter)
If you can't come up with something suitable, press F2 to open the object model viewer and search for that name. Hopefully the search doesn't come up with any results and you can safely use that name. But if not, look for where the conflict occurs
you will not have a problem if you're creating an Excel project and the conflict is found in the Outlook object. You may even consider removing the reference to the Outlook object from your project
if it's a property name (like Color) or a method (like Resize) then you can use with care (in some cases this is actually the ideal, like when assigning properties and methods to your custom classes)
if it is an object (like Workbook) then you should not use it. The risk is too big.
[I struck through the points of scope - it seems I am rather consistent with my naming conventions and was unaware the variables changed case regardless of scope.
Would never have known if not for Michael's answer]
This problem isn't arising because you're using variable names that match a built-in property. It's arising simply because you're using two different case-spellings of the same variable. If you used iRow in one module and irow in another, you would still get the same issue.
You can't stop this behaviour. The VBA IDE automatically changes the names of subroutines, functions, variables and properties to match the case of the last declaration statement that was edited. It also then remembers the case used for all future instances entered/edited anywhere in code.
When doing this renaming, the IDE doesn't distinguish between local and global variables, or properties of different classes. If you change the case anywhere, it will be updated and used everywhere.
This is actually useful to help prevent accidental code errors. VBA is case-insensitive and using different case would not create different variables. Trying to manage different variables based on case, even in different modules, is not good practice.
If they are local and distinct with no risk of confusion, then you might as well use the same case-spelling anyway. Why have one naming convention in one module and a different naming convention in a different module?
However, if you need to distinguish between variables in different modules, then you should be using different character-spelling anyway and in doing so, the issue would not arise.
This behaviour also helps you to ensure code is being correctly entered as you type, especially if you declare all variables, subroutines etc with at least one capital letter. You are then able to edit all code using only lower case, which reduces effort, and then everything that is correctly spelled will be automatically converted to the declared case. If you type a variable in lower case and it does not automatically change case, then you know immediately that you have mistyped the variable, rather than discovering the error only when you try to compile/run.
Note that the issue that does arise from using built-in properties as names for variables and subroutines is that the automatic changing of case applies equally here too. So if you define a variable as VALue, it will rename the .Value property wherever it is used.
If you then define a subroutine as value, it will rename both the existing VALue variable as well as the .Value property.
If you follow good naming conventions, and don't reuse names anywhere, then you won't have this issue.

Can I define a constant in VBA to be used in Excel worksheet?

Instead of using the same defined name in multiple workbooks, can I define a constant in my xlam file that can then be used in all my workbooks?
For example, in VBA:
Public Const nKilobyte = 1024
Then in a worksheet:
=A1 / nKilobyte
I tried this and I get a #NAME? error. Right now, it looks like a worksheet can refer to functions defined in VBA, but not constants. So as a workaround, I made up the following function:
Public Function vConstant(sName As String) As Variant
Select Case sName
Case "kb": vConstant = 1024
Case "Mb": vConstant = 1024 ^ 2
End Select
End Function
which I can then call in a worksheet as:
=A1 / vConstant("kb")
That works, but an actual constant would be better. Is there a way to do that?
That works, but an actual constant would be better. Is there a way to do that?
Nope.
Excel and VBA are two separate things: they talk to each other using a predefined pipeline, and that interface says public procedures in standard modules are exposed as "macros", and public functions in standard modules are exposed as user-defined functions. In the other direction, VBA gets to talk to Excel through its object model.
The Excel calc engine knows nothing of the VBA runtime context that's holding the runtime values of your global variables and constants: that's purely on the VBA side of things. In fact, it wouldn't be unreasonable to assume a constant doesn't even exist in the compiled code - that's actually the case in C# code: the compiler "burns" the value of constants in-place, at the call sites. I wouldn't be surprised to crack open the compiled p-code and find that all constants are just inlined into their call sites... but that's all just speculation.
For whatever it's worth, that magic-string driven function isn't ideal; callers might end up scratching their heads wondering whether it wants "KB", "Kb", "kB" or "kb" (Option Compare Binary being the default string comparison mechanism in VBA, i.e. case-sensitive), or maybe it was "KiB"? Make sure the supported values are well-documented, and use StrComp with text rather than binary comparison to make the match case-insensitive.
Consider exposing a single function / UDF per constant. That'll eliminate the magic strings and enhance the UDF usability.
Public Function BytesInKB() As Long
BytesInKB = CLng(1024)
End Function
Public Function BytesInMB() As Long
BytesInMB = CLng(1024) ^ 2
End Function

What's wrong with this VBA code?

Sub variable()
Dim data As Integer
Dim square As Integer
Dim num As String
End Sub
Private Sub CommandButton1_Click()
num = InputBox("Give me the number!", "Your number")
data = CInt(num)
End Sub
Private Sub CommandButton2_Click()
square = (data ^ 2)
MsgBox ("The result: " & square)
End Sub
Could anyone help me? Thanks.
The result isn't the right number. That is 0 all the time.
There are two problems with the code: You don't require variable declaration and you your variables are scoped differently than you expect.
In the Visual Basic Editor, choose Tools - Options and check Require Variable Declaration. This will automatically include Option Explicit at the top of any new modules. Unfortunately for existing modules, you'll have to type Option Explicit at the top yourself.
If you require variable declaration, the compiler will tell you when you're using a variable that's not in scope. It would have told you (in your example) Variable not defined and highlighted num (because it's the first one). That will help you identify these types of problems in the future.
Clearly you intended to declare your variables, you just didn't declare them in the right place. Your variable can have three scopes: local, module, global. You always want to use the smallest scope that suits your code. So use local, unless you need to use module. And use module unless global is absolutely necessary. Here's how the scopes work out:
Local - Use Dim inside a procedure (between Sub and End Sub or equivalent). Nothing outside of that procedure can see or change the variable.
Module - Use Private (Dim also works) at the top of the module, outside of any procedures. Now all the procedures in that module can see and use the variable. Procedures in other modules cannot.
Global - Use Public (Global also works) at the top of a standard module (not a class module, like ThisWorkbook, Sheet1, Userform1, or Class1). Every procedure in your project can see and set this variable.
For your case, you should use a module scoped variable. Your event subs are in the same module and they are the only subs using these variables. If a procedure outside of the module needs to use the variable, you may need to move it to a standard module and declare it with Public. But do that as a last resort. There are other options, like passing variables as a arguments, that are better than using global scoped variables.
Two more things: If you use Application.InputBox() you can specify a Type that will ensure the user enters a number. If you want to continue to use the InputBox() function, then CInt will error when the user types anything that can't be coerced to a number. In that case, consider using the Val function.

Return a User Defined Data Type in an Excel Cell

I've searched the web and I've searched the questions here on stackoverflow, but I haven't been able to find a solution.
Here is what I'd like to do:
Suppose I have the following code in a class module named "MyClass"
Option Explicit
Dim var1 as integer
Sub Initialize(v as integer)
var1 = v
End Sub
Function GetVar1()
GetVar1 = var1
End Function
Then I have a UDF in a separate module with the code
Function InitializeMyClass(v as integer) as MyClass
Dim myvar as MyClass
Set myvar = new MyClass
Call myvar.Initialize(v)
Set InitializeMyClass = myvar
End Function
Function GetMyVar(m as MyClass)
GetMyVar = m.GetVar1()
End Function
Now in cell A1 I have "=InitializeMyClass(3)" and in Cell A2 I have "=GetMyVar(A1)". I get #VALUE errors in both cells. This of course is caused by the fact that I am trying to return a user defined data type into cell A1. I feel that this should be possible, but I am not sure how.
Edit: Oh yeah, the question is, "Is there a way for me to return a user-defined data type into a cell and then have it be callable from another UDF in the example I gave above? I don't know if this requires COM or not. If it does, anyone know how I can get started? Ideally, if someone had an example of how this worked, that would be fantastic!"
Another Edit: Here we go, now i know it can be done: read this description, it's not quantitative, but will give you a sense of what they do, http://www.quanttools.com/index.php?option=com_content&task=view&id=19
As the other answers suggest, the literal answer to your question is "no". You can't store anything other than a number, string, boolean, error, etc. in a cell. You can't return anything other than a simple value such as those, or an array, or a range reference, from a UDF.
However, you can do essentially what you want by passing around (and storing in cells) some kind of handle to your objects that is a legal cell value (i.e. "myclass:instance:42"). This is probably what the example you linked to in your edit does. Your code has to be able to interpret the meaning of the handle values and maintain the objects in memory itself, though. This can get tricky if you care about not leaking objects, since there are lots of ways to erase or overwrite handles that you can't detect if you're using VBA to do all this.
I don't have it in front of me right now, but you might want to look at the book Financial Applications using Excel Add-in Development in C/C++ by Steve Dalton:
http://www.amazon.com/Financial-Applications-using-Development-Finance/dp/0470027975/ref=ntt_at_ep_dpt_1
He discusses ways to work with handles like this more robustly with XLL add-ins.
This looks to be a tough cookie. It's a little hokey, but what you could do is have your Initialize function return a name (string), then add the name parameter to the Get function. Basically manipulating the name string instead of the object directly.
The nesting won't work because myvar goes out of scope as soon as the UDF is done executing. There actually may be other problems associated with trying to return an object in a worksheet function (most certainly there are), but even if there weren't the scope problem would still kill it.
You could store a pointer to the object in a cell and get the object via that pointer, but again the scope will kill it. To get the object from the pointer, it would have to remain in scope, so why bother storing the pointer.
Obviously your real life situation is more complex than your example. So the answer is no as to storing objects in cells, but if you explain what you're trying to accomplish there may be alternatives.

Resources