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
Related
So I am trying to write a Macro for Excel, that adds 2 worksheets from an excel file to a new one.
Therefore, I try this:
Sub addfile()
Dim sheet1 As Worksheet
Dim sheet2 As Worksheet
Set sheet1 = Sheets.Add(Type:="C:\Users\Helge\AppData\Roaming\Microsoft\Templates\page1.xltx")
Set sheet2 = Sheets.Add(Type:="C:\Users\Helge\AppData\Roaming\Microsoft\Templates\page2.xltx")
End Sub
When I test it, it imports the first page, but the 2nd page gives me a Runtime error 1004.
Why does this happen?
And is there another way to get 2 sheets from one excel file to another via vba?
Much to my surprise this version of your code actually worked for me.
Sub addfile()
Dim Sheet1 As Worksheet
Dim Sheet2 As Worksheet
Set Sheet1 = Sheets.Add(Type:=Environ("Userprofile") & "\OneDrive\Desktop\Template1.xltx")
Set Sheet2 = Sheets.Add(Type:=Environ("Userprofile") & "\OneDrive\Desktop\Book2.xlsx")
Debug.Print Sheet1.Name, Sheet2.Name
End Sub
The reason for my surprise is that Sheet1 and Sheet2 are the default CodeName for the first and second worksheets in any workbook. Therefore there is a conflict of naming between the Sheet1 in the workbook and the Sheet1 you declare which should come to the surface not later than Debug.Print Sheet1.Name. In fact, it may have. I didn't check which name was printed. But the code didn't crash. Since it crashes on your computer, perhaps you have an older version of Excel. Try to stay clear of variable names that Excel also uses. Or there is something wrong with the path & file name, which is hard to tell in that syntax and therefore kept me fooled for quite some time too.
In fact, I discovered the above only after finding out that my Desktop was on OneDrive and not before I had written the function below which is designed to avoid the use of Sheets.Add. It also has some extras such as being able to specify the sheet to take from the template (you could have one template with 2 or more sheets). You can specify an index number or a sheet name. And the function will give a name to the copy, too, if you specify one.
Private Function AddWorksheet(ByVal Template As String, _
TabId As Variant, _
Optional ByVal TabName As String) As Worksheet
Dim Wb As Workbook
Dim Path As String
Dim FileName As String
Set Wb = ThisWorkbook ' change to suit
' make sure the path ends on "\"
Path = "C:\Users\Helge\AppData\Roaming\Microsoft\Templates\"
With Workbooks.Open(Path & Template)
.Sheets(TabId).Copy After:=Wb.Sheets(Wb.Sheets.Count)
.Close
End With
Set AddWorksheet = ActiveSheet
If Len(TabName) Then ActiveSheet.Name = TabName
End Function
You can call the function from a sub routine like this:-
Sub AddWorksheets()
Dim Tab1 As Worksheet
Dim Tab2 As Worksheet
Application.ScreenUpdating = False
Set Tab1 = AddWorksheet("Page1.xltx", 1, "New Tab")
Set Tab2 = AddWorksheet("Page2.xltx", "Sheet1", "Another new Tab")
Application.ScreenUpdating = True
End Sub
Please observe the difference between the two function calls.
I have developed the below code however it shows me the error here Runtime error 9 and 13. When it comes to part that the macro should copy data from one workbook to another. I know that I wrongly assigned the variables but no clue how to change it.
Workbooks(wbk).Worksheets(FieldBVal).Range("A1:V1000").Copy Workbooks(recon).Worksheets(FieldAVal).Range("B2")
Just shortly what the macro should do. It should simply copy sheets from one workbook to another. Each sheet refers to one company so it has to be past to another workbook also to the worksheet with the same name of the company. Therefore, I have decided to put name of sheets into excel where is macro. It can happen copmpanies will be added , removed so the user can easily change the name of worksheets or add the new one (without knowing macro structure) but unfortunately sth doesnt work. Can anyone help me out?
Code:
Sub Copy data()
Workbooks.Open Range("A10").Value
For Each wb In Application.Workbooks
If wb.Name Like "*Reconciliation*" Then
wb.Activate
Exit For
End If
Next wb
Set wbk = Workbooks(Range("A9").Value)
Set recon = Workbooks(Range("A11").Value)
Sheets("Macro").Select
Range("B6").Select
Dim i As Integer
Dim FieldAVal As String
Dim FieldBVal As String
Dim Iter As Integer
Iter = Cells(1, 3).Value
For i = 1 To Iter
FieldAVal = Cells(i + 5, 2).Value
FieldBVal = Cells(i + 5, 3).Value
'SAP code to be executed for each row
Workbooks(wbk).Worksheets(FieldBVal).Range("A1:V1000").Copy Workbooks(recon).Worksheets(FieldAVal).Range("B2") here shows error
Next i
End Sub
Set your logic before you start writing code. Start by writing Option Explicit at the top of your blank code module.
It seems, you have a workbook called like "Reconciliation". It seems that you want to call this workbook Wb. Therefore your first line of code should be
Dim Wb As Workbook ' the reconciliation workbook
It appears that somewhere in that workbook there are cells A9 and A11. Where? On a worksheet. Which worksheet? That leads you to the second line of code.
Dim Ws As Worksheet ' the worksheet from which to gather company info
Continue like that until you have identified each part of your project by its nature (workbook, worksheet, string, number), by its function in your project (supplier of data, receiver of data, helper), and given it a name.
Set wbk = Workbooks(Range("A9").Value)
Set recon = Workbooks(Range("A11").Value)
creates two workbook objects. You haven't declared them and give no indication of their function in your project. But it's clear that your code will fail if the ranges A9 and A11 don't hold the names of open workbooks. They must be open because your code doesn't open them, even if the cells hold full file names with their respective paths.
Observe that both A9 and A11 are on the ActiveSheet. That is so because you don't specify any sheet in particular. The ActiveSheet will be any sheet in the object Wb with a name like "Reconciliation" that happens to be active at the time - a very vague description. Chances that the correct sheet will be found are quite slim.
All of this confusion is due to the lack of planning before you started to write code. Go back and start over. Think in much smaller steps than you have done until now. However, one step that you don't have to think is what to Select or Activate. The answer is uniformly "Nothing". Wb.Worksheets("MySheet 1").Cells(9, "A") is a very clear address. VBA can find it. It can obtain its Value, its RowHeight, its Formula and change any of these and more just as soon as it can Select or Activate it. Activating and selecting is an action the user needs. VBA or Excel don't.
And, before I forget, VBA addresses range by name and cells by their coordinates. Range("A9") is a work-around to use a synthetic name for a range which is a single cell. Nothing good will ever come of such acrobatics. Since you already mastered the syntax for addressing cells, stick with it for that purpose. Use names to address ranges of several cells but bear in mind that names like "A1:C7" are artificially constructed from cell coordinates. It's a great system but, alas, the lowest rung on that particular ladder. You can do much more with real names that you create and manage yourself.
thanks for the feedback but there is still the error when it comes to the part copy and paste. I named sheets and workbooks but this combination below doesnt work.
Workbooks(wbk1).Worksheets(ws1).Range("A1:V1000").Copy Workbooks(wbk2).Worksheets(ws2).Range("B2")
Sub CopyData()
Dim i As Integer
Dim FieldAVal As String
Dim FieldBVal As String
Dim FieldCVal As String
Dim FieldDVal As String
Dim wbk1 As Workbook
Dim wbk2 As Workbook
Dim ws1 As Worksheet
Dim ws2 As Worksheet
Dim Iter As Integer
Dim recon As Workbook
Dim FilePath As String
FilePath = ThisWorkbook.Worksheets("Macro").Cells(11, 1)
Set recon = Workbooks(FilePath)
Workbooks.Open Range("A10").Value
recon.Activate
Iter = Cells(1, 3).Value
For i = 1 To Iter
FieldAVal = Cells(i + 14, 2).Value
FieldBVal = Cells(i + 15, 3).Value
FieldCVal = Cells(i + 16, 4).Value
FieldDVal = Cells(i + 17, 5).Value
Set wbk1 = Workbooks(FieldDVal)
Set wbk2 = Workbooks(FieldCVal)
Set ws1 = wbk1.Sheets(FieldBVal)
Set ws2 = wbk2.Sheets(FieldAVal)
Workbooks(wbk1).Worksheets(ws1).Range("A1:V1000").Copy Workbooks(wbk2).Worksheets(ws2).Range("B2")
Next i
End Sub
I am using the code below in a master workbook to open workbooks listed in the range E12:E24.
Once I have opened these workbooks, I need to count the number of open workbooks (in addition to the master workbook) and assign the number to cell E2 in the Portfolio Results sheet.
The code below works just as I would like except I get an error message on the line Worksheets("Portfolio Results").Range("E2") = nFields
It's unclear to me why this is the case. Thanks for any help.
Sub SkipBlankCells2()
Dim cell As Range, rng As Range, FName As String, nFields As Integer
Set rng = Range("E12:E24")
Application.DefaultFilePath = ActiveWorkbook.Path
nFields = 0
For Each cel In rng
If Len(cel) >= 1 Then
FName = cel.Value
Workbooks.Open Filename:=FName
nFields = nFields + 1
End If
Next cel
Debug.Print nFields
Worksheets("Portfolio Results").Range("E2") = nFields
End Sub
You should (almost always) have Option Explicit as your first line of the code.
Your current code contains an error, where you declared the variable
Dim cell as Range
but then in your For loop you're using an undeclared variable cel which defaults to a Variant data-type.
with Option Explicit enabled, the compiler will warn you about these kind of errors.
As to the actual answer, when switching between objects (be it Worksheet or Workbook) it's always a good programming practice to explicitly declare them.
Easiest way to access them is to store them inside a variable:
Dim wb as Workbook: Set wb = Workbooks("Static name")
Dim ws as Worksheet: Set ws = wb.Sheets("Your sheet name")
'later in the code
If wb.ws.Cells(1, 9) = "banana" Then '...
Not only this makes for a more read-able code for somebody else (because if somebody inherits your project, they can't know which workbook or worksheet intended the author to work with), but it also prevents these unnecessary kind of errors where a different Workbook or Worksheet is selected.
As a final note, if you don't know which Workbook might be open at the moment, but want to reference "this" specific one, then use ThisWorkbook instead
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 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.