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.
Related
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.
Problem
I have an excel file with a quite complicated VBA code behind and I am having some problems to track back and debug the errors.
Sometimes a function B gets a value that it should not get and crashes, but at this point it is not possible for me to know which other funciton called that function B, and the anylys process to finding it out is very much time consuming.
My solution
The solution could be transform all the functions I have in the following way:
Sub foo1(variable1 as string)
'do whatever
End Sub
into
Sub foo1(variable1 as string, inforvariable as string)
'do whatever
End Sub
Infovariable being the name of the function calling.
This is obviously very time consuming (refactoring everything). Besides I dont know neither how to access the name of the "sub" of funcion of the code calling, something like:
infovariable = self.name
call foo1(variable1, inforvariable)
Does not seems to exists in VBA
Some ideas?
I have a look here, here, and here
Debug your macro, put a breakpoint on the line with the problem (in case the line gets called too often, add a condition only to break upon that condition), and view the call stack (keyboard shortcut Ctrl+L).
I am running an Excel 2010 macro that opens another workbook and removes a few code modules (a form and a BAS module). After that it re-imports them as an updated version. This is the code:
For Each x In destination_wb.VBProject.VBComponents
If LCase(x.Name) Like LCase("frmCCLogin*") Or _
LCase(x.Name) Like LCase("modCQ_test*") Then
destination_wb.VBProject.VBComponents.Remove (x)
Next
I have no problem with the import but the remove process doesn't always work as expected. For some reason the BAS (modCQ_test.bas) module is not always removed. As a result, when I re-import, a new duplicated module is created ending with a "1" (i.e. modCQ_test1.bas).
I could see that many people experienced the same problem however, none of the proposed solutions worked for me. Not sure why this is happening?
Please advise.
If you can use the exact name of the module, you can write something like:
Public Sub RemoveComponent(ByVal Book As Workbook, ByVal Name As String)
On Error Resume Next
With Book.VBProject.VBComponents
Call .Remove(.Item(Name))
End With
End Sub
If you're stuck with wildcard matching (i.e. SomeName*) you could iterate the VBComponents collection and cache the names into a collection or array or whatever and call the function above for each name matched.
Additionally, if you wish to enumerate the VBComponents collection and remove like your code sample, I recommend that you go in the reverse order.
So something like:
Public Sub RemoveComponent1(ByVal Book As Workbook, ByVal NameSearch As String)
Dim oCompS As VBComponents
Dim oComp As VBComponent
Dim i As Integer
Set oCompS = Book.VBProject.VBComponents
For i = oCompS.Count To 1 Step -1
Set oComp = oCompS(i)
If oComp.Name Like NameSearch Then Call oCompS.Remove(oComp)
Next
End Sub
Problem is resolved. This simple line of code that hides the destination workbook, fixed the duplication issue in my case:
destination_wb.Windows(1).Visible = False
After this you can remove, then add the components. No duplication will occur.
I have experienced exactly the same phenomenon and it drove me mad for weeks already.
I do check whether the Code Module had definitely been removed directly after the removal and although it had vanished from the VBE's Project View, it still exists and consequently the subsequent import creates a Code Module named xxx1.
Any of the hints given proved not to be reliable on the long run and thus are nothing but guesses. Since the phenomenon is unpredictable as mentioned you never can really tell what did the trick.
As time (some years) has passed I do now have an answer for the phenomenon and a solid and stable solution.
In order not to "cut off the branch you sit on" one will have to envoke another Workbook/VB-Project for deleting and re-importing a Component.
Even another VB-Project performing the task will have to consider that the Component is definitely removed when the code which removed it has "finished".
Conclusion: Rename, Remove, Import, all performed by a VB-Project invoked via "Run ...." will do the trick.
For VBA functions and most programming in general i understand general program execution to occur something like this...
Inside a sub procedure if you call a function (it should return a value), in this case it would return a value to variable num.
To expand this means that execution reaches the line where I call the function, and should then skip down to where the function is written, and go through executing each line inside that function.
This is how it has worked for me previously when I use the F8 key to highlight and follow the code execution line by line.
The problem
The problem is when execution reaches the line inside the sub procedure where the function is called the function is just skipped over and execution doesn't go inside the function and run each line inside it.
(and I should say when this happened I had 95% of a working program, and i've tried re-writing the function, calling other functions).
But whenever a function is called execution of the code doesn't go into the function itself it just skips over it, and the variable that holds the result of the function therefore is left empty.
I've tried creating breakpoints at the beginning of the function and when the function is called in the sub procedure however this hasn't worked.
At the very least execution should get to the definition of the function (Function FirstRow() etc) and throw and error but it's not doing that.
Sub Main
Dim num as double
Dim sheet_name as string
num = FirstRow(sheet_name)
End Sub
Function FirstRow(sheet as string) as double
select case sheet '<<----- execution never gets inside the function
case "sheet"
FirstRow = 8
case "sheet2"
FirstRow = 12
end select
End Function
Put a value in sheet_name and try again.
What caused some functions to stop working (and execution of code to not reach the function definition or move inside it) was unloading the form.
All the functions and sub procedures were defined in a module which is part of the form.
Therefore you can only access this module by clicking on the controls on the form.
This meant that I only had one folder FORMS - which had one form inside it, and didn't have a Modules Folder which would normally appear in the VBA Project section.
Therefore I can either:
1) wait until all the data processing has been done before unloading the form right at the last minute
or
2) move the functions to a new module (External to the Form in the MODULES folder) so when the fuctions and sub procedures are called they don't rely on the form being open.
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.