I have the name of a worksheet stored as a string in a variable. How do I perform some operation on this worksheet?
I though I would do something like this:
nameOfWorkSheet = "test"
ActiveWorkbook.Worksheets(nameOfWorkSheet).someOperation()
How do I get this done?
There are several options, including using the method you demonstrate, With, and using a variable.
My preference is option 4 below: Dim a variable of type Worksheet and store the worksheet and call the methods on the variable or pass it to functions, however any of the options work.
Sub Test()
Dim SheetName As String
Dim SearchText As String
Dim FoundRange As Range
SheetName = "test"
SearchText = "abc"
' 0. If you know the sheet is the ActiveSheet, you can use if directly.
Set FoundRange = ActiveSheet.UsedRange.Find(What:=SearchText)
' Since I usually have a lot of Subs/Functions, I don't use this method often.
' If I do, I store it in a variable to make it easy to change in the future or
' to pass to functions, e.g.: Set MySheet = ActiveSheet
' If your methods need to work with multiple worksheets at the same time, using
' ActiveSheet probably isn't a good idea and you should just specify the sheets.
' 1. Using Sheets or Worksheets (Least efficient if repeating or calling multiple times)
Set FoundRange = Sheets(SheetName).UsedRange.Find(What:=SearchText)
Set FoundRange = Worksheets(SheetName).UsedRange.Find(What:=SearchText)
' 2. Using Named Sheet, i.e. Sheet1 (if Worksheet is named "Sheet1"). The
' sheet names use the title/name of the worksheet, however the name must
' be a valid VBA identifier (no spaces or special characters. Use the Object
' Browser to find the sheet names if it isn't obvious. (More efficient than #1)
Set FoundRange = Sheet1.UsedRange.Find(What:=SearchText)
' 3. Using "With" (more efficient than #1)
With Sheets(SheetName)
Set FoundRange = .UsedRange.Find(What:=SearchText)
End With
' or possibly...
With Sheets(SheetName).UsedRange
Set FoundRange = .Find(What:=SearchText)
End With
' 4. Using Worksheet variable (more efficient than 1)
Dim MySheet As Worksheet
Set MySheet = Worksheets(SheetName)
Set FoundRange = MySheet.UsedRange.Find(What:=SearchText)
' Calling a Function/Sub
Test2 Sheets(SheetName) ' Option 1
Test2 Sheet1 ' Option 2
Test2 MySheet ' Option 4
End Sub
Sub Test2(TestSheet As Worksheet)
Dim RowIndex As Long
For RowIndex = 1 To TestSheet.UsedRange.Rows.Count
If TestSheet.Cells(RowIndex, 1).Value = "SomeValue" Then
' Do something
End If
Next RowIndex
End Sub
The best way is to create a variable of type Worksheet, assign the worksheet and use it every time the VBA would implicitly use the ActiveSheet.
This will help you avoid bugs that will eventually show up when your program grows in size.
For example something like Range("A1:C10").Sort Key1:=Range("A2") is good when the macro works only on one sheet. But you will eventually expand your macro to work with several sheets, find out that this doesn't work, adjust it to ShTest1.Range("A1:C10").Sort Key1:=Range("A2")... and find out that it still doesn't work.
Here is the correct way:
Dim ShTest1 As Worksheet
Set ShTest1 = Sheets("Test1")
ShTest1.Range("A1:C10").Sort Key1:=ShTest1.Range("A2")
To expand on Ryan's answer, when you are declaring variables (using Dim) you can cheat a little bit by using the predictive text feature in the VBE, as in the image below.
If it shows up in that list, then you can assign an object of that type to a variable. So not just a Worksheet, as Ryan pointed out, but also a Chart, Range, Workbook, Series and on and on.
You set that variable equal to the object you want to manipulate and then you can call methods, pass it to functions, etc, just like Ryan pointed out for this example. You might run into a couple snags when it comes to collections vs objects (Chart or Charts, Range or Ranges, etc) but with trial and error you'll get it for sure.
Related
I am struggling with proper syntax for setting variables as ranges...
Specifically, I'm testing a function I want to use in an app that creates new profiles and store the data, I will store that data on a hidden sheet, so they can be recalled at run time.
I'm currently construction a userform in order to create a new profile, the profile data needs to be stored to the first free column on the hidden sheet.
(where I will have to create a dynamic namedRange, so that i can use that range to save the associated data, and update the listbox in the userform)
Right now, I'm stumped by this:
Sub TestFindLastFunctions()
Dim wb As Workbook
Set wb = ThisWorkbook
'wb.activate 'shouldn't be neccesary
Dim ws As Worksheet
Set ws = sh_02CRepStorage
'ws.activate 'shoudn't be neccesary
Dim trgtCol As Long
trgtCol = LastColInSheet(ws) + 2
Debug.Print trgtCol ' so far so good
'Cells(1, trgtCol).Select 'another debug check - only works if sheet activated
Dim trgtCell As Range
Set trgtCell = ws.Cells(1, trgtCol) '<------- problem line
Debug.Print trgtCell '<----- prints "" to the immediate window.
End Sub
The LastColInSheet function is copied form Ron de bruin's page: https://www.rondebruin.nl/win/s9/win005.htm it simply returns a column number, in this case: 4.(One problem with it is if the sheet is empty, it returns an error, wondering if this can be fixed with an if statement in the function.)
I've tried many iterations of the problem line, some work but only if the storage sheet is activated, and give an error if not activate or selected, as the sheet will be hidden, I need this to work without activating the sheet, (although I could switch off screen activation?).
But I understand that it is best practice to avoid extraneous selects and activates, how can I just point directly to what I want and save that range into a variable?
It just doesn't seem like it should be so difficult, I must be missing something obvious.
It also seems like it shouldn't need so many lines of code to do something so simple.
I tried some more iterations of the "problem line" after some more searching...
-The real problem was with the debug.print line
Sub TestFindLastFunctions()
Dim wb As Workbook
Set wb = ThisWorkbook
'wb.activate 'shouldn't be neccesary
Dim ws As Worksheet
Set ws = sh_02CRepStorage
'ws.activate 'shoudn't be neccesary
Dim trgtCol As Long
trgtCol = LastColInSheet(ws) + 2
Debug.Print trgtCol ' so far so good
'Cells(1, trgtCol).Select 'debug Only works if already on sheet
Dim trgtCell As Range
'Set trgtCell = ws.Range _
(ws.Cells(1, trgtCol), ws.Cells(1, trgtCol))
' unnecessarily complex, but correct if using .range?
'but works if insisting on range
Set trgtCell = ws.Cells(1, trgtCol) 'back to original
Debug.Print trgtCell.Address '<---problem was here?
End Sub
I'm very new to VBA and I need all the help I can get. Module 1 counts the numbers of cells with integers in the first row starting at C1 (A1 and B1 are titles)in the 'LLP Disc Sheet'. The number of cells for this specific worksheet is 9. However, 9 is not always the number. Sometimes the number is 1, 2, 3, 4, etc. It just depends if the user fills in those cells. I'm trying to store that number 9 to use in Module 2.
Module 2 produces copies of an entire sheet called 'MasterCalculator', which I plan on renaming each sheet produced to the Cell values that were counted in Module 1. The number of copies produced must match the calculation in Module 1 (Which is currently 9).
I can't seem to figure out how to reference the variable 'lc' in the t3() module in the test() module. The number of copies of the MasterCalculator Sheet is inaccurate.
MODULE 1
Public lc As Integer
Sub t3()
Dim lc As Long, sh As Worksheet
Set sh = ActiveSheet
With sh
lc = Rows(1).SpecialCells(xlCellTypeConstants, 23).Cells.Count - 1
End With
ThisWorkbook.Save
End Sub
MODULE 2
Sub test()
Dim sh As Worksheet
Dim last_is_visible As Boolean
With ActiveWorkbook
last_is_visible = .Sheets(.Sheets.Count).Visible
.Sheets(Sheets.Count).Visible = True
.Sheets("MasterCalculator").Copy After:=.Sheets(Sheets.Count)
Set sh = .Sheets(Sheets.Count)
If Not last_is_visible Then .Sheets(Sheets.Count - t3.lc).Visible = False
sh.Move After:=.Sheets("LLP Disc Sheet")
End With
End Sub
First off, your requirement isn't suitable to be filled by a global variable. It's clearly a task for a function.
Second, your line lc = Rows(1).SpecialCells(xlCellTypeConstants, 23).Cells.Count will throw an error if there are no matching SpecialCells. Therefore it would require an error handler so that it can return -1 instead of crashing.
Third, rather than make your idea of counting SpecialCells work, please consider the alternative which is to find the end of the first row by looking from the right (instead of counting from the left). The above reasoning leads me to this function.
Function ColumnsCount(Optional Ws As Worksheet) As Long
If Ws Is Nothing Then Set Ws = ActiveSheet
With Ws
ColumnsCount = .Cells(1, .Columns.Count).End(xlToLeft).Column - 1
End With
End Function
Implementation of this function into your code leads to these two lines of code in your Test procedure.
Set Sh = .Sheets(Sheets.Count)
If Not last_is_visible Then .Sheets(Sh.Index - ColumnsCount(Sh)).Visible = False
The function ColumnCount will return the count from the worksheet given to it as parameter. In the above code that is the worksheet Sh. In the code in your question it seems to be the ActiveSheet (Perhaps Sh is the active sheet. Just make sure you pass the sheet on which you want to count and the function will return the correct number.
As two matters of principle: First, avoid using ActiveSheet as much as possible. Assign your sheets to variables meaningfully named and refer to them by the names you gave yourself. This is because ActiveSheet can be affected by user action outside the scope of your code and 9 times out of 10 it isn't a meaningful name.
Second, avoid what rubberduck calls "snake_names". LastIsVisible is a meaningful name, last_is_visible is a pain in the eye. I would use LastVis because it's shorter. I also recommend to use upper and lower case letters in names and this is my reason.
As you declare names, in the Dim statements, use caps and smalls.
As you write your code, use lower case only.
VBA will correct the capitalisation of what you type to match the declaration.
So, when VBA doesn't change the names you typed you know that there is a typo. - Instant alert for no effort at all. And your code becomes easier to read into the bargain.
To me it's preferable to set down a Function:
Function Lc(Optional sh As Worksheet) As Long
If sh Is Nothing Then Set sh = ActiveSheet
With sh
Lc = .Rows(1).SpecialCells(xlCellTypeConstants, 23).Cells.Count - 1
End With
End Function
and call it whenever you need, for example:
Sub test()
Dim sh As Worksheet
Dim last_is_visible As Boolean
With ActiveWorkbook
last_is_visible = .Sheets(.Sheets.Count).Visible
.Sheets(Sheets.Count).Visible = True
.Sheets("MasterCalculator").Copy After:=.Sheets(Sheets.Count)
Set sh = .Sheets(Sheets.Count)
If Not last_is_visible Then .Sheets(Sheets.Count - Lc).Visible = False '<--- Lc will get the "current" Lc value
sh.Move After:=.Sheets("LLP Disc Sheet")
End With
End Sub
A Public variable is as handy as can be dangerous in that you have to carefully :
"follow" it throughout all your code and ensure it isn't being unwillingly set
check it doesn't persist through sessions
Can I set up a variable x = "Sheet1"
In order to do:
x.Range("A3")
instead of
Sheet1.Range("A3")?
What type of variable should it be? I tried string and it didn't work.
Thanks
Update:
I would like a method that won't be affected by changing the worksheet name. i.e.
Sheet1.Range("A3") will always refer to the same worksheet even if I change the worksheet name to "peanuts", at least that's what I thought.
You want to declare it a Worksheet Object:
Dim x as WorkSheet
Since it is an object we must Set the sheet:
Set x = WorkSheets("Sheet1")
or if you want to use the code name:
Set x = Sheet1
Then yes you can use it:
x.Range("A3")...
I think you're mixing two different declarations
Declaring a sheet (must set the sheet)
dim ws as worksheet
set ws = sheets("sheet1")
ws.cells(1,1).value = ""
Declaring a string as the name of a sheet (can utilize the NAME of the sheet as a string)
dim ws_name as string
ws_name = "sheet1"
sheets(ws_name).cells(1,1).value = ""
Additionally you could use the sheet index, which does not utilize the name of the sheet (if you change it later); this is a little different than the previous two, but this example (using a loop) helps more clearly explain how the index can be utilized
dim i as long
for i = 1 to sheets.count step 1
sheets(i).cells(1,1).value = ""
next i
and a simple use of a sheet index
sheets(1).cells(1,1).value = ""
It should be worksheet. The syntax would be:
Dim x as Worksheet
Set x = Sheet1
after that you can use x.range("A3") to refer to Cell A3 in sheet1
You want a Worksheet variable. The "name" you're referring to is the Name property, and that isn't in code; it's the "tab name" of the sheet, that the user can change on a whim and that, for that reason, you don't want to have to hard-code anywhere if you can avoid it.
But it looks like you're referring to a sheet that exists in ThisWorkbook at compile-time; each sheet has a (Name) property that you can edit in the Properties toolwindow (F4). By default that name is Sheet1, and VBA takes that name and makes it a project-scope identifier you can use anywhere you need to refer to that particular sheet.
So if you change a sheet's (Name) to PeanutsSheet, then you can use PeanutsSheet in your code:
PeanutsSheet.Range("A1").Value = 42 '<~ that's the "CodeName", and users can't easily break it
And that is preferable to referring to that same sheet by its "tab name":
ThisWorkbook.Worksheets("Peanuts").Range("A1").Value = 42 '<~ breaks if tab is ever renamed
Declaring a variable for a sheet that exists in ThisWorkbook at compile-time, is completely redundant:
Dim ws As Worksheet
Set ws = Sheet1 '<~ variable 'ws' is 100% redundant
Using such variables makes the code confusing and harder to follow than it needs to be, because now you have 2 (or more) identifiers referring to the same thing.
But this is worse:
Set ws = ThisWorkbook.Worksheets("Sheet1") '<~ now it's redundant *and* super frail
as a beginner to coding and VBA in specific im currently trying to automate as much worksteps as im able to, mainly to get into it. So for now i got a set of >50 Excel Workbooks and the "simple" task to collect the number of Datapoints (one row for each datapoint) in each and pass this value to a new workbook . What i built together until now is this (the credit for basic construct goes fully to Henrik Schiffner, i used it for several other operations):
Sub count_rows()
'Define variables:
Dim numberOfFilesChosen, i As Integer
Dim tempFileDialog As FileDialog
Dim mainWorkbook, sourceWorkbook As Workbook
Dim tempWorkSheet As Worksheet
Dim LastRow As Integer
Set mainWorkbook = Application.ActiveWorkbook
'This Step is not mandatory of course but quite comfortable to choose
'the workbooks to work with
Set tempFileDialog = Application.FileDialog(msoFileDialogFilePicker)
tempFileDialog.AllowMultiSelect = True
numberOfFilesChosen = tempFileDialog.Show
'Loop through all selected workbooks
For i = 1 To tempFileDialog.SelectedItems.count
Workbooks.Open tempFileDialog.SelectedItems(i)
Set sourceWorkbook = ActiveWorkbook
For Each tempWorkSheet In sourceWorkbook.Worksheets
LastRow = tempWorkSheet.UsedRange.Rows.count - 1
MsgBox LastRow
Next tempWorkSheet
Application.DisplayAlerts = False
sourceWorkbook.Close
Application.DisplayAlerts = True
Next i
mainWorkbook.Save
End Sub
This gives me the correct Value from each file in the prompted message box. However, im failing to grab the LasRow value and simply copy it to the mainWorkbook. Aim is to have one value after another in one column (lets say "A"). Failure is on different levels like: Searching for the last empty row in mainWorkbook with:
destinationRow = mainWorkbook.Worksheets("Sheet1").Cells(Rows.count, 1).End(xlUp) + 1
Or even to give the lastRow Value to any spot in mainWorkbooks with e.g.:
mainWorkbook.Worksheets("Sheet1").Rows(destinationRow) = LastRow
Or
LastRow.Copy After:=XXXX
Im pretty sure im misunderstanding a basic concept of VBA, so it would be awesome to get a short explanation why my operations did not work out instead of just getting a working code.
However, adding the name of each Workbook as a header for its value would be magnificent!
Searching for the last empty row in mainWorkbook would use the UsedRange property of Worksheet object; this returns a Range object which you then access the Address property of. You then want to use the Split() function which will take your string and place each substring seperated by a delimiter ($ in this case) into an index of an array. For your purposes you want the 4th index which will be the last row in the UsedRange:
'The MsgBox's should help you understand what is happening with this code
MsgBox (mainWorkbook.Sheets("Sheet1").UsedRange.Address)
lastRow = Split(mainWorkbook.Sheets("Sheet1").UsedRange.Address, "$")(4)
MsgBox (lastRow)
I am a little confused by what you meant with, "give the lastRow Value to any spot in mainWorkbooks." If you meant you would like the set the value of a cell in your mainWorkbook to the value of the lastRow, you would use:
mainWorkbook.Sheets("Sheet1").Cells(1, 1).Value = lastRow
And I am not sure what you are trying to do with: LastRow.Copy After:=XXXX. Your LastRow variable is an Integer Dim LastRow As Integer. An Integer Data Type cannot use the Copy Method. I mostly use the .Copy After:= Method to copy one worksheet and paste it after another. If that is your goal:
'Copy Sheet1 and place it after the last sheet in the workbook
Sheets("Sheet1").Copy After:=Sheets(ActiveWorkbook.Sheets.count)
Otherwise, if you could explain your goal with that I would be happy to help.
As a beginner to code and VBA I would suggest a few things. The first is to do research on OOP (Object Oriented Programming). A very basic breakdown is that you have Objects which then have Methods and Properties (for example the CoffeMug object has a .Temperature property and a .Spill Method, and timsMug and tinasMug are both CoffeeMug objects). I love OOP, but it takes a while to get a truly deep understanding of how it works. After that though, it's a lot of fun and really powerful.
Other than that, use https://learn.microsoft.com/en-us/office/vba/api/overview/ to learn about VBA. I use the Excel section and Language Reference section all the time.
I have a snippet which throws an error, I assume because the variable s is not initialized. How would I declare the variable s?
Dim X As Integer
Dim WS As Worksheet
'Look for existing sheets named "For Export
'If found, delete existing sheet
For Each s In ActiveWorkbook.Sheets
If s.Name = "For Export" Then
Application.DisplayAlerts = False
' s.Delete
End If
Next s
Probably the easiest way is to search for Option Explicit in your code and to delete it. Then you would not be forced to declare every variable. However, if you are not fan of writing ugly & dirty code try with the following:
Dim s as Sheet
In general, it is better to use the Worksheets collection, instead of the Sheets. Thus:
Dim s as Worksheet
For each s in ActiveWorkbook.Worksheets
The Sheets collection contains both Charts and Worksheets, thus it is better to be more specific.