How to destroy an object - excel

It seems that Set Object = Nothing didn't destroy the Fs Object in this code:
Sub Test2()
Dim Fs As New FileSystemObject
Set Fs = Nothing
MsgBox Fs.Drives.Count ' this line works
End Sub
The last line works with no errors!. thats mean Fs Object is still exists, right?.
So how to destroy this Fs Object.

Another way to ensure proper destruction of an object, is to yield its object reference to a With block (i.e. don't declare a local variable):
Sub Test()
With New FileSystemObject
MsgBox .Drives.Count
End With
End Sub
The object only ever exists inside the With block, and when execution reaches the End With token, if you try it with a custom class module you'll notice that the the class' Class_Terminate handler runs, effectively confirming the proper destruction of the object.
As for the As New quirk, as was already explained, if you intend to set the object reference to Nothing inside that scope, don't declare it with As New, because VBA will set the object reference to Nothing, but will also happily ("helpfully") create a new instance for you as soon as you re-reference it again, be it only to verify that the object Is Nothing.
Side note, this (annoying) counter-intuitive behavior is specifically what's behind the reasoning for Rubberduck's Object variable is self-assigned code inspection:
(note: if you've got code you want to inspect, know that it runs much faster in the actual VBE than on the website)
(if it wasn't clear already: I'm heavily involved with the Rubberduck project)

it must have to do with the "declare & instantiate" pattern, most often addressed as a "to be avoided" pattern
if you split them, you get Nothing after setting it to:
Sub Test2()
Dim Fs As FileSystemObject
Set Fs = New FileSystemObject
Set Fs = Nothing
MsgBox Fs.Drives.Count ' this line DOESN'T work
End Sub

This amended code seems to work fine. Seems a nuance with the dim/new on the same line. Hopefully someone else can give a better idea as to the reasoning.
As others have commented, if it is a dim within the sub then it will be collected after the sub completes anyway.
Sub Test2()
Dim Fs As FileSystemObject
Set Fs = New FileSystemObject
Set Fs = Nothing
End Sub

Related

Non-Basic Code in VBE Call Stack - What Does it Mean?

I am just now learning about how to use the Call Stack within the VBE. One question I have that I cannot seem to find an answer for is what it means when, as I am stepping through two subroutines, why there are two instances of [<Non-Basic Code>] appearing in my Call Stack.enter image description here
I am not sure if it is relevant, but I have included my two subroutines I am stepping through if it helps!
Here are my module-level declared variables (with the exception of Option Explicit):
Option Explicit
Dim fso As Scripting.FileSystemObject
Dim NewFolderPath As String
My first subroutine:
Sub UsingTheScriptingRunTimeLibrary()
Dim OldFolderPath As String
NewFolderPath = Environ("userprofile") & "\Documents\Education\Excel\VBA\Test" 'where we I am copying TO
OldFolderPath = Environ("userprofile") & "\Documents\Education\Excel\VBA\Excel VBA Introduction - Wise Owl" 'where we are copying FROM
Set fso = New Scripting.FileSystemObject
If fso.FolderExists(OldFolderPath) Then
If Not fso.FolderExists(NewFolderPath) Then
fso.CreateFolder NewFolderPath
End If
Call CopyExcelFiles(OldFolderPath) 'since we are sending this as an argument to our CopyExcelFiles sub for its StartFolderPath parameter, we do not need to set it in our next sub
End If
Set fso = Nothing
End Sub
Here is my second sub:
Sub CopyExcelFiles(StartFolderPath As String)
Dim Fil As Scripting.File
Dim SubFol As Scripting.Folder
Dim OldFolder As Scripting.Folder
Set OldFolder = fso.GetFolder(StartFolderPath)
For Each Fil In OldFolder.Files
If Left(fso.GetExtensionName(Fil.Path), 2) = "xl" Then
Fil.Copy NewFolderPath & "\" & Fil.Name
End If
Next Fil
For Each SubFol In OldFolder.SubFolders
Call CopyExcelFiles(SubFol.Path)
Next SubFol
End Sub
Anyways, when I am stepping through my first subroutine, I see my first subroutine listed and one instance of (and this is the literal text) [<Non-Basic Code>]. However, when I begin stepping through my second subroutine, I see two instances of [<Non-Basic Code>] as well as both of my subroutines listed.
Any help on this would be greatly appreciated!
As freeflow mentions in comment to you, the call stack shows [Non-Basic Code] whenever an external code is called.
Look at this part:
For Each Fil In OldFolder.Files
...
Next Fil
You are doing a For Each over the OldFolder.Files; who is the one doing the enumerating? It's not obvious when you program VBA, but if you were writing C, something has to implement the enumerator for the For Each statement to work on. In this case, the Scripting.Files object has implemented a hidden enumerator. Thus, when VBA has a For Each, it's calling the hidden enumerator, and getting a item out of it.
This is not the only way. There are other ways you can see a Non-Basic Code -- a common routine is via event handlers. Say you do a MyWorkbook.Save to save the Excel workbook. That will call events on the workbook such as BeforeSave event and so forth. If you have VBA code in the event handlers, you certainly will see a Non-Basic Code between the calls.
Basically, whenever your code has to pass through some external library to execute the code, the call stack will insert Non-Basic Code to let you know that there's a layer between your VBA code and the previous code that triggered it. There are also cases where your VBA code might be accessed by a non-basic Code entirely so it'll be the first one in the call stack, too.

Accessing a form button

I am trying to access a form button...as a test. But when I do so like this; I get the error that it doesn't support this property or method? What is the proper way to go about accessing a button like this?
With ActiveSheet.Shapes("FH_btnHideShowCNC")
MsgBox .Caption
Exit Sub
End With
Because ActiveSheet isn't necessarily a Worksheet, that property wraps your sheet in an Object, and then you basically have to guess what the correct incantations are and cross your fingers that there aren't any typos (Option Explicit can't save you from late-bound code).
Restore early binding as soon as possible, don't chain member calls to an Object or Variant.
Dim sheet As Worksheet
Set sheet = ActiveSheet
With sheet.Shapes("FH_btnHideShowCNC")
'Shapes.Item(String) returns a Shape object, so we're early-bound now.
'...
End With
Now that the interface of the With block variable is known at compile-time, you'll get the list of available members, and the code will blow up at compile-time if you try to invoke a member that doesn't exist (like .Caption), rather than blow up at run-time.
Is this what you are trying?
With ActiveSheet.Shapes("FH_btnHideShowCNC")
MsgBox .TextFrame.Characters.Text
Exit Sub
End With
And MsgBox .Name will give you the name of the button which is FH_btnHideShowCNC

How to Use the Find Function

I have recently Written some code to take an input from a userform text box and search for it in my database. If found I would like it to return the value and insert it into cell A1149; I have written the below code but it gives me error #424 "Object Required". I am very new to VBA so any and all help is greatly appreciated.
Private Sub CMDSearch_Click()
Dim pesquisa As Range
Set pesquisa = Worksheets("Petrobras").Activate.Range("$W:$W") _
.Find(What:=Opp_Num_Search.Value, LookIn:=xlValues, Lookat:=xlWhole).Activate
Worksheets("Petrobras").Range(A1149).Value = pesquisa.Value
UserForm1.Hide
End Sub
Range.Activate doesn't return anything, it's like a Sub procedure:
Public Sub DoSomething()
' does something...
End Sub
If you did Set foo = DoSomething.Something, you'd get the same error: an object is required, otherwise that .Something member call is illegal (except now the error would be at compile-time and not run-time, because of how binding works).
Set pesquisa = Worksheets("Petrobras").Activate...
You don't want to Activate any sheets.
Part of the problem is the implicit late-binding going on: Worksheets returns an Object, so everything you wrote after that, all these chained member calls, can only be resolved at run-time.
Make it early-bound, by declaring an explicit Worksheet variable:
Dim sheet As Worksheet
Set sheet = Worksheets("Petrobras")
Now if you want to activate it, you can do sheet.Activate (but you don't need to). And if you want to get a Range from that worksheet, you can make a .Range member call, and the IDE will now help you do it:
Dim result As Range
Set result = sheet.Range("$W:$W").Find(...)
NEVER chain any member calls to what Range.Find returns. If the search turned up a result, you have a Range object. If it didn't, you have Nothing - and any member call made against Nothing will always raise run-time error 91.
Validate the search result first:
If result Is Nothing Then
MsgBox "Could not find '" & Opp_Num_Search.Value & "' in column W."
Exit Sub
End If
Or:
If Not result Is Nothing Then
sheet.Range("A1149").Value = result.Value
End If
Note that A1149 is a string literal representing a cell address, and as such it must be surrounded with double quotes ("). If it's not in double quotes and it looks like a valid variable name, VBA will treat it as ..a variable... and that will cause yet another error (1004), because Range will be rather unhappy to work with a Variant/Empty value.
To prevent VBA from "declaring" on-the-fly variables with a typo (and causing hard-to-find bugs), make sure you have Option Explicit at the very top of every module in your project.
One last thing:
UserForm1.Hide
This hides the default instance of UserForm1, which may or may not be the current object / instance that's shown - and the form itself has no way to know how it was shown:
UserForm1.Show '<~ shows the default instance
With New UserForm1
.Show '<~ shows a new (non-default) instance
End With
For this reason, you should avoid referring to the default instance in a form's code-behind. Use Me instead:
Me.Hide
That way you're referring to whatever instance of the form is currently shown, whether that's the default instance or not. See UserForm1.Show for more information, tips, pitfalls and common mistakes involving userforms.
Note that Rubberduck has quite a few inspections that can identify and warn you about (and often, automatically fix) most of these problems. Rubberduck is a free and open-source VBIDE add-in project I manage.

How to assign an event to multiple objects with excel vba?

I have ten drop down menus on a worksheet each of which should respond the same to the GotFocus() event.
I have written the following code but I get a run time error (459) - "Object or class does not support the set if events"
In a class called clsPDRinput I have the following:
Public WithEvents inputObj As OLEObject
Public Property Set myInput(obj As OLEObject)
Set inputObj = obj
End Property
Public Sub tbPDRInput_GotFocus()
//Do some stuff...
End Sub
I am then running the following code which is producing the error:
Dim tbCollection As Collection
Public Sub InitializePDRInput()
Dim myObj As OLEObject
Dim obj As clsPDRInput
Set tbCollection = New Collection
For Each myObj In Worksheets("1. PDR Documentation").OLEObjects
If TypeName(myObj.Object) = "ComboBox" Then
Set obj = New clsPDRInput
Set obj.myInput = myObj <-- **THIS LINE THROWS ERROR**
tbCollection.Add obj
End If
Next myObj
Set obj = Nothing
End Sub
I am not sure what is causing this error. One thought I had is that OLEObject is too generic and not every OLEObject supports the GotFocus() event and that is why the code is giving the error message?
I have tried replacing OLEObject with MSForms.ComboBox but that doesn't resolve issue.
Any ideas - have googled for two hours now and come up blank...
EDIT - Update on what I think the issue is...
I did more investigating and here is what the issue is as far as I can tell.
If you declare a variable as OLEObject (as in ...inputObj as OLEObject) then the only events exposed are GotFocus() and LostFocus().
If you declare a variable as MSForms.ComboBox (as in ...inputObj as MSForms.ComboBox) then a variety of events are exposed (e.g. Change(), Click(), DblClick()) but the events GotFocus() and LostFocus() are not exposed
Points 1 and 2 are consistent with the object model in excel. As a result, when I try to assign a ComboBox to my class I get an error (see original post) as the ComboBox does not support the GotFocus() and LostFocus events.
Now for the puzzle. If I add a ComboBox onto a worksheet (using Control ToolBox) and I double click that ComboBox to get to the code behind then all events are exposed, including GotFocus() and LostFocus()!
The below works for me. There were a couple of problem with your code, and comboboxes don't have a GotFocus event, so you'll have to use a different one.
The collection needs to be a global in the module, not part of the class.
I couldn't get this to work using the generic "OLEobject" approach (same error you got).
' ### in the class
Public WithEvents inputObj As MSForms.ComboBox
Private Sub inputObj_Change()
MsgBox "Change!"
End Sub
' ### in a module
Dim tbCollection As Collection
Public Sub InitializePDRInput()
Dim myObj As OLEObject
Dim obj As clsPDRInput
Set tbCollection = New Collection
For Each myObj In Worksheets("Sheet1").OLEObjects
If TypeName(myObj.Object) = "ComboBox" Then
Set obj = New clsPDRInput
Set obj.inputObj = myObj.Object
tbCollection.Add obj
End If
Next myObj
End Sub
Update
I was too focused in making the code compile and someone was nice enough to point out that the answer below is bad juju. So do not use. It does compile, but not a good answer.
I reproduced your error and fixed by changing the following declaration:
Public WithEvents inputObj As OLEObject
to this:
Public inputObj As New OLEObject
Of course, this is a different type of declaration so I'm not sure if it will work for you. It does remove the exception.
I'd also like to note that if you don't have Option Explicit set, you should. There are some variables in your code that are not declared. My guess is that you perhaps modified the code before posting your question.
Just making sure.

Modifying a spreadsheet using a VB macro

I have two spreadsheets... when one gets modified in a certain way I want to have a macro run that modifies the second in an appropriate manner. I've already isolated the event I need to act on (the modification of any cell in a particular column), I just can't seem to find any concrete information on accessing and modifying another spreadsheet (this spreadsheet is located on a different LAN share also... the user has access to both, though).
Any help would be great. References on how to do this or something similar are just as good as concrete code samples.
In Excel, you would likely just write code to open the other worksheet, modify it and then save the data.
See this tutorial for more info.
I'll have to edit my VBA later, so pretend this is pseudocode, but it should look something like:
Dim xl: Set xl = CreateObject("Excel.Application")
xl.Open "\\the\share\file.xls"
Dim ws: Set ws = xl.Worksheets(1)
ws.Cells(0,1).Value = "New Value"
ws.Save
xl.Quit constSilent
You can open a spreadsheet in a single line:
Workbooks.Open FileName:="\\the\share\file.xls"
and refer to it as the active workbook:
Range("A1").value = "New value"
After playing with this for a while, I found the Michael's pseudo-code was the closest, but here's how I did it:
Dim xl As Excel.Application
Set xl = CreateObject("Excel.Application")
xl.Workbooks.Open "\\owghome1\bennejm$\testing.xls"
xl.Sheets("Sheet1").Select
Then, manipulate the sheet... maybe like this:
xl.Cells(x, y).Value = "Some text"
When you're done, use these lines to finish up:
xl.Workbooks.Close
xl.Quit
If changes were made, the user will be prompted to save the file before it's closed. There might be a way to save automatically, but this way is actually better so I'm leaving it like it is.
Thanks for all the help!
Copy the following in your ThisWorkbook object to watch for specific changes. In this case when you increase a numeric value to another numeric value.
NB: you will have to replace Workbook-SheetChange and Workbook-SheetSelectionChange with an underscore. Ex: Workbook_SheetChange and Workbook_SheetSelectionChange the underscore gets escaped in Markdown code.
Option Explicit
Dim varPreviousValue As Variant ' required for IsThisMyChange() . This should be made more unique since it's in the global space.
Private Sub Workbook-SheetChange(ByVal Sh As Object, ByVal Target As Range)
' required for IsThisMyChange()
IsThisMyChange Sh, Target
End Sub
Private Sub Workbook-SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
' This implements and awful way of accessing the previous value via a global.
' not pretty but required for IsThisMyChange()
varPreviousValue = Target.Cells(1, 1).Value ' NB: This is used so that if a Merged set of cells if referenced only the first cell is used
End Sub
Private Sub IsThisMyChange(Sh As Object, Target As Range)
Dim isMyChange As Boolean
Dim dblValue As Double
Dim dblPreviousValue As Double
isMyChange = False
' Simple catch all. If either number cant be expressed as doubles, then exit.
On Error GoTo ErrorHandler
dblValue = CDbl(Target.Value)
dblPreviousValue = CDbl(varPreviousValue)
On Error GoTo 0 ' This turns off "On Error" statements in VBA.
If dblValue > dblPreviousValue Then
isMyChange = True
End If
If isMyChange Then
MsgBox ("You've increased the value of " & Target.Address)
End If
' end of normal execution
Exit Sub
ErrorHandler:
' Do nothing much.
Exit Sub
End Sub
If you are wishing to change another workbook based on this, i'd think about checking to see if the workbook is already open first... or even better design a solution that can batch up all your changes and do them at once. Continuously changing another spreadsheet based on you listening to this one could be painful.

Resources