Find first empty row in Excel and select - vba

I tried adapting this post: Error in finding last used cell in VBA to my needs but couldn't quite get it to work.
I'm pasting data into a new worksheet and then want to select the first empty row after data. Currently what's happening is the data is pasted and then the very first row in the sheet is selected. See code below. Any thoughts?
'runs when user enters data
If Target.Cells.Count = 1 And _
Not Application.Intersect(Target, [I3:I10000]) Is Nothing Then
Application.EnableEvents = False
'User inputs type of event
Archive = InputBox("Was this event a Win, Loss, or Close? (Please input Win/Loss/Close)")
With Target
If Archive = "Win" Then
'all data to transfer is selected and cut
.EntireRow.Select
Selection.Cut
'the receiving sheet is selected and data is pasted to the selected cell
Sheets("Win").Select
ActiveSheet.Paste
'the selection on the sheet the data was cut from is deleted
Sheets("Begin").Select
Selection.Delete
'this is the issue I'm having - I want to select the row below the row I just copied into.
Sheets("Win").Select
lastRow = Range("C" & .Rows.Count).End(xlUp).Row
ActiveSheet.Range("C" & lastRow & ":C" & lastRow).EntireRow.Select
Sheets("Begin").Select

Try replacing this:
'this is the issue I'm having - I want to select the row below the row I just copied into.
Sheets("Win").Select
lastRow = Range("C" & .Rows.Count).End(xlUp).Row
ActiveSheet.Range("C" & lastRow & ":C" & lastRow).EntireRow.Select
with this:
With Sheets("Win")
lastRow = .Range("C" & .Rows.Count).End(xlUp).Row
.Cells(lastRow + 1, 1).EntireRow.Select
End With

Just to add to the existing answer. You can avoid doing so much selection by using a construction more like this:
On Error GoTo problem
Dim Archive As String
If (Target.Cells.Count = 1) And _
Not (Excel.Application.Intersect(Target, [I3:I10000]) Is Nothing) Then
Excel.Application.EnableEvents = False
'User inputs type of event
Archive = InputBox("Was this event a Win, Loss, or Close? (Please input Win/Loss/Close)")
With Target
'>>>> good idea to defend against users entering "win" instead of "Win"
If (LCase(Archive) = "win") Then
'>>>> find the last row in Win sheet at the beginning
With Sheets("Win")
lr = .Range("C" & .Rows.Count).End(Excel.xlUp).Row
End With
'>>>> as you are cutting there should be no need to do any subsequent deletion or clearcontents
.EntireRow.Cut Sheets("Win").Rows(lr + 1)
End If
End With
End If
problem:
Excel.Application.EnableEvents = True

Related

Find & Replace blank cells in a dynamic range after transfering data into it VBA

Okay I know there are multiple questions similar to this, but at least from what I've found, my problem is different so please bear with me.
I am building a user form that allows users to input data into a tracking sheet, but there are certain scenarios where only part of the data will get submitted at a time,leaving "holes" in the tracking sheet. My goal is to fill these "holes" with a period so that way relevant data will stay together in the same row instead of getting bumped up into those holes.
Sheets("Tracker").Activate
Worksheets("Tracker").Columns("A:J").Replace What:="", Replacement:=".", _
SearchOrder:=xlByColumns, MatchCase:=False
I realize I am essentially telling excel to fill columns A through J with a period, so my question is, is there an easier way to do this, or a way to specify that I only need the most recent row to contain the periods?
Update:
This is the code I am using to find the next available row in the tracker for new data to go. Thus when portions of the previous row are missing, the macro fills those unused cells with the next rows data.
Set ws = Sheets("Tracker") Sheets("Tracker").Select
ws.Range("E" & LastRow).Value = job_txtb.Text
'Finds the last blank row
LastRow = ws.Range("A" & ws.Rows.Count).End(xlUp).Row + 1
'Adds the Job name Code into Col E & Last Blank Row
The following will give you the first unused row. I don't understand why periods would be necessary.
With ActiveSheet
On Error Resume Next
lastRow = .Cells.Find("*", .Cells(.Cells.Count), xlFormulas, _
xlWhole, xlByRows, xlNext).Row
If Err <> 0 Then lastRow = 0
End With
firstUnusedRow = lastRow + 1
Range("A" & firstUnusedRow).Value = Textbox1.Value
Range("B" & firstUnusedRow).Value = Textbox2.Value
Range("C" & firstUnusedRow).Value = Textbox3.Value
'etc.
'etc.,,

Need more efficiency than For Each Loop vba

I am a newcomer to vba/excel macros and need a more efficient way to run the below code. I am using a for each loop to return a value from a row based on a column's value (same row). The code works, but takes far too much processing power and time to get through the loops (often freezing the computer or program). I would appreciate any suggestions...
'The following is searching each cell in a range to determine if a cell is not empty. If the cell is not empty, the macro will copy the value of the cell and paste it in to another worksheet (same row)
Set rng = Worksheets("Demographic").Range("AU2:AU" & lastRow)
i = "2"
For Each cell In rng
If Not IsEmpty(cell.Value) Then
Sheets("Demographic").Range("AU" & i).Copy
Sheets("Employee import").Range("F" & i).PasteSpecial xlPasteValues
End If
i = i + 1
Next
'The following is searching each cell in a range to determine if a cell contains a "T". If the cell contains a "T", the macro will copy the value of a different column (same row) and paste it in to another worksheet (same row)
Set rng = Worksheets("Demographic").Range("AM2:AM" & lastRow)
i = "2"
For Each cell In rng
If cell.Value = "T" Then
Sheets("Demographic").Range("AO" & i).Copy
Sheets("Employee import").Range("G" & i).PasteSpecial xlPasteValues
End If
i = i + 1
Next
A formula array should be your best hope. This supposes that the cells that do not match will lead to empty values in the destination range:
chk = "Demographic!AU2:AU" & lastRow
src = "Demographic!AU2:AU" & lastRow
With Sheets("Employee import").Range("F2:F" & lastRow)
.FormulaArray = "=IF(" & chk & "<> """"," & src & ", """")"
.Value = .Value '<-- if you want to remove the formulas and keep only the copied values
End With
chk = "Demographic!AM2:AM" & lastRow
src = "Demographic!AO2:AO" & lastRow
With Sheets("Employee import").Range("G2:G" & lastRow)
.FormulaArray = "=IF(" & chk & "= ""T""," & src & ", """")"
.Value = .Value '<-- if you want to remove the formulas and keep only the copied values
End With
Not sure that it will be faster with your dataset though, you can only verify by trying it.
If you just want a straight data transfer (ie no formulas or formats), and your data set is large, then you could consider writing the data in one batch by way of an array.
Your own code shouldn't be horrendously slow though, so it suggests you have some calculations running or maybe you're handling Worksheet_Change events. If this is possible, then you might want to disable those during the data transfer:
With Application
.EnableEvents = False
.ScreenUpdating = False
.Calculation = xlCalculationManual
End With
Just remember to reset them at the end of your routine:
With Application
.EnableEvents = True
.ScreenUpdating = True
.Calculation = xlCalculationAutomatic
End With
If you went the array route, skeleton code would be like so:
Dim inData As Variant
Dim outData() As Variant
Dim r As Long
'Read the demographic data
With Worksheets("Demographic")
inData = .Range(.Cells(2, "AU"), .Cells(.Rows.Count, "AU").End(xlUp)).Value2
End With
'Use this if your column F is to be entirely overwritten
ReDim outData(1 To UBound(inData, 1), 1 To UBound(inData, 2))
'Use this if you have exisiting data in column F
'With Worksheets("Employee import")
' outData = .Cells(2, "F").Resize(UBound(inData, 1)).Value2
'End With
'Pass the values across
For r = 1 To UBound(inData, 1)
If Not IsEmpty(inData(r, 1)) Then
outData(r, 1) = inData(r, 1)
End If
Next
'Write the new values
Worksheets("Employee import").Cells(2, "F").Resize(UBound(outData, 1)).Value = outData
as for your first copy/paste values, it actually doesn't need any check, since blank values would be pasted as blank ones...
so you could go:
With Worksheets("Demographic")
With .Range("AU2", .Cells(.Rows.count, "AU").End(xlUp))
Worksheets("Employee import").Range("F2").Resize(.Rows.count).Value = .Value
End With
End With
as for your 2nd copy/paste values, you could paste all values and then filter not wanted ones and clear them in target sheet
like follows:
With Worksheets("Demographic")
With .Range("AM2", .Cells(.Rows.count, "AM").End(xlUp))
Worksheets("Employee import").Range("G2").Resize(.Rows.count).Value = .Offset(, 2).Value
End With
End With
With Worksheets("Employee import")
With .Range("G1", .Cells(.Rows.count, "G").End(xlUp))
.AutoFilter field:=1, Criteria1:="<>T"
.Resize(.Rows.count).Offset(1).SpecialCells(xlCellTypeVisible).ClearContents
End With
.AutoFilterMode = False
End With
that said, if your workbook has many formulas and/or event handlers then you would also greatly benefit from disabling them (Application.EnableEvents = False, Application.Calculation = xlCalculationManual) before running your code and enabling them back (Application.EnableEvents = True, Application.Calculation = xlCalculationAutomatic) after you code completes

How to pause macro, then do my stuff and continue/resume from where I left?

I got data in one sheet form B2:ZY191, and I want to copy each row (B2:ZY2,B3:ZY3, and so on till B191:ZY191) to another workbook worksheet for analysis. Now while doing so I sometimes need to stop and mark my results in between and then continue from where I left. For example, I started the macro and it copied from B2:ZY2 to B52:ZY52 then I pause the macro & mark my results. Now I want to continue from B52:ZY52 onwards then again if I want to stop after copying data till B95:ZY95 I should be able to pause the macro, mark my result and continue from B95:ZY95 thereon. I should be able to do this as many times as I want.
If provided with buttons like start, pause and resume would be very helpful.
you could adopt the following workaround:
choose the "sets" you want to virtually divide your data range into
let's say:
set#1 = rows 1 to 20
set#2 = rows 21 to 30
... and so on
mark with any character in column "A" the final rows of all chosen sets
so you'd put a "1" (or any other character other than "|I|" or "|E|" - see below) in the following cells of column "A" (i.e. the one preceding your data range):
A21
A31
..., and so on
(since your data starts at row 2 then its ith row is in worksheet row I+1)
then you put the following code in any module of your data range workbook:
Option Explicit
Sub DoThings()
Dim dataRng As Range, rngToCopy As Range
'assuming Analysis.xlsx is already open
Set dataRng = Worksheets("BZ").Range("B2:ZY191") '<--| this is the whole data range. you can change it (both worksheet name and range address) but be sure to have a free column preceeding it
Set rngToCopy = GetCurrentRange(dataRng) '<--| try and set the next "set" range to copy
If rngToCopy Is Nothing Then '<--| if no "set" range has been found...inform the user and exit sub!
MsgBox "There's an '|E|' at cell " _
& vbCrLf & vbCrLf & vbTab & dataRng(dataRng.Rows.Count, 1).Offset(, -1).Address _
& vbCrLf & vbCrLf & " marking data has already been entirely copied" _
& vbCrLf & vbCrLf & vbCrLf & "Remove it if you want to start anew", vbInformation
Exit Sub
End If
With rngToCopy
Workbooks("Analysis").Worksheets("Sheet1").Range(.Address).value = .value
End With
End Sub
Function GetCurrentRange(dataRng As Range) As Range
Dim f As Range
Dim iniRow As Long, endRow As Long
With dataRng
With .Offset(, -1)
Set f = .Resize(, 1).Find(what:="|E|", lookat:=xlWhole, LookIn:=xlValues) '<--| look for the "all copied" mark ("|E|")
If Not f Is Nothing Then Exit Function '<--| if "all copied" mark was there then exit function
Set f = .Resize(, 1).Find(what:="|I|", lookat:=xlWhole, LookIn:=xlValues) '<--| look for any "initial" mark put by a preceeding sub run
If f Is Nothing Then '<--|if there was no "initial" mark ...
iniRow = 1 '<--| ...then assume first row as initial one
Else
iniRow = f.row - .Cells(1).row + 1 '<--| ... otherwise assume "marked" row as initial one
f.ClearContents '<--| and clear it not to found it the next time
End If
endRow = .Cells(iniRow, 1).End(xlDown).row - .Cells(1).row + 1 '<--| set the last row as the next one with any making in column "A"
If endRow >= .Rows.Count Then '<--| if no mark has been found...
endRow = .Rows.Count '<--| ...set the last row as data last row...
.Cells(endRow, 1).value = "|E|" '<--|... and put the "all copied" mark in it
Else
.Cells(endRow, 1).ClearContents '<--| ...otherwise clear it...
.Cells(endRow + 1, 1).value = "|I|" '<--| ... and mark the next one as initial for a subsequent run
End If
End With
Set GetCurrentRange = .Rows(iniRow).Resize(endRow - iniRow + 1) '<--| finally, set the range to be copied
End With
End Function
and make it run as many times as you need: after each time it ends and you can mark your result and then make it run again and it'll restart form where it left
you can use Stop and Debug.Print to achieve the desired results when placed within your code. For example if you're looping through a range, add the statement of choice with an if statement:
for a = 1 to 150
if a = 20 or a = 40 then
debug.Print "The value of a is: " & a.value 'or whatever you want to see
end if
next
This will print to the immediates window, or use stop to pause your code in a strategic place in the same manner.
I dont understand what you mean by buttons? They surely aren't a good idea as the code will run too fast?

VBA Whitelist - match against whitelist then delete specific range

I want to creat a macro that matches against a whitelist, then delete everything that's not on the whitelist. I have the following code:
Sub WHITELIST()
Dim LR As Long, i As Long
With Sheets("Sheet1")
LR = .Range("A" & Rows.Count).End(xlUp).Row
For i = LR To 1 Step -1
If IsError(Application.Match(.Range("A" & i).Value, Sheets("Whitelist").Columns("A"), 0)) Then .Rows(i).Delete
Next i
End With
End Sub
But my code deletes the entire row. I only want to delete the range A:B and shift the cells up (the row size is variable so i always want to check till the last row - up to 40.000 rows). Its important that "important Data" doesn't gets deleted too. Here is an example how the macro SHOULD work:
Hope someone can help me
Greetings
Try changing this line
If IsError(Application.Match(.Range("A" & i).Value, Sheets("Whitelist").Columns("A"), 0)) _
Then .Rows(i).Delete
to this
If IsError(Application.Match(.Range("A" & i).Value, Sheets("Whitelist").Columns("A"), 0)) _
Then .Range("A" & i & ":B" & i).Delete Shift:=xlUp
This will only delete the cells in columns A and B.
Using the Shift:=xlUp will allow you delete those specific cells in the range you define and move the entire block of cells below that range up.

Automatic spreadsheet generation in Excel VBA

My friend and I currently have a master spreadsheet that I need to be broken out into smaller spreadsheets regularly. This used to be a manual process, but I'd like to automate it. I created a three step solution in VBA which would help me accomplish this that did the following:
Apply relevant filters to spreadsheet
Export data currently visible after filter into new spreadsheet
Save spreadsheet and go back to 1 (different criteria)
Unfortunately I am having a hard time implementing it. Whenever I try to generate the spreadsheet, my document hangs, starts performs several calculations and then gives this me this error message:
Upon debugging the code, I get an error message at this line:
One Excel workbook is left open and only one row is visible (the second row pulled from the Master which contains header information) and nothing else.
What exactly is going on here?
This is my code so far:
The heart of it all
' This bit of code get's all the primary contacts in column F, it does
' this by identifying all the unique values in column F (from F3 onwards)
Sub GetPrimaryContacts()
Dim Col As New Collection
Dim itm
Dim i As Long
Dim CellVell As Variant
'Get last row value
LastRow = Cells.SpecialCells(xlCellTypeLastCell).Row
'Loop between all column F to get unique values
For i = 3 To LastRow
CellVal = Sheets("Master").Range("F" & i).Value
On Error Resume Next
Col.Add CellVal, Chr(34) & CellVal & Chr(34)
On Error GoTo 0
Next i
' Once we have the unique values, apply the TOKEN NOT ACTIVATED FILTER
Call TokenNotActivated
For Each itm In Col
ActiveSheet.Range("A2:Z2").Select
Selection.AutoFilter Field:=6, Criteria1:=itm
' This is where the magic happens... creating the individual workbooks
Call TokenNotActivatedProcess
Next
ActiveSheet.AutoFilter.ShowAllData
End Sub
The "token not activated" filter
Sub TokenNotActivated()
'Col M = Yes
'Col U = provisioned
ThisWorkbook.Sheets(2).Activate
ActiveSheet.Range("A2:Z2").Select
Selection.AutoFilter Field:=13, Criteria1:="Yes"
Selection.AutoFilter Field:=21, Criteria1:="provisioned", Operator:=xlFilterValues
End Sub
Running the process to get the workbooks saved
Function TokenNotActivatedProcess()
Dim r As Range, n As Long, itm, FirstRow As Long
n = Cells(Rows.Count, 1).End(xlUp).Row
Set r = Range("A1:A" & n).Cells.SpecialCells(xlCellTypeVisible)
FirstRow = ActiveSheet.Range("F2").End(xlDown).Row
itm = ActiveSheet.Range("F" & FirstRow).Value
If r.Count - 2 > 0 Then Debug.Print itm & " - " & r.Count - 2
Selection.SpecialCells(xlCellTypeVisible).Select
Selection.Copy
Workbooks.Add
ActiveSheet.Paste
Application.CutCopyMode = False
ActiveWorkbook.SaveAs Filename:="C:\Working\Testing\TokenNotActivated - " & itm + ".xls", FileFormat:=52, CreateBackup:=False
End Function
This error is caused by trying to filter an empty range. After analysing your code, my guess is that you are missing a worksheet activation here, since repeating the line ActiveSheet.Range("A2:Z2").Select after calling the function TokenNotActivated does not make sense and maybe your code is trying to filter some empty range/worksheet.