Difference between Worksheets & Worksheet obj - vba

I'm trying to understand the difference between worksheets & worksheet obj in Excel VBA. I understand from the MSDN reference that the worksheet is a child obj of worksheets & sheets.
However, we reference every worksheet using the worksheets obj, not the worksheet obj. e.g.
worksheets("ExcelIsCool").range("a1").value -> CORRECT
worksheet ("ExcelIsCool").range("a1").value -> INCORRECT
My question is whats the difference between the two?
Is worksheet only used for declaring a variable (the only place where I've used so far). e.g.
dim wks as worksheet

Like #Orphid has already stated, Worksheets is a collection of Worksheet objects.
When declaring a Worksheet variable, you can explicitly declare it as such.
Dim ws As Worksheet
Set ws = Worksheets("Sheet1")
So you're declaring a Worksheet from the Collection of Worksheets.
You can also loop through each Worksheet within a Collection.
Dim ws As Worksheet
For Each ws In Worksheets
Debug.Print ws.Name
Next ws
So you can see that Worksheet is used in scenarios where you're explicitly referring to a single Worksheet, primarily when declaring a variable.

Worksheets is a collection of Worksheet objects. A "Workbook" has one or more "Worksheets" - the collection, whilst a specific object from the collection is a "Worksheet".
In your example, you are trying to select a worksheet from the collection by name, but since one worksheet by itself isn't a worksheet collection, it will not work. When you do ThisWorkbook.Worksheets("MyWorksheetName") the returned value is a worksheet object, so you can interact with it in the way described.
Edit: Use Cases
Well, use Worksheet when you want it to be clear you are working with a worksheet, when you want to keep your functions clean of multiple responsibilities, and where you want good intellisense support inspecting the Worksheet's members. So if you are taking a worksheet as a parameter to some function (for example, a function that finds that last row of data from the bottom in a given column), your function signature could look like this:
Public Function GetLastRow(ByRef wsTarget as Excel.Worksheet, ByVal column as Long) as Long
GetLastRow = wsTarget.Cells(wsTarget.Rows.Count, column).End(xlUp).Row
End Function
This is nice, because it means you are passing around the actual object you want to refer to, rather than a string or number) (its name or index). It also means the called function (GetLastRow) doesn't need to know about which workbook the sheet is in (ActiveWorkbook and ThisWorkbook are not always the same). This gives the caller the responsibility to locate the sheet and make sure you get the right one, keeping the function clean. A user could call it with a worksheet's variable name (as displayed in one of the VBA editor panels):
last_row = GetLastRow(Sheet1, 1)
Or any of the other ways of referencing worksheets (see this).
You use the Worksheet object anytime you want to talk about only one sheet. Explicitly declaring your variable as a Worksheet object will give you the intellisense options appropriate to working with a worksheet. And it's clear to a reader that you are working with a worksheet, not a string.

The difference is purely syntactical in your case, but it is important to distinguish between the actual object and the programmatic "object". For instance, you can use both of these:
Worksheet Collection:
Worksheets("Sheet1").Range("A1").Value = "1"
Worksheet "Object":
Dim wks As Worksheet
Set wks = Worksheets("Sheet1")
wks.Range("A1").Value = "1"
There is no meaningful difference, except ease of use and sometimes better readability -- they both reference the actual object, which is "Sheet1". In Excel terminology, wks is also often referred to as an object, but what's important for you to remember is that wks represents an object, it isn't an object in itself.
Everything you can do with wks, you can also do with Worksheets("Sheet1") -- it's just two not-so-different ways of referring to the worksheet in question. In fact, in this particular example, "object" (like wks) is practically equivalent with "alias".

Related

VBA : How best for multiple subroutines to refer to the same worksheet

I am relatively inexperienced in VBA and don't know how to best structure my code.
I have a number of subs that all operate on an particular sheet, let's say Sheet1.
Each of my subs starts by setting the worksheet as follows:
Set ws = Sheets("Sheet1")
but I am conscious that down the track I may change the name of Sheet1 to something else, and this would require me to make changes to all of my subs. Ideally it is better to set this declaration once so that I only have to change it once.
What would be the best way to do this?
Several ways to do so. Btw, when using Sheet, you should always specify the workbook, else VBA will try to access the sheet from the active Workbook, and that is not always the workbook you want to work with. If the sheets and your code are in the same book, best is to refer to ThisWorkbook
o Define a constant at the top of your code. When sheetname is changed, you just need to change the const definition
Const MySheetName = "Sheet1"
Sub MySub1
Dim ws as Worksheet
Set ws = ThisWorkbook.Sheets(MySheetName)
(...)
o Use the Code name. A code name is a technical name of a sheet that can be changed only in the VBA-environment, so usually it never changes. See https://stackoverflow.com/a/41481428/7599798
o If the sheet is always the first (and maybe the only) sheet of a workbook, you can use the index number instead
Set ws = ThisWorkbook.Sheets(1)
o Use a function that returns the sheet:
Function getMySheet() As Worksheet
Set getMySheet = ThisWorkbook.Sheets("Sheet1")
End Function
Sub MySub1
Dim ws as Worksheet
Set ws = getMySheet
(...)

Variable name of a worksheet

I want to name a worksheet in excel as the value inside cell "C6" in the tab named "Control". I am new to VBA and what I tried was typing this on a module.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
'
' Macro2 Macro
'
'
Dim month As String
month = Sheet2.Range("C5")
Sheets("Month").Name = month
End Sub
Moreover, I do not know whether the name will be updated automatically. I do not want to have to run a macro to change the worksheet name...
Thanks!
It sounds like you want to be able to always refer to the same worksheet, regardless of the worksheet's name changing (as shown on the worksheet's tab) .
This is where the worksheets' CODENAME Property comes in handy.
Let's say you have an object variable declare for your worksheet like Dim ws As Worksheet. You can refer to a worksheet three basic ways.
...by Name :
Set object variable:
Set ws = Sheets("Sheet1")
The worksheet name is the only worksheet identifier that can be changed to whatever you want, which (as you're probably aware) is done like:
ws.Name = "NewSheetname"
...or alternatively, like:
Sheets("Sheet1").Name = "NewSheetName"
...by Index Number :
The index number identifies the position of the worksheet's "tab", compared to the others (and cannot be changed without changing the order of the worksheets)
Set object variable:
Set ws = Sheets(1)
...then you could (still) change the worksheets name like:
ws.Name = "NewSheetname"
...or you could change the name by referring to the worksheet index number like:
Sheets(1).Name = "NewSheetName"
NOTE: If the worksheet is moved (or another worksheet is inserted before it), the Index number will change! Therefore, it's usually not the preferred method of referring to a worksheet.
...by CodeName :
The CodeName is how Excel refers to the worksheets internally. It is the original name that Excel gave the worksheet, and it does not change since it is a read-only property.
Set object variable:
Set ws = Sheet1
...then you could (still) change the worksheets name like:
ws.Name = "NewSheetname"
...or you could change the name by referring to the worksheet codename like:
Sheet1.Name = "NewSheetName"
You can check a worksheets' CodeName property like:
MsgBox Sheets("YourSheetName").CodeName
...or, if ws is already referencing the worksheet:
MsgBox ws.CodeName
So, in your case, you could change the name of your worksheet as often as you like, with:
Sheet2.Name = "NewNameHere"
...just keep referring to it as Sheet2.
One more example to clarify the difference:
Create a new workbook. (It will automatically have worksheet named Sheet1.)
Change the worksheet name to "Sheet1999", either manually (by double-clicking the name on it's tab) or programmatically (with Sheet1.Name="Sheet1999")
Now, if you want to find out how many rows on that sheet have been used, you use use either:
MsgBox Sheets("Sheet1999").UsedRange.Rows.Count
...or:
MsgBox Sheet1.UsedRange.Rows.Count
A note about Sheets versus Worksheets:
When referring to a worksheet Sheets and Worksheets can usually be used interchangeably, so for example these two lines do the same thing:
Worksheets("mySheet").Calculate
Sheets("mySheet").Calculate
The difference is that:
the Worksheets object searches the Worksheets Collection for a matching Name, Index, or CodeName.
the Sheets object searches the Worksheets Collection **and the Charts Collection** for a matching Name, Index, or CodeName.
Therefore, the only time it would be a problem is if you have a chart and a worksheet with the same name.
So, don't ever name a chart the same as a worksheet, and then you won't have to worry about it, and can go on saving 4 keystrokes any time you refer to a Sheet... :)
More Information:
MSDN : Worksheet Object
MSDN : Worksheet Properties
MSDN : Worksheet Methods
MSDN : Sheets Object

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

VBA: How to address the correct workbook when giving a worksheet as an argument to a function?

I have a question regarding the correct address of Workbooks in VBA, which I am fairly new to.
Here is what I have done so far:
I have written a sub that, amongst other things, creates a worksheet with the CodeName "table10".
Then I defined a function to manipulate the contents of said sheet: this function
Text_To_Numbers(worksheet as worksheet)
expects a worksheet argument. I call the function from another sub using the following line:
Call Text_To_Numbers(table10)
Now, here is my issue:
The above works flawlessly when the only open workbook is the one I want to manipulate with my function. However, when I have multiple open workbooks, the function will try to manipulate a different workbook, resulting in an error.
I am quite certain that there must be a way to specify the workbook to be used, but I am unable to find it. That being said, there is another complication: The name of the workbook which I would like to manipulate is machine generated, so it always has a different name. This means that using an explicit reference to the same file name time and again is not an option.
Could anybody help me resolve this?
You need to fully qualify objects in VBA to avoid situations like this where it is ambiguous what the parent is.
In your situation, you want the sheet to be connected to its parent workbook, so make sure you specify that it came from a given workbook!
You cannot directly refer to worksheets in other workbooks by their CodeName, this can only be done to the ThisWorkbook object (the workbook containing the VBA code). See the question Fully reference a worksheet by codename for details on how to get the sheet by its codename from another workbook. I have included the function in the answer and how to use it in this context.
You created the sheet table10 in one of the following:
ActiveWorkbook
ThisWorkbook
WB (some workbook object)
So you can access it using that workbook object without a need for the name!
Using ThisWorkbook.table10 should give same behaviour as just table10, but here are two neater examples for calling the function.
' A neater way to call the function:
Text_To_Numbers worksheet:=ThisWorkbook.table10
' You could also call it simply using
Text_To_Numbers ThisWorkbook.table10
If your sheet is not within ThisWorkbook
' Get sheet (from the workbook object you are using, WB) and pass to your Text_To_Numbers
Text_To_Numbers GetSheetWithCodename("table10", WB)
Function GetSheetWithCodename(ByVal worksheetCodename As String, Optional wb As Workbook) As Worksheet
Dim iSheet As Long
If wb Is Nothing Then Set wb = ThisWorkbook ' mimics the default behaviour
For iSheet = 1 To wb.Worksheets.Count
If wb.Worksheets(iSheet).CodeName = worksheetCodename Then
Set GetSheetWithCodename = wb.Worksheets(iSheet)
Exit Function
End If
Next iSheet
End Function
Try assigning the workbook and sheet to a variable then calling it in this way when you need to do some work in it:
Dim WB As Workbook
Dim WS As Worksheet
'If you want to open the workbook before doing work
Set WB = Workbooks.Open("/Workbook path name goes here”)
Set WS = WB.Worksheets("Table‌​10")
Then you just need to pass a call to the WS variable from within your function to perform operations within the specified sheet.
Edit:
Apologies, didn't realise you were trying to reference the index name in the project editor when I first read your question. The code name can be referenced from an external workbook with the following example which shows how to select the workbook and sheet codename to perform a copy/paste from one workbook to another:
Sub UseCodeNameFromOutsideProject()
Dim WS As Worksheet
With Workbooks("MyWorkbook.xlsb")
Set WS = _
.Worksheets(CStr(.VBProject.VBComponents("Sheet1").Properties(7)))
WS.Range("A1").Copy
Selection.Copy
WS.Range("B1").PasteSpecial
End With
End Sub
Thanks to Enderland for the idea.

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