VBA runtime error 91: Object variable not set? - vba

Why is this not working? Run time error 91?
Dim fornameCurr As String
Dim surnameCurr As String
Dim rowCurr As Long
rowCurr = 13
fornameCurr = Activesheet.Cells(rowCurr, 1) << ERROR HERE
surnameCurr = Activesheet.Cells(rowCurr, 2)

Runtime error 91 suggests that ActiveSheet Is Nothing (because ActiveSheet is the only presumed Object in the few lines of code you've provided, and certainly the only Object on the line raising the error).
Several scenarios I can think of might contribute to this:
The code is being called from an Add-In, in Excel or another application, and no Excel workbooks are open. In this case, there is no ActiveWorkbook and also no ActiveSheet.
The code is being executed from another application (Outlook,
Access, Word, PowerPoint, etc.), but you're using the Excel built-in
ActiveSheet which doesn't exist in the other applications.
You've shadowed the built-in ActiveSheet with Dim Activesheet As
Worksheet and neglected to assign a worksheet object to that variable, so it's Nothing by default.
Resolutions:
Add logic to check for presence of Workbook object, etc.
Declare a Worksheet object variable and assign to it.
Prefer to avoid shadowing existing built-in names, ensure assignment to the object variable

Related

VBA code help, issue with ranges [duplicate]

This is part of a larger code, but this snippet isn't working. I'm trying to just set two cells equal to each other, but it's not working. When I use the .Range("v1_copy"), the code runs, but when I name that range and place it as a variable (myCopyRange), the code doesn't run and I get the error: Compile error: Method or data member not found. Any help would be appreciated!
Sub copy_paste_test()
Dim myCopyRange As Range
Dim myPasteRange As Range
Dim myWS1 As Worksheet
Dim myWS2 As Worksheet
Set myWS1 = Sheets("Sheet1")
Set myWS2 = Sheets("Sheet2")
myCopyRange = Range("v1_copy")
myPasteRange = Range("v1_paste")
'myWS2.Range("v1_paste").Value = myWS1.Range("v1_copy").Value
' This line works, but the below line doesn't
myWS2.myPasteRange.Value = myWS1.myCopyRange.Value
' This should be the exact same, just substituting the variable, but doesn't work
End Sub
You're missing the Set keyword for your Range object reference assignments to myCopyRange and myPasteRange.
But for retrieving a named range, the best place to go if you want fully explicit code that does what it says and says what it does, is to dereference the Name from the appropriate Names collection.
If the names are workbook-scoped, qualify with a Workbook object - here a book object variable, but depending on needs ActiveWorkbook or ThisWorkbook work just as well:
Set myRange = book.Names("name").RefersToRange
If the names are worksheet-scoped, qualify with a Worksheet object - here a sheet object variable, but ActiveSheet works just as well:
Set myRange = sheet.Names("name").RefersToRange
That way the code won't break if the workbook is renamed, or if the user changes the "tab name" of the sheet. It won't break as long as the name exists in the queried Names collection.
'myWS2.Range("v1_paste").Value = myWS1.Range("v1_copy").Value
' This line works, but the below line doesn't
myWS2.myPasteRange.Value = myWS1.myCopyRange.Value
' This should be the exact same, just substituting the variable, but doesn't work
This should be the exact same - no. myWS1.myCopyRange is illegal: myWS1 is a Worksheet object: the Worksheet interface doesn't have a myCopyRange member, hence method or data member not found.
Since myCopyRange is a Range object, it knows about its Parent which is the Worksheet it belongs to: there's no need to qualify it... and there's no need to dereference it again either - this is enough:
myPasteRange.Value = myCopyRange.Value
Range will only apply to the currently active worksheet unless you add the Worksheet reference at the time of assignment (not at the time usage as you have done).
Since you are access a different worksheet, your second assignment will fail.
myCopyRange = myWS1.Range("v1_copy")
myPasteRange = myPasteRange = Range("v1_paste")
See the Range Object Documentation:
When it's used without an object qualifier (an object to the left of
the period), the Range property returns a range on the active sheet
... Use the Activate method to activate a worksheet before you use the
Range property without an explicit object qualifier
If you are trying to refer to NamedRanges and not a name held in a VBA variable, you need to change the way you are accessing the range.
Workbook-scope NamedRanges do not use worksheet reference - since they don't apply to a worksheet, they apply at the workbook level. If you need to add a qualifier, you add the workbook:
Range("MyBook.xls!MyRange")
If you are referring to Worksheet-scope NamedRange, you need a qualifier, but it goes inside the quotations:
Range("Sheet1!Sales")
Properly create ranges by using Set and don't refer to worksheets before them. Workbook-scoped ranges don't need to be tied to any worksheet.
Sub copy_paste_test()
Dim myCopyRange As Range
Dim myPasteRange As Range
Set myCopyRange = Range("v1_copy")
Set myPasteRange = Range("v1_paste")
Range("v1_paste").Value = Range("v1_copy").Value
'myPasteRange.Value = myCopyRange.Value
End Sub

Referencing Excel Cells in VBA

My main problem is assigning a variable an manipulating it in VBA.
I know that this code works, but I'm not sure if it this is a proper way to assign a variable in VBA like I have with currentcell = Cells(i, 1).Value
Sub C()
Dim i As Integer
For i = 1 To 4
currentcell = Cells(i, 1).Value
MsgBox currentcell
Next i
End Sub
What the macro recorder and a lot of tutorials won't tell you, is that these calls are implicitly referring to the ActiveSheet, which is obviously implicitly in the ActiveWorkbook. When all you ever have to deal with is a single worksheet, it's probably fine, but reality is that it's never the case, and code written with implicit references to the ActiveSheet is incredibly frail, bug-prone, and the underlying cause behind way too many Stack Overflow questions tagged with vba:
Cells
Range
Name
Rows
Columns
Whenever you use any of those, you're making calls against some global-scope Property Get accessor that "conveniently" fetches the ActiveSheet reference for you:
Screenshot of Rubberduck's toolbar, showing the declaration site of the selected code. Disclaimer: I manage that open-source VBIDE add-in project.
So yes, this "works", but what you'll want is to work off an explicit Worksheet object reference instead. There are many ways to get a Worksheet object reference, but the best one is to not care about where the worksheet is coming from, and take it as a parameter:
Sub DoSomething(ByVal ws As Worksheet)
Dim currentCell As Variant
Dim i As Integer
For i = 1 To 4
currentcell = ws.Cells(i, 1).Value
'in-cell error values can't be converted to a string:
If Not IsError(currentCell) Then MsgBox currentcell
Next i
End Sub
This takes the responsibility of knowing exactly what worksheet to work with away from the procedure, and gives it to the calling code, which can look like this:
DoSomething Sheet1 'uses the sheet's CodeName property
Or this:
DoSomething ThisWorkbook.Worksheets("Period" & n)
Or whatever. There are plenty of ways, each with their own gotchas - often you'll see something like this:
DoSomething Sheets("Sheet1") 'where "Sheet1" is in ThisWorkbook
Except Sheets implicitly refers to the ActiveWorkbook which may or may not be ThisWorkbook, and the Sheets collection could return a Chart object, which isn't a Worksheet. Also if "Sheet1" is in ThisWorkbook (the workbook with the code that's running), then referring to a worksheet by name means your code breaks as soon as the user renames the tab for that sheet.
The most robust way to refer to a worksheet that exists at compile/design-time, is by its CodeName. Select the sheet in the Project Explorer (Ctrl+R), then bring up the Properties toolwindow (F4), and set its CodeName by changing the (Name) property to whatever you need: VBA defines a global-scope global object variable by that name, so you can call it MyAwesomeReport and then refer to it as such in code:
DoSomething MyAwesomeReport
And that code will not break if the user changes the worksheet's "name".
Your code (however small that snippet might be) has other issues, notably it's using undeclared variables, which means you're not using Option Explicit, which is another thing that will end up biting you in the rear end and come to Stack Overflow with an embarrassing question that boils down to a typo - because without Option Explicit specified at the top of every module, VBA will happily compile a typo, making your code behave strangely instead of simply not compiling. These bugs can be excruciatingly hard to find, and they're ridiculously easy to prevent:
Use. Option. Explicit. Always.

Dimension not valid while trying to add a worksheet to a workbook

I am working on a VBA script in Workbook_A which creates a series of separate workbooks (Workbook_B, Workbook_C). The script will create these separate workbooks, name them according to variables present in Workbook_A (in this case those variables are Workbook_B, etc.), write simulation results in sheets by different simulation run, and save them.
So far, I am able to open/create new workbooks and save them correctly. I'm running into issues when it comes to creating new sheets. Here are the relevant portions of the code:
... Initializing other variables...
Dim workbookname As String
Dim wksht1 As Worksheet
... irrelevant code ...
Set wkbk = Workbooks.Add
wkbk.title = savedVeh
workbookname = savedVeh & "full"
wkbk.SaveAs fileName:=workbookname, FileFormat:=56
Workbook_A.Activate
' Starting process of creating sheets for different results
Set wksht1 = Workbooks(workbookname).Worksheets.Add '***
wksht1.Name = "const_speeds"
ffastsim_validation.Activate
I get an error on the line with the '*** commented. The error states:
"The specified dimension is not valid for the current chart type."
This is my first time playing with VBA - am I just making a syntax error? Or is wksht1 dimensioned improperly for the operation I'm trying to perform? I'd appreciate any help explaining what's going on here.
You do:
wkbk.SaveAs fileName:=workbookname, FileFormat:=56
And then:
Set wksht1 = Workbooks(workbookname).Worksheets.Add
If I understand correctly, that Workbook object you're trying to re-dereference again, would be the wkbk object. So why bother dereferencing it again from the Workbooks collection? You already have it:
Set wksht1 = wkbk.Worksheets.Add

vba excel sheets.range.value Error

So, sometimes when I try to execute this command, it gives me an error. The problem is that it is very inconsistent. In certain cases, it works and in others, it just doesn't.
This is the line from getCellVal function. cellName is a string and s is an integer.
getCellVal = Sheets(s).Range(cellName).Value
This time it is giving me:
Run-time error '438':
Object doesn't support this property or method
This line actually worked without problems moments ago. I added some other functions that use it and now it's not working anymore.
Any ideas about why?
Unqualified calls into the Sheets collection implicitly refer to whatever workbook is currently active, and it's important to know that the collection contains Worksheet objects, ...but also Chart objects.
Since you mean to work with a Worksheet, use the Worksheets collection instead.
If you're not getting an "index out of bounds" error, then the sheet you asked for does exist. But error 438 points to that sheet not being a Worksheet (and thus not having a Range member).
I bet the active workbook has a chart sheet at index s.
The solution is simply to be explicit.
If you mean to work with the workbook that contains the code that's running, qualify Workbook member calls with ThisWorkbook:
getCellVal = ThisWorkbook.Worksheets(s).Range(cellName).Value
If you mean to work with a workbook, then you need to get ahold of the Workbook object when you open it:
Dim wb As Workbook
Set wb = Application.Workbooks.Open(path)
'...
getCellVal = wb.Worksheets(s).Range(cellName).Value

shorthand for workbook().worksheets()?

I may be blind, but I've been working with VBA for a few years now but still write out
Workbook("Book1").Sheets("Sheet1").Range("A1").Value
or (after dimming Book1 as a workbook and Sheet1 as a string
Book1.Sheets(Sheet1).Range("A1").Value
Is there a way that you can shorthand the "workbook.sheets" part (without doing a "With" statement)?
Sure. Just do it the wrong way:
Sheet1.Activate
Range("A1").Value = 42
Unqualified in a standard code module, Range is a member of _Global, which implements its Range property by returning the specified range on whichever worksheet happens to be active... if any (in a worksheet's code-behind, it implicitly refers to Me.Range, i.e. a range on that sheet).
If you're going to implicitly work off ActiveSheet, you can also defer type resolution to run-time with a less performant late-bound call, and make the host application (here Excel) evaluate a bracketed expression for even faster typing:
[A1].Value = 42
Heck, the Range type has a default member that points to its Value, so you could even do this:
[A1] = 42
As you can see, less code isn't always better code. Qualify your Worksheet member calls, and use default members consciously and judiciously.
Every time someone makes an implicit call on _Global, a baby unicorn dies and two new Stack Overflow questions involving errors stemming from unqualified worksheet calls are summonned from the darkness.
Sarcasm aside, if you find yourself constantly chaining such Workbook("Book1").Sheets("Sheet1").Range(...)... calls, then you're constantly dereferencing the same objects, over and over: that's not only redundant to type, it's also redundant to execute.
If you're working with ThisWorkbook (the workbook running the code), you never have a legitimate reason to do this to dereference a worksheet that exists at compile-time. Use its code name instead:
Sheet1.Range(...)...
If the workbook only exists at run-time, or otherwise isn't ThisWorkbook, then at one point in time your code opened or created that workbook - there's no need to ever dereference it from the Workbooks collection, ...if you stored the reference in the first place:
Set wbExisting = Workbooks.Open(path)
Set wbNew = Workbooks.Add
Same for worksheets that are created at run-time in other workbooks by your code: keep that object reference!
Set wsNew = wbNew.Worksheets.Add
This leaves only 1 scenario where you would ever need a string to dereference a specific worksheet: the sheet already exists in a workbook that isn't ThisWorkbook.
If that workbook's structure isn't (or can't be) protected, avoid hard-coding the sheet's index or name if you can:
Set wsExisting = wbExisting.Worksheets(1) ' user may have moved it!
Set wsExisting = wbExisting.Worksheets("Summary") ' user may have renamed it!
TL;DR
Work with objects. Declare objects, assign object references, and work with them, pass them as arguments to your procedures. There's no reason to be constantly dereferencing objects like you're doing. If you need to do it, then you only ever need to do it once.
Sure. Just do it the right way:
Dim wb As Workbook
Set wb = Workbooks("Book1")
Dim ws As Worksheet
Set ws = wb.Worksheets("Sheet1")
Dim x As Variant
x = ws.Range("A1").Value
(Sorry Mat's Mug - I had to have a bit of a dig with that first line :D)
OK, I'm going to take the "Book1" name literally, and assume that you're writing code to deal with a new workbook - probably with something like:
Dim myWorkbook As Workbook
Workbooks.Add
Set myWorkbook = Workbooks("Book1")
That's already a bad start, because:
The language of the user will determine the "Book" part of the name.
The numeric suffix will increment with every additional new workbook
So, many inexperienced coders try this:
Dim myWorkbook As Workbook
Workbooks.Add
Set myWorkbook = ActiveWorkbook
But that's open to error too. What if there are event handlers looking to change the active workbook? What is the user changes the active workbook while stepping through code?
The best way to assign your myWorkbook variable is with something like this:
Dim myWorkbook As Workbook
Set myWorkbook = Workbooks.Add
And, just like when adding a new workbook, you should follow the same approach when opening an existing workbook:
Dim myWorkbook As Workbook
Set myWorkbook = Workbooks.Open("C:\Foo.xlsx")
In both cases, you know you've got a reference to the correct workbook, and you don't care what it is called or whether it is active. You've just made your code more robust and more efficient.
Alternatively, if your VBA is working with the workbook in which it resides, you can just use ThisWorkbook or the codename of the sheet(s).
Debug.Print ThisWorkbook.Name
'By default, a sheet named "Sheet" has a codename of 'Sheet1'
Debug.Assert Sheet1.Name = ThisWorkbook("Sheet1").Name
'But give meaningful names to your sheet name and
'sheet codename, and your code becomes clearer:
Debug.Assert AppSettings.Name = ThisWorkbook("Settings").Name
You'll probably find that most of your code deals with the workbook in which it resides, or existing workbooks that your code opens, or new workbooks that your code creates. All of those situations are handled above. On the rare occasions in which your code must interact with workbooks that are already open, or are opened by some other process, you'll need to refer to the workbook by name, or enumerate the Workbooks collection:
Dim myWorkbook As Workbook
Set myWorkbook = Workbooks("DailyChecklist.xlsx")
For Each myWorkbook In Workbooks
Debug.Print myWorkbook.Name
Next myWorkbook
The one exception being add-ins, which can't be enumerated using the Workbooks collection, but can be referenced with Workbooks("MyAddin.xlam")
Like this
Sub temp()
Dim WB As Workbook, WS As Worksheet
Set WB = ActiveWorkbook
Set WS = WB.Sheets(2)
MsgBox WS.Range("A2").Text
End Sub
You can set the worksheet variable along with its parent workbook
Dim wb1s1 As Worksheet
Set wb1s1 = Workbook("Book1").Sheets("Sheet1")
And then write
wb1s1.Range("A1").Value