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
Related
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
(...)
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("Table10")
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.
So I currently have three workbooks, I have created a folder in my Desktop, and where I am using workbook x, through a range on a cell to open up workbook called m (old) and workbook called n (new). I am then updating the old with the new.
Problem
Once I have opened up m, I am having to do a save as of the file as a newname. I am keen to know is there way of referring to it without using the workbook newname, as this workbook name would be concatenated with today's date and the name can be quite long and time consuming to type. I have produced several subroutines, one to remove protection etc,. and one for carrying out the coping actions and I am calling the subroutines, I just want to now if there is way of linking the newsaved name to some of the subroutines from referencing. Alternatively the old file could be saved at the very end. I am keen to pursue this avenue as I will be playing in certain other cases with four workbooks.
I have taken this code and amended it, but I am unable to open both spreadsheets, the strange thing when i block out the code and go through a test both work for each path they work, but not at the same time.
this is the code i amended, which i was able to find in
Dim x As Workbook, y As Workbook
Dim ws1 As Worksheet, ws2 As Worksheet
Set x = Workbooks.Open("path to copying book")
Set y = Workbooks.Open("path to pasting book")
Set ws1 = x.Sheets("Sheet you want to copy from")
Set ws2 = y.Sheets("Sheet you want to copy to")
With ws1
.Cells.Copy ws2.cells
y.Close True
x.Close False
End With
End Sub
this is my code where it is not opening both workbooks
Dim wb as Workbook
Dim wb1 As Workbook, wb2 As Workbook
Dim ws as Worksheet, ws1 As Worksheet, ws2 As Worksheet
Dim Lst As Long,
Dim r1, r2 As Range
Set wb = ThisWorkbook
Set wb1 = Workbooks.Open("Sheets("x").Range("A4").Value")
Set wb2 = Workbooks.Open("Sheets("x").Range("A5").Value")
With wb2
Call Wbkunprtect()
I would be grateful for some help, please for those who are trigger happy can you hold back from pressing the down arrow some of us are not excel knowledgeable and also trying to learn and do not want to be banned from asking questions, I am trying to move data between two workbooks wb1 and wb2, through another wb i have completed my code, and this is the bit which stopping me from going forward.
I have learned a great deal from this site the reason I asked certain questions as I am reluctant to use select or activate as I have been told this is a bad habit you have to keep away from.
From what I understood, you'll need to use a WorkBook object! the WorkBook object will have the instance of the newly added workbook. It is as simple as that:
Sub example_new_workbook()
Dim wb As Workbook
Set wb = Workbooks.Add
wb.SaveAs ("path where you want to save")
End Sub
That way, "wb" will have the instance of the new workbook, and you can use freely to reference that new workbook. Remember, the WorkBooks object holds all the instances of all open workbooks, so you can get the reference for any other workbook through this object.
As to make it "Global", you can declare it outside the "Sub" scope, so the variable will be permanent as long as the module (or wherever you put your code) is open like this:
public wb as Workbook
Sub <your_routines>()
...
End Sub
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".
I have the following which is working in the same workbook how do I get this to use a summary sheet in a different workbook?
Sub SummurizeSheets()
Dim ws As Worksheet
Application.ScreenUpdating = False
Sheets("Summary").Activate
For Each ws In Worksheets
If ws.Name <> "Summary" Then
ws.Range("D2:D6, D8:D15").Copy
Worksheets("Summary").Cells(Rows.Count, 4).End(xlUp).PasteSpecial (xlPasteValues)
End If
Next ws
End Sub
Make sure you place the sub-routine in a Module, and not in
ThisWorkbook. You can insert a new module by right-clicking on workbook name (from the VBA editor) and going to Insert > Module.
Make sure the workbooks you want to use/reference are open.
Make sure the workbooks are all located in the same workbook collection. This is not a problem unless you manually create an additional instance of Excel.
Reference the workbooks using the Workbooks() object just like you
do with Worksheets.
Sub test()
Dim b2 As Workbook 'We will use this variable as a reference to the external workbook that contains the "Summary" worksheet.
Set b2 = Excel.Workbooks("testbook2") 'We assign the external workbook (which I named "testbook2" for the purposes of this example) to our 'b2' variable.
Dim ws1 As Worksheet
Dim ws2 As Worksheet 'We will use these variables as references to the worksheets we're using.
Set ws1 = Excel.ActiveSheet 'We set ws1 to equal our current sheet (which presumably is where you'll be copying data from).
Set ws2 = b2.Worksheets("Summary") 'We set ws2 to equal the sheet (named "Summary") in the external workbook (named "testbook2").
ws1.Range("D2:D6").Copy 'We copy the data from the active sheet, which we reference using our 'ws1' variable.
'Note: I had issues with using multiple ranges in the same object so I removed this from your code.
ws2.Cells(Rows.Count, 4).End(xlUp).PasteSpecial xlPasteValues 'You only need to use the ws2 variable since we've already defined it as the "Summary" sheet you want.
End Sub
I'm not sure why I follow that first rule, but I seem to remember having issues when using ThisWorkbook in conjunction with external workbook references.
Update
I edited the code to show you a better example of how to do this. You almost never need to use "Activate" or "Select" commands in VBA. Just assign variables and reference the values directly.