Copy Pasting Range to new page if it can't fit in current - excel

For i = 1 + num_ranges To n
ws.Range("newRange").Copy
Set nextCell = ws.Cells(ws.Rows.Count, "A").End(xlUp).Offset(2, 0)
nextCell.PasteSpecial xlPasteAll
Next i
Currently copy and pasting a range up to N times and it works just fine. What I would like is to only paste it if the entire range can fit in the page. Otherwise, go to the next page and continue pasting.
The way I'm thinking is to only paste if (Last row in page - nextCell) > the amount of rows the range has (6) else set nextCell to first row in next page but can't figure out how to get the last (or first row) of current/next page.

Here's some code I used for testing. You should be able to adjust for your needs.
Sub CopyPasteWithPageBreaks()
Dim f As Worksheet, i As Long, rngCopy As Range, rngPaste As Range
Dim pb As Long, newPaste As Range, pgBrk As HPageBreak, nC, nR
Set rngCopy = Sheets("Sheet2").Range("A1:I20") 'some data to copy
nC = rngCopy.Columns.Count
nR = rngCopy.Rows.Count
Set f = Sheets("Sheet1")
f.Cells.Clear
DoEvents
Set rngPaste = f.Range("A1")
For i = 1 To 10
pb = f.HPageBreaks.Count 'current # of page breaks
'set up the copy range so it's clear which iteration the pasted content was from
rngCopy.Value = i
rngCopy.Font.Color = IIf(i Mod 2 = 0, vbRed, vbBlue)
rngCopy.Copy rngPaste
DoEvents
If f.HPageBreaks.Count > pb Then 'added a page break by pasting?
If i = 1 Then
'If the first paste creates a new page break then
' the copied range will not fit on a page....
MsgBox "Range is too large for 1 page!"
Exit Sub
End If
Set pgBrk = f.HPageBreaks(f.HPageBreaks.Count)
Debug.Print "new pagebreak " & pgBrk.Location.Address
Set newPaste = f.Cells(pgBrk.Location.Row, "A")
rngPaste.Resize(nC, nR).Clear 'clear last paste
rngCopy.Copy newPaste 're-copy to top of next page
Set rngPaste = newPaste.Offset(nR)
Else
Set rngPaste = rngPaste.Offset(nR)
End If
Next i
End Sub

Related

How to copy specific rows to another sheet below black cell

I want to write a macro to copy rows from one worksheet to another below cell that is colored black (manually) - if it is detected, otherwise just copy rows from first sheet to Sheet1 at the top. After many trials and errors I came up with that code:
Sub copytherows(clf As Long, lastcell As Long) 'clf - cell that marks the start, lastcell - ending cell
Dim st As Long, cnext As Range
Dim wshet As Worksheet
Dim wshetend As Worksheet
'st - start of looking up, cnext - range of lines, wshet - worksheet
Dim coprange As String
Dim cnextcoprow, cnextrow As Long
'variables for copying macro part
Dim rangehelper As Range
Dim TargetColor As Long
Dim cell As Range
Dim sht As Worksheet
Dim x As Long
Dim Aend As Long
Set wshet = Worksheets(1)
Set wshetend = Sheets("Sheet1")
wshetend.Cells.Delete
For st = 1 To wshet.Cells(Rows.Count, "B").End(xlUp).Row
If wshet.Cells(st, "B").Interior.Color = clf Then 'has the color of interest
cnextcoprow = st
Set cnext = wshet.Cells(st, "B").Offset(1, 0) 'next cell down
Do While cnext.Interior.Color <> lastcell
Set cnext = cnext.Offset(1, 0) 'next row
Loop
st = st + 1
End If
Next st
cnextrow = cnext.Row - 1
coprange = cnextcoprow & ":" & cnextrow
Aend = Cells(Rows.Count, "A").End(xlUp).Row
'set color is black
TargetColor = RGB(255, 255, 255)
wshetend.Activate
For x = 1 To Rows.Count
If wshetend.Cells(x, "A").Interior.Color = TargetColor Then
x = x + 1
Set rangehelper = wshetend.Rows(x)
wshet.Range(coprange).Copy wshetend.Range(rangehelper).Offset(1)
Else
wshet.Range(coprange).Copy wshetend.Range("A" & Rows.Count).End(xlUp).Offset(1)
End If
Next x
End Sub
When Macro is ran it displays an error(Run-time error '1004' Method 'Range' of object '_Worksheet' failed on line :
wshet.Range(coprange).Copy wshetend.Range(rangehelper).Offset(1)
Sheet1 is for sure present in Workbook.
Edit as suggested by #FaneDuru:
1 - in this image is my curret state of worksheet that is wshet in my macro and for example if I select (by checkboxes) section1 and section3, section3 should be in the place of black cell in section1 (the order of sections doesn't really matter to me) inside destination sheet ( I know I'm not good in explaining things like that).
2 - this should be end result of this macro
It's quite confusing how you use the for loops.
In the first one you use it to check for the start -which is fine- but then you put a while loop in there which will end up in an endless loop once your st gets past your lastcell row, instead use
ElseIf wshet.Cells(st, "B").Interior.Color = lastcell Then
cnextrow = st
Exit For
End If
In the second for loop you copy the rows if you find the black cell but you don't exit the for loop, speaking of which, you delete all the cells in your wshetend so you'll always start at row 1. So either you don't want to delete all the cells in your wshetend or the for loop is unnecessary.
This is my testSub and it copies from the first sheet to Sheet2 after the cell with black background (black = 0) (commented out the delete cells)
Sub TestBlackCellCopy()
Dim st As Long, cnext As Range
Dim wshet As Worksheet
Dim wshetend As Worksheet
'st - start of looking up, cnext - range of lines, wshet - worksheet
Dim coprange As String
Dim cnextcoprow, cnextrow As Long
'variables for copying macro part
Dim rangehelper As Range
Dim TargetColor As Long
Dim cell As Range
Dim sht As Worksheet
Dim x As Long
Dim Aend As Long
Dim clf As Long, lastcell As Long
clf = 5296274
lastcell = 65535
cnextcoprow = 0
Set wshet = Worksheets(1)
Set wshetend = Sheets("Sheet1")
' wshetend.Cells.Delete
For st = 1 To wshet.Cells(Rows.Count, "B").End(xlUp).Row
Debug.Print (wshet.Cells(st, "B").Interior.Color)
If wshet.Cells(st, "B").Interior.Color = clf And cnextcoprow = 0 Then 'has the color of interest
cnextcoprow = st
ElseIf wshet.Cells(st, "B").Interior.Color = lastcell Then
cnextrow = st - 1
Exit For
End If
Next st
coprange = cnextcoprow & ":" & cnextrow
Aend = Cells(Rows.Count, "A").End(xlUp).Row 'unused variable?
'set color is black
TargetColor = 0
wshetend.Activate
For x = 1 To Rows.Count
Debug.Print (wshetend.Cells(x, "A").Interior.Color)
If wshetend.Cells(x, "A").Interior.Color = TargetColor Then
wshet.Rows(coprange).EntireRow.Copy wshetend.Range("A" & x).Offset(1)
Exit For
' Else
' wshet.Range(coprange).Copy wshetend.Range("A" & Rows.Count).End(xlUp).Offset(1)
End If
Next x
End Sub
So you'll have to figure out what exactly you want, to delete the cells? Then it starts at row 1, then put a skip after a copy you place after the second for loop.
Something like this:
wshetend.Activate
Aend = Cells(Rows.Count, "A").End(xlUp).Row
For x = 1 To Rows.Count
Debug.Print (wshetend.Cells(x, "A").Interior.Color)
If wshetend.Cells(x, "A").Interior.Color = TargetColor Then
wshet.Rows(coprange).EntireRow.Copy wshetend.Range("A" & x).Offset(1)
GoTo skipFor
End If
Next x
wshet.Rows(coprange).EntireRow.Copy wshetend.Range("A1")
skipFor:
Hope this helps.
Please, try the next way. It should work if you respected all what we set in the above discussion (check boxes in G:G, black cells in B:B for first sheet, and a black cell in any place of the second sheet:
Sub CopyRowsCheckBox_Black_limited()
Dim wshet As Worksheet, wshetend As Worksheet, blackCell As Range, redCell As Range, rngCopy As Range
Dim sh As Shape, chkB As MSForms.CheckBox, cellPaste As Range, pasteRow As Long
Set wshet = ActiveSheet 'use here the sheet where from you need copying
Set wshetend = wshet.Next 'use here the sheet where to copy
'settings to make Find function searching for Interior color:
With Application.FindFormat
.Clear: .Interior.Color = vbBlack
.Locked = True
End With
'find the black cell in the second sheet:
Set cellPaste = wshetend.cells.Find(What:=vbNullString, After:=wshetend.Range("A1"), SearchFormat:=True)
If Not cellPaste Is Nothing Then 'set the row where to copy first
pasteRow = cellPaste.Offset(1).row
Else
pasteRow = 1
End If
'iterate between all shapes, found the ones being checkBoxes and being on column G:G, set the rows range and copy it:
For Each sh In wshet.Shapes
If TypeName(sh.OLEFormat.Object.Object) = "CheckBox" And sh.TopLeftCell.Column = 7 Then
Set chkB = sh.OLEFormat.Object.Object 'set the checkBox ActiveX object
If chkB.Value = True Then 'if it is checked
Set blackCell = wshet.Range("B:B").Find(What:=vbNullString, After:=wshet.Range("B" & _
sh.TopLeftCell.row), SearchFormat:=True) 'find first black cell
Set rngCopy = wshet.Range(wshet.Range("B" & sh.TopLeftCell.row), blackCell).EntireRow 'set the rows to be copied
rngCopy.Copy wshetend.Range("A" & pasteRow): pasteRow = pasteRow + rngCopy.rows.count 'copy and update pasting row
End If
End If
Next sh
MsgBox "Ready..."
End Sub
The range to be copied is the one between the checked check box and the first black cell in B:B column.
Important Note: The top left corner of the check boxes must be inside of first series row!
Please, send some feedback after testing it.

How to Automate my Manual Selection Process in VBA

I have a manual selection process that I have tried but failed to automate, so I am reaching out for help. I have attached an image of my Excel sheet as a visual guide when reading my process. Excel Snapshot.
I select cell "L2" and run the code below. It finds the first instance of the value within "A2:J1501" and cuts the whole row. It pastes the row onto the sheet named Lineups. Then it highlights each of the values of the cut row in column "L:L" to let me know that value has been used. I then manually select the next non-highlighted value (in the image example it would be "L2") and run the code again, and again, and again, until every row of L:L is highlighted. This process can take some time depending on the number of rows in L:L so I was hoping I can get some help to automate.
Thank you very much.
Sub ManualSelect()
Dim rng As Range
Set rng = Range("A1:J1501")
Dim ac As Range
Set ac = Application.ActiveCell
rng.Find(what:=ac).Select
Range("A" & ActiveCell.Row).Resize(1, 10).Cut
ActiveWindow.ScrollRow = 1
Sheets("Lineups").Select
nextRow = Cells(Rows.Count, 1).End(xlUp).Row + 1
Cells(nextRow, 1).Select
ActiveSheet.Paste
Sheets("Data").Select
Dim wsData As Worksheet
Dim wsLineups As Worksheet
Dim rngToSearch As Range
Dim rngLineupSet As Range
Dim rngPlayerID As Range
Dim Column As Long
Dim Row As Long
Dim LastRow As Long
Set wsData = Sheets("Data")
Set wsLineups = Sheets("Lineups")
Set rngPlayerID = wsData.Range("L2:K200")
Set rngToSearch = rngPlayerID
LastRow = wsLineups.Cells(Rows.Count, 1).End(xlUp).Row
For Row = 2 To LastRow
For Column = 1 To 10
Set rngLineupSet = rngPlayerID.Find(what:=wsLineups.Cells(Row, Column), LookIn:=xlValues)
If Not rngLineupSet Is Nothing Then rngLineupSet.Interior.Color = 65535
Next Column
Next Row
End Sub
This should be pretty close:
Sub ManualSelect()
Dim wsData As Worksheet, c As Range, dict As Object, v, rw As Range
Dim wsLineups As Worksheet, c2 As Range, f As Range
Set dict = CreateObject("scripting.dictionary") 'for tracking already-seen values
Set wsLineups = ThisWorkbook.Worksheets("Lineups")
Set wsData = ThisWorkbook.Worksheets("Data")
For Each c In wsData.Range("L2", wsData.Cells(Rows.Count, "L").End(xlUp))
v = c.Value
If dict.exists(CStr(v)) Then
c.Interior.Color = vbYellow 'already seen this value in L or a data row
Else
'search for the value in
Set f = wsData.Range("A2:J1501").Find(v, lookat:=xlWhole, LookIn:=xlValues, searchorder:=xlByRows)
If Not f Is Nothing Then
Set rw = f.EntireRow.Columns("A").Resize(1, 10) 'A to J
For Each c2 In rw.Cells 'add all values from this row to the dictionary
dict(CStr(c2)) = True
Next c2
rw.Cut Destination:=wsLineups.Cells(Rows.Count, "A").End(xlUp).Offset(1)
c.Interior.Color = vbYellow
Else
'will there always be a match?
c.Interior.Color = vbRed 'flag no matching row
End If
End If 'haven't already seen this col L value
Next c 'next Col L value
End Sub
I believe this should do it (updated):
Sub AutoSelect()
Dim wsData As Worksheet, wsLineups As Worksheet
Dim rng As Range, listIDs As Range
Set wsData = ActiveWorkbook.Sheets("Data")
Set wsLineups = ActiveWorkbook.Sheets("Lineups")
Set rng = wsData.Range("A2:J1501")
'get last row col L to define list
LastRowL = wsData.Range("L" & Rows.Count).End(xlUp).Row
Set listIDs = wsData.Range("L2:L" & LastRowL)
'loop through all cells in list
For i = 1 To listIDs.Rows.Count
myCell = listIDs.Cells(i)
'retrieve first mach in listID
checkFirst = Application.Match(myCell, listIDs, 0)
'only check first duplicate in list
If checkFirst = i Then
'get new row for target sheet as well (if sheet empty, starting at two)
newrow = wsLineups.Range("A" & Rows.Count).End(xlUp).Row + 1
'check if it is already processed
Set processedAlready = wsLineups.Cells(2, 1).Resize(newrow - 1, rng.Columns.Count).Find(What:=myCell, lookat:=xlWhole, LookIn:=xlValues)
'if so, color yellow, and skip
If Not processedAlready Is Nothing Then
listIDs.Cells(i).Interior.Color = vbYellow
Else
'get fist match for value, if any (n.b. "xlWhole" ensures whole match)
Set foundMatch = rng.Find(What:=myCell, lookat:=xlWhole, LookIn:=xlValues)
'checking for a match
If Not foundMatch Is Nothing Then
'get the row
foundRow = foundMatch.Row - rng.Cells(1).Row + 1
'specify target range and set it equal to vals from correct row in rng
wsLineups.Cells(newrow, 1).Resize(1, rng.Columns.Count).Value2 = rng.Rows(foundRow).Value
'clear contents rng row
rng.Rows(foundRow).ClearContents
'give a color to cells that actually got a match
listIDs.Cells(i).Interior.Color = vbYellow
Else
'no match
listIDs.Cells(i).Interior.Color = vbRed
End If
End If
Else
'duplicate already handled, give same color as first
listIDs.Cells(i).Interior.Color = listIDs.Cells(checkFirst).Interior.Color
End If
Next i
End Sub
Also, I think, slightly faster than the other solution offered (because of the nested loop there?). Update: I got a bit confused about the nested loop in the answer by Tim Williams, but I missed that you also want to "accept" the values in the list that matched on a row that is already gone. I fixed this in the updated version by checking if a value that fails to match on the data range has already been transferred to Lineups. Provided that doing so is permissible, this method avoids the nested loop.
I checked both methods for speed (n = 50) on a list (n = 200) for the full data range, ended up with average of 1.70x faster... But maybe speed is not such a big deal, if you're coming from manual labor :)

Cut and delete an entire row but paste only the value of a row falling under specific column

I'm trying to cut and delete some entire rows having move it in ID column and paste only the value of that ID to another sheet using a macro. What I've written so far can cut and delete the rows but paste the whole row in another sheet instead of only the IDs.
Sheet1 before running the macro:
Sheet1 after running the macro:
Sheet2 where the value should be placed like:
My following attempt can do the second step flawlessly but when it comes to paste only the value of kicked out IDs in another sheet, it pastes the whole row.
Sub CutAndPaste()
Dim cel As Range
Dim ws As Worksheet: Set ws = ThisWorkbook.Sheets("Sheet1")
Dim wsAno As Worksheet: Set wsAno = ThisWorkbook.Sheets("Sheet2")
For Each cel In Range("C2:C10")
If cel(1, 1) = "move it" Then
ws.Range(cel(1, 1).Address).EntireRow.Cut wsAno.Range("A" & wsAno.Rows.count).End(xlUp).Offset(1, 0)
ws.Range(cel(1, 1).Address).EntireRow.Delete
wsAno.Range("B" & wsAno.Rows.count).End(xlUp).Offset(1, 0).Value = "done"
End If
Next cel
End Sub
How can I cut and delete an entire row but paste only the value of a row falling under ID column in another sheet?
Please, try the next code. It should be very fast, using an array for the ranges to be copied (cut if you like it better...) and deletion is done at once:
Sub CutAndPaste_()
Dim cel As range, rngDel As range, arrCut, lastRAn As Long, k As Long
Dim ws As Worksheet: Set ws = ThisWorkbook.Sheets("Sheet1")
Dim wsAno As Worksheet: Set wsAno = ThisWorkbook.Sheets("Sheet2")
ReDim arrCut(1 To 2, 1 To 9) 'redim the array at a maximum able to keep all the necessary elements (10 -1)
For Each cel In ws.range("C2:C10")
If cel(1, 1).Value = "move it" Then
If rngDel Is Nothing Then 'if range to be deleted does not exist, yet:
k = k + 1
arrCut(1, k) = cel: arrCut(2, k) = "done" 'or cel.Offset(0, 1)
Set rngDel = cel
Else
k = k + 1
arrCut(1, k) = cel: arrCut(2, k) = "done" 'or cel.Offset(0, 1)
Set rngDel = Union(rngDel, cel)
End If
End If
Next cel
lastRAn = wsAno.range("A" & rows.count).End(xlUp).row + 1 'last empty row
ReDim Preserve arrCut(1 To 2, 1 To k) 'keep only the existing filled array elements
wsAno.range("A" & lastRAn).Resize(k, 2).Value = WorksheetFunction.Transpose(arrCut) 'drop the array value at once
rngDel.EntireRow.Delete xlUp 'delete the necessary rows at once
End Sub
I would suggest you to also use a variable to determine the last row in C:C and use it to create the range and also preliminary ReDim the array. It is filled with columns instead of rows, because only the last dimension can be ReDim Preserved..

How to select and cut an entire row from sheet1 and paste it in sheet2

Simple table in sheet1 with data in cells(A2:C4), column D is empty. I want to select the entire row, cut and paste it in sheet2 when a time is added in colum D.
When I clicked on the logout button, It will add a time punch in column D.
I want that entire row to be selected and then cut and paste in sheet2.
I want also to arrange the remaining entry to move up so that there's no spaces between.
Screenshot
Dim CM As Boolean
Private Sub cmdMove_Click()
Dim myLog As Worksheet
Dim myLogSheet As Range
Dim i As Long: i = 1
Set myLog = Sheets("Sheet1")
Set myLogSheet = myLog.Range("B:B").Find(txtID.Value, , , xlWhole)
'Dim LastRow As Long
'LastRow = Sheets("Sheet2").Range("A65536").End(xlUp).Row + 1
If Not myLogSheet Is Nothing Then
myLogSheet.Offset(0, 2) = Format(Now, "hh:mm:ss")
With ActiveSheet
For n = nLastRow To nFirstRow Step -1
If .Cells(n, "D") = "" Then
.Cells(n, "D").EntireRow.Cut Sheet2.Cells(i, "A")
.Cells(n, "D").EntireRow.Delete '~~> if you want to delete
i = i + 1
End If
Next
End With
Else
txtName.Value = "NO RECORD"
End If
End Sub
You need to remove your loop, and just use the row you found using the Find:
Dim CM As Boolean
Private Sub cmdMove_Click()
Dim myLog As Worksheet
Dim myLogSheet As Range
Dim myLogSheetRow As Long
Dim i As Long
i = 1
'Probably you want:
i = Sheet2.Cells(Sheet2.Rows.Count, "A").End(xlUp).Row + 1
Set myLog = Sheets("Sheet1")
Set myLogSheet = myLog.Range("B:B").Find(txtID.Value, , , xlWhole)
If Not myLogSheet Is Nothing Then
myLogSheetRow = myLogSheet.Row ' So we can delete the row later
myLogSheet.Offset(0, 2) = Format(Now, "hh:mm:ss")
myLogSheet.EntireRow.Cut Sheet2.Cells(i, "A")
myLog.Rows(myLogSheetRow).Delete
Else
txtName.Value = "NO RECORD"
End If
End Sub
Note that Excel exhibits very odd behaviour when deleting the row after the Cut. Using a statement of myLogSheet.EntireRow.Delete after the Cut causes Excel to delete the row in Sheet1 based on the new location of the cell in Sheet2. This is why a variable needs to be created to refer to the row prior to the Cut, so that it can be used in the Delete after the Cut.

Copy Paste VBA Code Has Blank Rows

The below code searches, copies & pastes the found data into another worksheet. However, there are blanks when this is done in the pasted worksheet. Eg: Found "To Be Copied" in Cell A1 and copied the entire row to the specified worksheet. Found "To Be Copied" in A4 and copied the entire row to the specified worksheet. However, there are two blank rows in the pasted sheet between A1 and A4. Thanks for your help.
Sub Deleting()
Application.ScreenUpdating = False
Dim wsh As Worksheet, i As Long, Endr As Long, x1 As Worksheet, p As Long
Set wsh = ActiveSheet
Worksheets.Add(Before:=Worksheets("Original Sheet")).Name = "Skipped"
Set x1 = Worksheets("Skipped")
Worksheets("ABC").Activate
i = 2
Endr = wsh.Range("A" & wsh.Rows.Count).End(xlUp).Row
While i <= Endr
If Cells(i, "A") = "To Be Copied" Then
wsh.Rows(i).Copy
x1.Rows(i).PasteSpecial
p = p + 1
Endr = Endr + 1
End If
i = i + 1
Wend
End Sub
You need two counters: i for the source rows, j for the destination rows. You only increment j when a row is copied.
Your existing code needs either
A separate counter for the written row position (Cutter's point), or
Pasting to the last used row of "Skipped" using xlUp to find the last used cell
But better still would be copying the rows in a single shot using AutoFilter. Something like below
Sub Quicker()
Dim ws1 As Worksheet
Dim ws2 As Worksheet
Dim rng1 As Range
Application.ScreenUpdating = False
Set ws1 = Sheets("ABC")
Set ws2 = Worksheets.Add(Before:=Worksheets("Original Sheet"))
'in case Skipped exists
On Error Resume Next
ws2.Name = "Skipped"
On Error GoTo 0
ws1.AutoFilterMode = False
Set rng1 = ws1.Range(ws1.[a1], ws1.Cells(Rows.Count, "A").End(xlUp))
rng1.AutoFilter 1, "To Be Copied"
If rng1.SpecialCells(xlCellTypeVisible).Count > 1 Then
Set rng1 = rng1.Offset(1, 0).Resize(rng1.Rows.Count - 1)
rng1.EntireRow.Copy ws2.[a1]
End If
ws1.AutoFilterMode = False
MsgBox "Sheet " & ws2.Name & " updated"
End Sub

Resources