Runtime Error 1004 using Select with several workbooks - vba

I have an Excel workbook which pulls out data from two other workbooks.
Since the data changes hourly there is the possibility that this macro is used more than one time a day for the same data.
So I just want to select all previous data to this date period and want to delete them.
Later on the data will be copied in anyway.
But as soon as I want to use
WBSH.Range(Cells(j, "A"), Cells(lastRow - 1, "M")).Select
the code stopes with Error 1004 Application-defined or object-defined error.
Followed just a snippet of the code with the relevant part.
What is wrong here?
'Set source workbook
Dim currentWb As Workbook
Set currentWb = ThisWorkbook
Set WBSH = currentWb.Sheets("Tracking")
'Query which data from the tracking files shoud get pulled out to the file
CheckDate = Application.InputBox(("From which date you want to get data?" & vbCrLf & "Format: yyyy/mm/dd "), "Tracking data", Format(Date - 1, "yyyy/mm/dd"))
'states the last entry which is done ; know where to start ; currentWb File
With currentWb.Sheets("Tracking")
lastRow = .Range("D" & .Rows.Count).End(xlUp).Row
lastRow = lastRow + 1
End With
'just last 250 entries get checked since not so many entries are made in one week
j = lastRow - 250
'Check if there is already data to the look up date in the analyses sheet and if so deletes these records
Do
j = j + 1
'Exit Sub if there is no data to compare to prevent overflow
If WBSH.Cells(j + 1, "C").Value = "" Then
Exit Do
End If
Loop While WBSH.Cells(j, "C").Value < CheckDate
If j <> lastRow - 1 Then
'WBSH.Range(Cells(j, "A"), Cells(lastRow - 1, "M")).Select
'Selection.ClearContents
End If
Thank you!

Actually you can't use Select for on Range on a Sheet which is not selected/activated. Select only works on the active worksheet.
You should have something like
WBSH.Activate
before
WBSH.Range(Cells(j, "A"), Cells(lastRow - 1, "M")).Select
WBSH.Range(WBSH.Cells(j, "A"), WBSH.Cells(lastRow - 1, "M")).Select
Take a look here: Selecting and activating Cells
NOTE: Avoid using Select as much as you can
EDIT: To clear the content (As asked in the comments), you should use someting like
WBSH.Range(WBSH.Cells(j, "A"), WBSH.Cells(lastRow - 1, "M")).Clear
instead of (I imagine)
WBSH.Range(Cells(j, "A"), Cells(lastRow - 1, "M")).Clear
because the cells in your range needs to be qualified.
You can use Clear which deletes the formatting of the cell and the value in that cell, and ClearContent which deletes only the value in the cell.
Note that you can use With:
With WBSH
.Range(.Cells(j, "A"), .Cells(lastRow - 1, "M")).Clear
End With

Related

If Date in this range can be found in separate range, delete row

I have a Excel workbook, almost like a database, where I update Historical data each week. Using a separate sub, I pull in an Export as a worksheet to the book. I find the unique dates that are in the export. I then look at Historical data, and if the Historical date matches one of the Export dates, I delete the row in Historical. Eventually I copy and paste the Export in to the Historical data tab.
The code below works how I'd like it to, but I have some questions after the block of code:
Sub AddNewData()
'This will take what's in Export and put it in to Historical
Dim Historical As Worksheet
Dim Export As Worksheet
Dim exportdates As Range
Set Historical = ThisWorkbook.Worksheets("Historical")
Set Export = ThisWorkbook.Worksheets("Export")
'Pulling unique values of dates from this range and pasting to M1:
Export.Range("B2:B" & Export.Cells(Export.Rows.Count, 1).End(xlUp).Row).AdvancedFilter _
Action:=xlFilterCopy, CopyToRange:=Export.Range("M1"), Unique:=True
'Originally I was thinking I could make this a list of some sort vlookup or match?
'As of now, though, it goes unused...:
Set exportdates = Export.Range("M1:M" & Export.Cells(Export.Rows.Count, 13).End(xlUp).Row)
For r = Historical.Cells(Rows.Count, 1).End(xlUp).Row To 1 Step -1
If Historical.Cells(r, 2).Value = exportdates(1, 1).Value Or _
Historical.Cells(r, 2).Value = exportdates(2, 1).Value Or _
Historical.Cells(r, 2).Value = exportdates(3, 1).Value _
Then Historical.Rows(r).Delete
Next
'Copying and pasting Export data to Historical tab
Export.Range("A2:J" & Export.Cells(Export.Rows.Count, 1).End(xlUp).Row).Copy
Historical.Range("A" & Historical.Cells(Historical.Rows.Count, 1).End(xlUp).Row + 1).PasteSpecial xlPasteValues
Application.CutCopyMode = False
End Sub
1) Can that IF statement be condensed somehow using the exportdates range?
2) This works just fine for a few hundred rows of data when my dates are simply the first of each month, but I also have an export that has each day as a date that I'll have to match with a different tab with daily information. That one has THOUSANDS of rows. I don't believe this macro will be much more efficient than simply sorting by date and eliminating myself? Can I change the IF statement to be more inclusive, like question 1?
Thank you!
Whenever you have to delete many rows in Excel with VBA, the best practice is to assign these rows to a range and to delete the range at the end.
Thus, your code should be refactored in this part:
For r = Historical.Cells(Rows.Count, 1).End(xlUp).Row To 1 Step -1
If Historical.Cells(r, 2).Value = exportdates(1, 1).Value Or _
Historical.Cells(r, 2).Value = exportdates(2, 1).Value Or _
Historical.Cells(r, 2).Value = exportdates(3, 1).Value _
Then Historical.Rows(r).Delete
Next
This is a simple sample that you can use for the refactoring (just make sure to write a few times 1 in Range("A1:A20") to see how it works:
Public Sub TestMe()
Dim deleteRange As Range
Dim cnt As Long
For cnt = 20 To 1 Step -1
If Cells(cnt, 1) = 1 Then
If Not deleteRange Is Nothing Then
Set deleteRange = Union(deleteRange, Cells(cnt, 1))
Else
Set deleteRange = Cells(cnt, 1)
End If
End If
Next cnt
deleteRange.EntireRow.Select
Stop
deleteRange.EntireRow.Delete
End Sub
Once you run the code it stops at the Stop sign. You see that the rows to be deleted are selected. Once you continue with F5 they would be deleted. Consider removing the Stop and .Select line in your code.
Some general ideas how to speed up code: https://stackoverflow.com/a/49514930/5448626

Using VBA to Add Rows based on a cell value onto another worksheet

I am trying to create a spreadsheet whereby I have a value in a cell in a worksheet called "Equipment" cell C5, for example a Value of 4.
Starting Cell Image
I need to use this value to copy a section of the same row (D5:M5) and paste it that many times into a worksheet called "Programming" also if this changes I would like it to delete or add where required, ignoring where there is a blank or 0 value in the "equipment" sheet
Desired Result
I have around 30 different items and all will have different sections to copy but they will be of the same size. Also Could this look down a list of values all in the same column and do the same for all the values
I'm very new to VBA and have managed to hide and show tabs based on values but i'm struggling to get my head around this as it's a little too complicated at this point.
Thank You in advance
Lee
This is what I have so far, I have edited the code to what I believe is correct but it still isn't working
Sub copySheetCells()
'loop by each cell in column "C"
For i = 2 To Sheets("Equipment").Cells(Rows.Count, "C").End(xlUp).Row
'repeat copy x times (based on cell in column "C" value)
For j = 0 To (Sheets("Equipment").Cells(i, "C").Value - 1)
'define source range
Source = "D" & (i) & ":M" & (i)
'find last row on second sheet
lastRowS2 = Sheets("Hardware_Programming").Cells(Rows.Count, "A").End(xlUp).Row
'copy data
Sheets("Equipment").Range(Source).copy Destination:=Sheets("Hardware_Programming").Range("A" & lastRowS2 + 1)
Next j
Next i
'copy headers
Sheets("Equipment").Range("D1:M1").copy Destination:=Sheets ("Hardware_Programming").Range("A1:J1")
End Sub
I only get blank spaces, is anyone able to advise any further?
Here you go, use this macro. Based on names Programming and Equipment as originally requested.
Sub copySheetCells()
'loop by each cell in column "C"
For i = 2 To Sheets("Programming").Cells(Rows.Count, "C").End(xlUp).Row
'repeat copy x times (based on cell in column "C" value)
For j = 0 To (Sheets("Programming").Cells(i, "C").Value - 1)
'define source range
Source = "D" & (i) & ":M" & (i)
'find last row on second sheet
lastRowS2 = Sheets("Equipment").Cells(Rows.Count, "A").End(xlUp).Row
'copy data
Sheets("Programming").Range(Source).copy Destination:=Sheets("Equipment").Range("A" & lastRowS2 + 1)
Next j
Next i
'copy headers
Sheets("Programming").Range("D1:M1").copy Destination:=Sheets("Equipment").Range("A1:J1")
End Sub
EDIT
Please avoid copying the code from the answer and posting it back at your question, I replaced the Sheet1 with Programming so you can rename that sheet in your workbook.
Macro seems to do what it does, the quantity in Sheet1/Programming was not provided (column "C" according to your initial requirements):
Source (with added quantity)
Result:
Hope this will solve your problem :)
For i = 1 To 30 Step 1
If Sheets("Equipment").Cells(1 + 4, 3).Value > 0 Then
Sheet1.Range(Cells(i + 3, 5), Cells(i + 3, 13)).Copy
For j = 1 To Sheet1.Cells(1 + 4, 3).Value Step 1
LR = Sheets("Programming").Cells(Sheets("Programming").Rows.Count, "A").End(xlUp).Row
Sheets("Programming").Range("A" & LR + 1).PasteSpecial Paste:=xlPasteValues
Next
End If
Next
Cheers ;)

VBA-code does not look through hidden rows for adding a row with tracking number

I have another question which I hope to resolve with your help.
What do I want to do.
I use Excel to track my work, activities, contacts, et cetera. While doing that I found I was doing a lot of repetitive work in adding rows at the end of a sheet called "Activities".
What I want to do is this:
- Press a button and adding a row.
- Increase the trackingnumber with 1
- Insert default values
The code.
To automate this, I have found (copy, pasted, adjusted it to my needs) the following code:
Sub AddRowActiviteiten_NewAtEnd()
'Add's a new row at the end of the sheet.
Dim wsActiviteiten As Worksheet
Set wsActiviteiten = Sheets("Activiteiten")
DefType = "Daily"
DefStatus = "Open"
DefIssue = "*****"
DefImpact = "*****"
DefPrio = "Laag"
MyDate = Date
wsActiviteiten.Range("A4").Value = "1"
'Copy the "One Row To Rule Them All"
wsActiviteiten.Range("A3:Q3").Copy
wsActiviteiten.Range("A" & Rows.Count).End(xlUp).Offset(1, 0).PasteSpecial (xlPasteAll)
'Stop the "copy-action"
Application.CutCopyMode = False
'Increase the tracking number with "one"
LastNumber = wsActiviteiten.Range("A" & Rows.Count).End(xlUp).Value
wsActiviteiten.Range("A" & Rows.Count).End(xlUp).Offset(1, 0).Value = LastNumber + 1
'Insert default values
LastRow = wsActiviteiten.Range("A" & Rows.Count).End(xlUp).Offset(-1, 0).Row
Cells(LastRow + 1, 2) = DefType
Cells(LastRow + 1, 3) = DefStatus
Cells(LastRow + 1, 4) = DefIssue
Cells(LastRow + 1, 5) = DefImpact
Cells(LastRow + 1, 6) = DefPrio
Cells(LastRow + 1, 8) = MyDate
'Step down 1 row from present location.
ActiveCell.Offset(1, 0).Select
End Sub
The problem.
In this sheet I open new items, but I also close them. I do this by changing their status and hide them from view. And this is the point where it goes wrong. When I close the last item on the list and want to add a new row, the macro adds a new row below the last visible entry. It does not find the last entry I have just hidden. And also, when this happens, adding the default values to the new row does not work. It adds them at the row above the added one.
Somehow this makes perfect sense. I tell the macro to look for the last entry, but what I don't understand is why it looks at the last visible entry and why it does not look in the hidden rows.
To replicate. Copy the code into a sheet (maybe you need to change the name of the sheet) and add a few lines. Put some info in the last row and hide it. Add another few lines and see what happens.
The solution. Is there a way to resolve this? Maybe there is a smarter way of doing things? I looked into things, but mostly I got results using "("A" & Rows.Count).End(xlUp)". A loop could work, but I am afraid that 1) It does not search through hidden rows and 2) it makes the sheet (somewhat) sluggish. I must say I have tried to make a loop, first I want to see if my first solution is salvageable.
Thank you for your input, if there are any questions please let me know.
Simon
EDIT: Working code for anyone interested
Sub AddRowActiviteiten_NewAtEnd()
'Add's a new row at the end of the sheet.
Dim wsActiviteiten As Worksheet
Set wsActiviteiten = Sheets("Activiteiten")
DefType = "Daily"
DefStatus = "Open"
DefIssue = "*****"
DefImpact = "*****"
DefPrio = "Laag"
MyDate = Date
'Copy the One Row To Rule Them All
wsActiviteiten.Range("A3:Q3").Copy
'Offset(y,x)
'De -16 is een getal dat iets doet, maar ik weet niet wat.
wsActiviteiten.Range(Split(ActiveSheet.AutoFilter.Range.Address, ":")(1)).Offset(1, -16).PasteSpecial (xlPasteAll)
'Stop the "copy-action"
Application.CutCopyMode = False
'Het volgnummer verhogen met 1
'Het laatste getal selecteren (LastNumber) en dan plus 1.
LastNumber = wsActiviteiten.Range(Split(ActiveSheet.AutoFilter.Range.Address, ":")(1)).Offset(0, -16).Value
wsActiviteiten.Range(Split(ActiveSheet.AutoFilter.Range.Address, ":")(1)).Offset(1, -16).Value = LastNumber + 1
'Insert default values
LastRow = wsActiviteiten.Range(Split(ActiveSheet.AutoFilter.Range.Address, ":")(1)).Offset(-1, 0).Row
Cells(LastRow + 1, 2) = DefType
Cells(LastRow + 1, 3) = DefStatus
Cells(LastRow + 1, 4) = DefIssue
Cells(LastRow + 1, 5) = DefImpact
Cells(LastRow + 1, 6) = DefPrio
Cells(LastRow + 1, 8) = MyDate
'Step down 1 row from present location.
ActiveCell.Offset(1, 0).Select
End Sub
Update
I see your sheet has an autofilter "hiding" the status rows - which Find wont detect, unlike hidden rows.
Suggest you try this updated code below:
Sub Test()
Dim rng1 As Range
If ActiveSheet.AutoFilterMode Then
MsgBox ActiveSheet.Range(Split(ActiveSheet.AutoFilter.Range.Address, ":")(1)).Row
Else
Set rng1 = Columns("A:A").Find("*", [a1], xlFormulas, , xlByRows, xlPrevious)
If Not rng1 Is Nothing Then MsgBox rng1.Row
End If
End Sub
initial post
If you are hiding rows then you can use Find with the xlFormulas option to find entries in hidden rows (unlike xlValues).
Dim rng1 As Range
Set rng1 = Columns("A:A").Find("*", [a1], xlFormulas, , xlByRows, xlPrevious)
MsgBox rng1.Address
Say we have a status column AB and we currently close an item by placing the word "Closed" in that column and then hiding the row.
Instead:
Unhide all rows
Perform any required inserts and edits
Via a loop, hide all rows marked "Closed"
Use this for getting the last row and it will see the last row, even if it is hidden.
LastRow = wsActiviteiten.UsedRange.Rows.Count
I've just found solution:
If you have at least one column in your range with 'consistent' data (all cells in that column are not empty/blank) you can use formula COUNTA and you can reference you code to value of that COUNTA formula.
For example:
Insert formula '=COUNTA(A1:A100000)' in 'B1' cell.
In B1 you will get how many rows you have they are hidden or not.
In your code change:
wsActiviteiten.Range("A" & Rows.Count).End(xlUp).Offset(1, 0).PasteSpecial (xlPasteAll)
to
wsActiviteiten.Range("A" & Range("B1").Value).Offset(1, 0).PasteSpecial (xlPasteAll)
Of course, if, for example, cell 'A2' is blank and this is the only blank cell you will need to adjust your formula to '=COUNTA(A1:A100000) +1'.
If you have more blank/empty cells and you don't know the exact number of them (blank cells have been changed dynamically) this method will not work.
As I said previously you need to have at least one column with 'consistent' data (with known number of empty cells in advance if any).

Excel VBA User form entry to a specific range inside worksheet

So I am currently trying to have a user input some information and put that information in a specific range based on what values in the dropdown list are selected. For example, I selected Monday, so I want the information to go in the Range A1:A12, but if they select Tuesday then go to range G1:G12.
Also, if the range is already full with data, I'd like for it to tell the user that it is full. Do not have any example code, but here is a psuedo-code example
Private Sub cbSubmit_Click()
range("A2").Select
ActiveCell.End(xlDown).Select
lastrow = ActiveCell.Row
MsgBox lastrow
If ComboBox1.Value = "Monday" Then
Cells(lastrow + 1, 1).Value = tb1.Text
Cells(lastrow + 1, 2).Value = tb2.Text
End If
If ComboBox1.Value = "Tuesday" Then
range("G2").Select
ActiveCell.End(xlDown).Select
lastrow2 = ActiveCell.Row
Cells(lastrow2 + 1, 1).Value = tb1.Text
Cells(lastrow2 + 1, 2).Value = tb2.Text
End If
End Sub
Also, in the above code, is there a better way to find the last cell in the range that is blank? This one only works if there is already data in the range and that's not always the case.
And also check some sort of CountA or something to see if the range is already full of data. Thanks in advance!
I will answer just so I can demonstrate code better.
As I said in the comments, if you want to check each value in a range, you can do so like this:
Dim c
For Each c in Range("A1:A10") 'Whatever your range to check is
If c <> vbNullString Then
'Found Data - not empty
Exit For
End If
Next
If you're just checking 2 cells as it looks like you are, you should probably just use:
If Cells(lastrow2 + 1, 1) <> vbNullString and Cells(lastrow2 + 1, 2) <> vbNullString
If you just want to add the data to the bottom of the list, your code is already getting the last row and adding to it ... so each time you call this:
ActiveCell.End(xlDown).Select
lastrow2 = ActiveCell.Row
It is getting the last row and the rest of your code adds it to the end.
One more thing. You should really replace this:
range("G2").Select
ActiveCell.End(xlDown).Select
lastrow2 = ActiveCell.Row
With this:
lastrow2 = range("G2").End(xlDown).Row
You should avoid using select as often as possible.
In my experience, it is really only necessary when displaying a different sheet.

How to paste data to specific cell range?

I'm new to vba and need a little help. I have a sheet named "Archive" which will have 12 sets of data displayed/structured in somewhat of a table format. My goal is to pull data from other sheets within the same workbook and paste it in a specific range that corresponds to the appropriate "table" for that data. Here is my code for data that is being pulled from a sheet named "Daily DB" and is being pasted to the "Archive" sheet.
Sub GetDailyDataByWeek()
Dim cw As Integer ' current week
Dim lr As Long 'last row of data
Dim i As Long ' row counter
'Clear exsisting contents
Worksheets("Archive").Range("A5:E11").ClearContents
'Get week number and year of current date
cw = Format(Date, "ww")
With Worksheets("Daily DB")
' Find last row of data
lr = .Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).Row
For i = 2 To lr
If Format(.Cells(i, 1).Value, "ww") = cw Then
.Range(.Cells(i, 1), .Cells(i, 5)).Copy
Worksheets("Archive").Range("a" & Rows.Count).End(xlUp).Offset(1, 0).PasteSpecial xlPasteFormulasAndNumberFormats
End If
Next i
End With
Application.CutCopyMode = False
End Sub
This code does what I want it to do. The line that I need help in fixing is:
Worksheets("Archive").Range("a" & Rows.Count).End(xlUp).Offset(1, 0).PasteSpecial xlPasteFormulasAndNumberFormats
As this line looks for the last row of data, which in my case would be the header row of the 12th table. However, I'd like this particular data to go into the first table which after the header row starts at "A5", but I'm not sure how to go about that. Any and all help is greatly appreciated.
If you want to replicate the data from another cell or range into the same workbook I would use the "Value" method of the Range object, like this:
Worksheets("Archive").Range("A" & i).Value = Worksheets("XXX").Range("Z" & j).Value
By doing it like this you would avoid doing all the copy and paste operations.
If you dont want to specify a Range for each value, you could activate the firs cell of the first row and then "offset" your way through, like this:
Worksheets("Archive").Range("A" & i).Activate
ActiveCell.Value = blah blah blah
ActiveCell.Offset(1, 0).Activate 'If you want to move to the next row (same column)
ActiveCell.Offset(0, 1).Activate 'If you want to move to the next column (Same row)