This is my first post on stackoverflow. I have two sub procedures in Excel VBA. The first one, called Sub IAR_part_2(), is intended to assign two sheets (by index location) to two variables named sheetname1 and sheetname2. after assigning the variables I am trying to pass them to my second sub procedure, called IAR_macro, to be processed. The two sheets are dependant on one another, so sheets 4 and 8 are ran through the IAR macro, sheets 5 and 9, sheets 6 and 10, etc. My problem is that I cannot figure out how to pass the sheetname variables from IAR_part_2 to IAR_macro. What am I doing wrong?
Sub IAR_part_2()
sheetname1 = Worksheets(4)
sheetname2 = Worksheets(8)
Call IAR_macro
End Sub
Sub IAR_macro(sheetname1 As Worksheet, sheetname2 As Worksheet)
Dim h As Long
Dim i As Long
Dim l As Long
Dim j As Long
Dim k As Long
Dim lr As Long
Worksheets(sheetname1).Activate
' Find the number of the last cell with data in column A and subtract 1 to populate variable i
On Error GoTo Canceled
i = (Range("B1").End(xlDown).Row) - 1
'Switch over to the Code sheet
Worksheets(sheetname2).Activate
'While the number of loops is less than variable i minus 1, copy the contents of cells A2 through A29 over and over down the worksheet
Do While l < (i - 1)
Range("A2:A29").Select
Selection.Copy
lr = Cells(Rows.Count, "A").End(xlUp).Row
Range("A" & lr + 1).Select
ActiveSheet.Paste
l = l + 1
'rest of macro follows from here...
Simple example of how to pass worksheet objects to a different sub:
Sub Macro1()
'Declare variables
Dim ws1 As Worksheet
Dim ws2 As Worksheet
'Assign variables to worksheet objects
Set ws1 = Worksheets(4)
Set ws2 = Worksheets(8)
'Call the second sub and pass the worksheet variables to it
Call Macro2(ws1, ws2)
End Sub
Sub Macro2(ByVal arg_ws1 As Worksheet, ByVal arg_ws2 As Worksheet)
'Reference the accepted arguments (in this case worksheet variables) directly:
MsgBox arg_ws1.Name
MsgBox arg_ws2.Name
'This will result in an error because you're using the passed argument incorrectly:
MsgBox ActiveWorkbook.Sheets(arg_ws1).Name '<-- Results in error
End Sub
You must reference the passed arguments directly. If you want to use the structure shown in your code, then the arguments passed need to be a string (but this method is NOT recommended):
Sub Macro1()
'Declare variables
Dim sSheet1 As String
Dim sSheet2 As String
'Assign variables to worksheet objects
sSheet1 = Worksheets(4).Name
sSheet2 = Worksheets(8).Name
'Call the second sub and pass the worksheet variables to it
Call Macro2(sSheet1, sSheet2)
End Sub
Sub Macro2(ByVal arg_sSheetName1 As String, ByVal arg_sSheetName2 As String)
'Because the arguments are strings, you can reference the worksheets this way
'This method is NOT recommended
MsgBox Worksheets(arg_sSheetName1).Name
MsgBox Worksheets(arg_sSheetName2).Name
End Sub
I noticed in your examples, you have your variables declared inside the function. Normally any variables you wish to use are better implemented by using option explicit. Also when identifying sheets, you will have less problems when addressing a sheet by its sheet number as opposed to the sheet name. That way if you need to use a variable, you can use just a integer instead as well.
Option Explicit
Dim h as Long, i as Long, l as Long, j as Long, k as Long, lr as Long
Dim x as Integer
Sub IAR_macro()
On Error GoTo Canceled
i = (Range("B1").End(xlDown).Row) - 1
Sheets(x).Activate
Do While l < (i - l)
Sheet ids can be located in the development tool. Here is an example:
This is minimal way of passing the worksheets. As far as they are objects, they are passed by reference by default:
Sub TestMe()
Dim ws1 As Worksheet
Dim ws2 As Worksheet
Set ws1 = Worksheets(1)
Set ws2 = Worksheets(2)
Passing ws1, ws2
End Sub
Sub Passing(arg_ws1 As Worksheet, arg_ws2 As Worksheet)
Debug.Print arg_ws1.Name
Debug.Print arg_ws2.Name
End Sub
VBA is not as easy and as simple as many people (mainly those who consider it to be a funny-scripting-language, written by wanna-be-developers) think. Sometimes it allows to write ByVal, but it follows its own rules and takes the argument ByRef, just to comfort you and make sure you are not going to make an error.
Saw the answer from #tigeravatar here and I have decided not to write a comment under it, but to explain in a different post why it is wrong and dangerous, as far as explaining it as a comment would have been tough.
If you try to write a Stop line here from the answer:
Sub Macro1()
'Declare variables
Dim ws1 As Worksheet
Dim ws2 As Worksheet
'Assign variables to worksheet objects
Set ws1 = Worksheets(4)
Set ws2 = Worksheets(8)
'Call the second sub and pass the worksheet variables to it
Call Macro2(ws1, ws2)
End Sub
Sub Macro2(ByVal arg_ws1 As Worksheet, ByVal arg_ws2 As Worksheet)
'Reference the accepted arguments (in this case worksheet variables) directly:
MsgBox arg_ws1.Name
Stop
MsgBox arg_ws2.Name
'This will result in an error because you're using the passed argument incorrectly:
MsgBox ActiveWorkbook.Sheets(arg_ws1).Name '<-- Results in error
End Sub
Run the answer and wait for the Stop line:
If the arg_ws1 were taken byVal, then if someone changes the name of the 8th worksheet, while the Stop is lighting, then it should still take the old name. It is ByVal, remember? Well, go ahead and change the name. Then continue with F5. Which name are you getting? Why?
The answer is because of the way the Sub is called with parenthesis in the arguments. These force ByVal and ignore anything explicitly written.
CPearson ByRef vs ByVal
Disclaimer - My Article for how to refer a function ByVal when it is ByRef
You need to pass the two variables you created to your second sub when you call the procedure:
Sub IAR_part_2()
Set sheetname1 = Worksheets(4)
Set sheetname2 = Worksheets(8)
Call IAR_macro (sheetname1,sheetname2)
End Sub
Related
this might be answered already from other posts I have read but still struggling to figure it out.
I have a workbook with 85 worksheets on it. Each sheet is like an invoice format, meaning it is not formatted as a normal data set. In order for me to get the data only I need, i created helper columns which only selects the data I need for consolidation. So I have a range I13:N42 which contains the data I need to consolidate.
At the end of the workbook, I already set up a Master Sheet with all the necessary headers for the data set. And there are 2 more worksheets namely "Tracking" & "AppControl" but I dont want them to be included in the loop together with the Master sheet.
For my range (filled with cell references/formulae), I need to copy only the row that has data in it.
You might have some ideas to improve the code I am currently using.
Sub Combine()
Dim i As Integer
Dim ws As Worksheet
Dim rng As Range
On Error Resume Next
For i = 1 To Sheets.Count
Sheets(i).Activate
Range("I13:N42").Select
Selection.Copy Destination:=Sheets("Master").Range("A65536").End(xlUp)(2)
Next i
End Sub
First remove On Error Resume Next. This line hides all error messages but the errors still occour, you just cannot see their messages. So if there are errors you cannot see you cannot fix them. If you don't fix them your code cannot work. Remove that line and fix your errors! Also see VBA Error Handling – A Complete Guide.
Second Avoid using Select in Excel VBA. That is a very bad practice and makes your code unreliable!
Option Explicit
Public Sub Combine()
Dim wsMaster As Worksheet ' set master worksheet
Set wsMaster = ThisWorkbook.Worksheets("Master")
Dim ExcludeWorksheets As Variant ' define worksheets names to exclude
ExcludeWorksheets = Array(wsMaster.Name, "Tracking", "AppControl")
Dim i As Long
For i = 1 To ThisWorkbook.Worksheets.Count
If Not IsInArray(ThisWorkbook.Worksheets(i).Name, ExcludeWorksheets) Then 'exclude these worksheets
ThisWorkbook.Worksheets(i).Range("I13:N42").Copy Destination:=wsMaster.Cells(wsMaster.Rows.Count, "A").End(xlUp)(2)
End If
Next i
End Sub
Public Function IsInArray(ByVal StringToBeFound As String, ByVal Arr As Variant) As Boolean
IsInArray = (UBound(Filter(Arr, StringToBeFound)) > -1)
End Function
Alternatively you can use a For Each loop which looks a bit cleaner then
Option Explicit
Public Sub Combine()
Dim wsMaster As Worksheet ' set master worksheet
Set wsMaster = ThisWorkbook.Worksheets("Master")
Dim ExcludeWorksheets As Variant ' define worksheets names to exclude
ExcludeWorksheets = Array(wsMaster.Name, "Tracking", "AppControl")
Dim ws As Worksheet
For Each ws Is ThisWorkbook.Worksheets
If Not IsInArray(ws.Name, ExcludeWorksheets) Then 'exclude these worksheets
ws.Range("I13:N42").Copy Destination:=wsMaster.Cells(wsMaster.Rows.Count, "A").End(xlUp)(2)
End If
Next ws
End Sub
If you are going to be just working with a single worksheet in your function or sub and you pass the worksheet, do you also need to pass the workbook, or does that come along with worksheet that was passed?
Option 1
Sub option1 (byref wb as workbook, byref ws as worksheet)
wb.ws.range("A1") = 8
end sub
Option 2
sub option2(byref ws as worksheet)
ws.range("A1") = 8
end sub
in option 2 is there any danger of of opening a worksheet with the same name in a different workbook then then the one you passed the worksheet from?
The worksheetobject always has a workbook as a parent. So as long as you make sure you're calling the right worksheet when passing it on, you should be fine. So something like this should always work:
dim ws as Worksheet
set ws = ThisWorkbook.Sheets("Sheet1")
call option2(ws)
I'd like to find the position of a worksheet as it is displayed in a workbook.
For example, assume I have a workbook starting with Sheet1, Sheet2 and Sheet3 in that order. Then a user drags Sheet2 to left, before Sheet1.
I want Sheet2 to return 1, Sheet1 to return 2 (and Sheet3 still to return 3).
I can't find a way to determine this in VBA.
This should do it:
Worksheets("Sheet1").Index
https://msdn.microsoft.com/en-us/library/office/ff836415.aspx
You can just iterate the Worksheets collection of the Workbook object. You can test yourself by running the following code, switch the order around in the UI, then run it again:
Option Explicit
Sub IterateSheetsByOrder()
Dim intCounter As Integer
Dim wb As Workbook
Set wb = ThisWorkbook
For intCounter = 1 To wb.Worksheets.Count
Debug.Print wb.Worksheets(intCounter).Name
Next intCounter
End Sub
To loop through all worksheets in a workbook use For Each WS in ThisWorkbook.Worksheets where WS is a worksheet object. Hence to obtain order of Excel worksheets as shown, we may also use the following code:
Sub LoopThroughWorksheets()
Dim WS As Worksheet
For Each WS In ThisWorkbook.Worksheets
Debug.Print WS.Name
Next
End Sub
To obtain an output like Worksheets("Sheet1").Index then you may use this code
Sub IndexWorksheet()
Dim WS As Worksheet, n As Long
For Each WS In ThisWorkbook.Worksheets
n = n + 1
If WS.Name = "Sheet1" Then Debug.Print n
Next
End Sub
You can use the Sheets object. In your example, reading Sheets(2).Name should return Sheet1.
Right answer provided by Anastasiya-Romanova, but missing some important details.
There are two methods of doing this. First, with a For Each loop:
Sub ListSheetNames()
Dim ws As Worksheet
For Each ws In ThisWorkbook.Worksheets
Debug.Print ws.Name
Next ws
End Sub
Second, with a basic For loop:
Sub ListSheetNames
Dim i As Long
For i = 1 to ThisWorkbook.Worksheets.Count
Debug.Print ThisWorkbook.Worksheets(i).Name
Next i
End Sub
You will find the second method will always output the names in the sheet index order, which is generally the order the sheets were created in unless you change the index. Simply rearranging the sheets from the workbook window won't change the index.
Therefore, the first method is the correct way to do this. It will always follow the tab order as you see on your screen.
Below code works even if sheet is renamed or its sequence is changed.
Sub Display_Sheet_Tab_Number()
Dim WorksheetName As String
Dim n As Integer
WorksheetName = Sheet1.Name
MsgBox Worksheetname
n = Sheets(WorksheetName).Index 'n is index number of the sheet
MsgBox "Index No. = " & n
End Sub
I have created a function to receive three parameters.:
An array of strings
A worksheet name
An index for the array of strings
When I compile I am getting a compile error stating:
Compile Error:
Expected:=
My call is:
Sub C_Button_ImportBOM_Click()
Dim strFilePathName As String
Dim strFileLine As String
Dim v As Variant
Dim RowIndex As Long
Dim mySheet As Worksheet
ActiveSheet.Name = "Import"
mySheet = Worksheets("Import")
strFilePathName = ImportFilePicker
v = QuickRead(strFilePathName)
For RowIndex = 0 To UBound(v)
PopulateNewLine (v(RowIndex), mySheet, RowIndex)
Next
End Sub
The function declaration is:
Function PopulateNewLine(SourceString As String, ImportSheet As Worksheet, CurrentRow As Long)
I have tried many things to no avail. Initially with just the first argument declared and used this worked okay.
Any ideas greatly appreciated.
Excel 2010 VBA
You are calling your function like a subroutine, i.e., you're not returning a value from it and assigning it to a variable, as you'd normally do with a function. That means you need to call it without the parentheses surrounding the arguments:
PopulateNewLine v(RowIndex), mySheet, RowIndex
If you really aren't returning anything from it, you should probably turn it into a Sub for clarity's sake:
Sub PopulateNewLine(SourceString As String, ImportSheet As Worksheet, CurrentRow As Long)
... your code here
End Sub
On another note, you should qualify your mySheet reference, and as I mentioned in the comments, use Set. It should look something like:
Set mySheet = ActiveWorkbook.Worksheets("Import")
Substitute whatever workbook Import is in for ActiveWorkbook above.
I am new in VBA coding. Lets say I am retrieving value from Sheet3.Cell(23, 4) for a value, is there any way in the VBA code which let me set this as a variable?
For example, I have changed the interface and let the value stay at Sheet4.Cell(20,1), everywhere in my code which refer to Sheet3.Cell(23, 4) need to be changed to Sheet4.Cell(20, 1). I am thinking is there any best practice for coding VBA for situation like this?
Yes. For that ensure that you declare the worksheet
For example
Previous Code
Sub Sample()
Dim ws As Worksheet
Set ws = Sheets("Sheet3")
Debug.Print ws.Cells(23, 4).Value
End Sub
New Code
Sub Sample()
Dim ws As Worksheet
Set ws = Sheets("Sheet4")
Debug.Print ws.Cells(23, 4).Value
End Sub
Yes, set the cell as a RANGE object one time and then use that RANGE object in your code:
Sub RangeExample()
Dim MyRNG As Range
Set MyRNG = Sheets("Sheet1").Cells(23, 4)
Debug.Print MyRNG.Value
End Sub
Alternately you can simply store the value of that cell in memory and reference the actual value, if that's all you really need. That variable can be Long or Double or Single if numeric, or String:
Sub ValueExample()
Dim MyVal As String
MyVal = Sheets("Sheet1").Cells(23, 4).Value
Debug.Print MyVal
End Sub