VBA copy number of Rows to new Workbook - excel

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.

Related

Copy from one workbook to another using loop

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

Can't set xVar = range from worksheet.cells(numRow, yVarColumn)

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

How to use Count If function inside of a sub?

I am trying to count the number of times the string shows up in the range and return to worksheet 1. I am assuming this can be done in a Function () but I'm don't know how to write out the syntax.
Sub DistributedApps()
Dim LastRow As Long
Dim Dist As Long
LastRow = Worksheets(3).Cells(Rows.Count, 25).End(xlUp).Row
Dist = Application.Worksheets(3).WorksheetFunction.CountIf(Range("Y1:Y" & LastRow), "Distributed Apps")
Worksheets(1).Range("N66:P69").Value = Dist
End Sub
Object doesn't support this property or method
Dist = Application.Worksheets(3).WorksheetFunction...
The Worksheets property is returning an Object that could be either a Sheets collection (if given an array of sheet names) or a Worksheet object (if given one sheet name, or a sheet index), so you're getting a Worksheet object, but VBA only knows this at run-time.
Move that knowledge to compile-time by introducing a local Worksheet variable; note that Application.Worksheets will give you the sheets collection of whatever workbook is currently active, so it might be a good idea to make that more explicit by qualifying the member call with an actual Workbook object:
Dim sheet As Worksheet
Set sheet = ActiveWorkbook.Worksheets(3)
Now that VBA knows what interface this object has, the editor can help you: when you type the dot in sheet., you'll get a list of all members of the Worksheet interface - and see that none of them is WorksheetFunction: that is why error 438 is raised at run-time, the object doesn't support this property.
The Application object does have a WorksheetFunction member though, so this will work.
lastRow = sheet.Cells(sheet.Rows.Count, 25).End(xlUp).Row
dist = Application.WorksheetFunction.CountIf(sheet.Range("Y1:Y" & lastRow), "Distributed Apps")
Note that the Range member call inside the CountIf argument list is also explicitly qualified with the sheet object. Without this qualifier, Range would be referring to whatever the ActiveSheet is1, and since you don't want to need to Activate any sheets for this to work, using an explicit worksheet object as a qualifier ensures you're computing the count off the correct sheet.
1Unless that code is written in the code-behind for a Worksheet module - in which case the implicit qualifier is Me and the unqualified Range call is referring to that worksheet.
I think this will work.
Sub DistributedApps()
Dim LastRow As Long
Dim Dist As Long
LastRow = Worksheets(3).Cells(Rows.Count, 25).End(xlUp).Row
Dist = Application.WorksheetFunction.CountIf(Worksheets(3).Range("Y1:Y" & LastRow), "Distributed Apps")
Worksheets(1).Range("N66:P69").Value = Dist
End Sub

How to get dependent drop-down lists to work in exported workbook?

I'm still reasonably new to VBA and feel I'm punching a little above my weight, so hopefully someone can help.
I need to issue a spreadsheet to people in my company which they can fill out and send it back. This needs to be done multiple times, so I have tried to automate this as much as possible. The source data is pasted in an "input" tab - this is then pivoted by user and input into a template tab. I can select any user and run a macro which does this and exports the filled out template to a new workbook.
In this template tab, I have dependent drop-down lists, which I have done by data validation - this relies on named ranges from the "coding" tab, which is also exported. One named range shows a list of values, and the other indexes over this and matches it to the required cell, to ensure only valid combinations are shown.
My issue is that the new workbook must not contain any links to the master - it should function completely in its own right. However, something is going wrong with the data validation/named ranges. Either some named ranges are being deleted (I know which bit of code is doing that but without it you get prompted to update links) or the data validation formula links back to the original workbook and doesn't work. I cannot find another way of achieving what I need without this particular data validation set up, so I need to try and adjust my macro to cater for this.
Is it possible to simply copy the template and coding tabs, with all the data validation, to a new workbook and break all links to the original, so that there are no startup prompts and the drop-downs all work?
Sub Copy_To_New_Workbook()
Dim wb As Workbook
Dim name As String
Dim ExternalLinks As Variant
Dim x As Long
Dim strFolder As String, strTempfile As String
name = Worksheets("Control").Cells(14, 7).Value
Let FileNameIs = Range("Filepath").Value & Range("FileName").Value
Set wb = Workbooks.Add
ThisWorkbook.Worksheets("Coding").Copy Before:=wb.Sheets(1)
ActiveSheet.name = "Coding"
ThisWorkbook.Worksheets("Transactions").Copy Before:=Worksheets("Coding")
ActiveSheet.name = "Transactions"
With ActiveSheet.UsedRange
.Value = .Value
End With
Application.DisplayAlerts = False
Worksheets("Sheet1").Delete
Application.DisplayAlerts = True
ExternalLinks = wb.LinkSources(Type:=xlLinkTypeExcelLinks)
ExternalLinks = wb.LinkSources(Type:=xlLinkTypeExcelLinks)
For x = 1 To UBound(ExternalLinks)
wb.BreakLink name:=ExternalLinks(x), Type:=xlLinkTypeExcelLinks
Next x
Dim objDefinedName As Object
For Each objDefinedName In wb.Names
If InStr(objDefinedName.RefersTo, "[") > 0 Then
objDefinedName.Delete
End If
Next objDefinedName
On Error GoTo 0
wb.SaveAs Filename:=FileNameIs, FileFormat:=52
ActiveWorkbook.Close
End Sub

Reference excel worksheet by name?

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.

Resources