I stink at Loops - vba

I have 3 Sheets: Work, Bill, and Cust. Cust column A contains my unique customers, which I then paste onto cell A3 on the Work sheet where it runs its calculations and then paste it on to the Bill sheet. I then take the next value on the Cust sheet and i paste it back to Work, run the calculation and paste it below the previous set on the Bill sheet. I have 2 questions.
Why isn't my loop working? I'm trying to keep going until I run out of customers on the cust sheet?
Why is it that I can use the custom range BillPlace in the first part of my code, yet I actually have to refer to the cells in the later parts?
Thanks in advance
Sub test1()
Dim WorkPlace As Range, BillPlace As Range, WorkProd As Range
Set WorkPlace = Sheets("Work").Cells(3, 1)
Set BillPlace = Sheets("Bill").Cells(3, 1)
Set WorkProd = WorkPlace.CurrentRegion
WorkPlace.CurrentRegion.Copy
BillPlace.PasteSpecial xlPasteAll
Sheets("Cust").Select
Cells(1, 1).Copy
WorkPlace.PasteSpecial xlPasteAll
WorkProd.Copy
Sheets("Bill").Select
Range("A3").Select
Selection.End(xlDown).Select
Selection.Offset(1, 0).PasteSpecial xlPasteAll
Sheets("Cust").Select
Cells(2, 1).Select
Selection.Offset(1, 0).Select
Do
ActiveCell.Offset(1, 0).Copy
WorkPlace.PasteSpecial xlPasteAll
WorkProd.Copy
Sheets("Bill").Select
Range("A3").Select
Selection.End(xlDown).Select
Selection.Offset(1, 0).PasteSpecial xlPasteAll
Loop Until IsEmpty(ActiveCell.Offset(1, 0))
End Sub

#Portland Runner has a point about using a For Each / Next loop. By doing that you can probably eliminate the counters and a bunch of selecting from your working code above, removing a bunch of complexity from your process.
The principle of a For/Next loop is easy enough: define TheLargerRange containing the cells you will loop through. Define a SingleCell range to contain the current cell you are working with. Then you can start the loop saying something like:
For Each SingleCell in TheLargerRange
'~~> your loop actions go here
Next SingleCell
Also, you can do a lot without selecting specific locations in your workbook. Instead copy, paste, or assign values by just referencing the location. If you want, you can set variables to make this easier in longer code.
The following example just moves a column of customer data from one sheet to another, as an example of how to use the For Each / Next loop structure and how to avoid selecting everything you work with. There is only one selection in this code, and that is only because the compiler chokes if you use End(xldown) to attempt setting a range on an unselected tab. Otherwise there could be no selections.
Sub UsingForNextAndAvoidingSelections()
'~~> Set variables for referencing the "Cust" tab
Dim CustomerList As Range
Dim Customer As Range
Dim CustomerTab As Worksheet
Set CustomerTab = Sheets("Cust")
CustomerTab.Select
Set CustomerList = CustomerTab.Range("A1", Range("A1").End(xlDown))
'~~> Set variables for referencing the "Bill" tab
Dim BillTab As Worksheet
Dim BillRow As Range
Set BillTab = Sheets("Bill")
Set BillRow = BillTab.Range("A1")
'~~> Loop through the customer list, copying each value to the new BillRow location
For Each Customer In CustomerList
Customer.Copy
BillRow.PasteSpecial xlPasteAll
Set BillRow = BillRow.Offset(1, 0)
Next Customer
End Sub
12/27/2013: I just realized why the code Set CustomerList = CustomerTab.Range("A1", Range("A1").End(xlDown)) was throwing an error when CustomerTab was not selected: I forgot to fully qualify the second range statement in that line: Range("A1").End(xlDown).
I believe that if you qualify that line of code like this Set CustomerList = CustomerTab.Range("A1", CustomerTab.Range("A1").End(xlDown)) you can eliminate the CustomerTab.Select that precedes it and conduct the entire process without a single Select.

WorkProd.Copy
Sheets("Bill").Select
Range("A3").Select
Selection.End(xlDown).Select
Selection.Offset(1, 0).PasteSpecial xlPasteAll
Loop Until IsEmpty(ActiveCell.Offset(1, 0))
You are going to the end of a column and pasting one row further down. You then check if the cell one row further down is empty, but it won't be because you've just pasted into it. This is why it repeats endlessly.
I assume you should be looking for an empty cell somewhere other than one row below the current cursor position.

HA! i fixed it. This isn't the most orthodox approach but it worked. Oh pardon me but i did it in production so the name of the sheets and cell positions changed slightly. CountC is a helper cell that counts the number of customers. Thanks everyone for your help.
Sub Pull_Billing()
Dim WorkPlace As Range, BillPlace As Range, WorkProd As Range, PlaceHolder As Range, CountC As Integer, n As Integer
Set WorkPlace = Sheets("Work").Cells(3, 1)
Set BillPlace = Sheets("ABS_Billing_Sheet").Cells(3, 1)
Set WorkProd = WorkPlace.CurrentRegion
CountC = Sheets("CTA_Info").Cells(1, 5).Value
Sheets("CTA_info").Cells(2, 2).Copy
WorkPlace.PasteSpecial xlPasteAll
WorkPlace.CurrentRegion.Copy
BillPlace.PasteSpecial xlPasteAll
Sheets("CTA_Info").Select
Cells(3, 2).Copy
WorkPlace.PasteSpecial xlPasteAll
WorkProd.Copy
Sheets("ABS_Billing_Sheet").Select
Range("A3").Select
Selection.End(xlDown).Select
Selection.Offset(1, 0).PasteSpecial xlPasteAll
Sheets("CTA_Info").Select
Cells(4, 2).Select
n = ActiveCell.Row
Do
Cells(n, 2).Select
Selection.Copy
WorkPlace.PasteSpecial xlPasteAll
WorkProd.Copy
Sheets("ABS_Billing_Sheet").Select
Range("A3").Select
Selection.End(xlDown).Select
Selection.Offset(1, 0).PasteSpecial xlPasteAll
Sheets("CTA_Info").Select
Cells(n + 1, 2).Select
n = ActiveCell.Row
Loop Until n > CountC + 2
Sheets("CTA_info").Cells(2, 2).Copy
WorkPlace.PasteSpecial xlPasteAll
Sheets("ABS_Billing_Sheet").Select
End Sub

Related

Dynamic copy and pasting using ctrl+shift+down function aka Selection.End(x1down)

I am attempting to move ranges (of varying lengths) between two workbooks.
Windows("Comp1.xlsx").Activate 'Open sheet to pull data from
Range("E2").Select 'Starting point is the same every time
Range(Selection, Selection.End(xlDown)).Select 'Select all data below
Application.CutCopyMode = False
Selection.Copy 'Copy range
Windows("Comps Proto.xlsm").Activate 'Sheet to be pasted into
Range("K12").Select
Selection.PasteSpecial Paste:=xlPasteValuesAndNumberFormats, Operation:= _
xlNone, SkipBlanks:=False, Transpose:=False 'Paste data into new sheet
In order to repeat this function for the next workbook, I need to move off the pasted selection. I have tried everything, including offset, and Application.CutCopyMode = False. Doesn't work.
See below picture: The first paste cycle ends with that range selected. I want to move to the cell labeled and circled Start. This is where the next range will be pasted in the same fashion as above, rinse and repeat
The main issue I was trying to resolve was getting the cursor to click off a pasted range, so that I could continue. I found the code below to be helpful. This is the original post on another forum: https://superuser.com/questions/342772/how-do-i-move-the-selection-down-one-row-in-excel-2007/342835
Dim ColNumber As Integer
ColNumber = Selection.Column
Range("K" & CStr(ColNumber)).Select 'Click off the pasted values
ActiveCell.End(xlDown).Select 'Equivalent of ctrl+down
ActiveCell.End(xlDown).Select 'Moves down through the range in picture
ActiveCell.Offset(2, 0).Select 'Moves selection to the "Start" point in picture above
Judging by your range location, it has free cells across all borders. This allows to use handy CurrentRegion property. In my code I assume you have 5 such blocks to copy. Feel free to change the place to copy to.
Sub MoveCells()
Dim x%, rng As Range
Set rng = [E5].CurrentRegion
'// Copy 5 blocks of cells
Do
'// Change target cell to the one you need
rng.Copy Sheets(2).Cells(1, x + 1)
'// Here we locate last cell in block of cells, offset two cells down
'// and select CurrentRegion again
Set rng = rng(rng.Cells.Count).Offset(2).CurrentRegion
x = x + 1
Loop While x <= 5
End Sub

Looping & copying cells

Generally my macro goes through every "O" cell, checks if the row meets given requirements (not mentioned in this part of code) and copies surrounding cells on the side. I have two columns used in this part: "contract no"(M), "date"(O). The problem is that I try to use below method to go up to last the contract number and copy it as well.
I do not get any error but the contract cell value does not paste. Could you tell me what I've done wrong?
If ActiveCell.Offset(0, -2) = "" Then
'Go up find contract number copy
ActiveCell.Offset(0, -2).Select
Do Until ActiveCell.Value <> ""
ActiveCell.Offset(-1, 0).Select
Loop
ActiveSheet.Range("M" & ActiveCell.Row).Copy _
Destination:=ActiveSheet.Range("V" & ActiveCell.Row)
'Go down and return to the last active cell
Do Until ActiveCell.Value <> ""
ActiveCell.Offset(1, 0).Select
Loop
ActiveCell.Offset(0, 2).Select
End If
You didn't select the desired cell
Problem lies in this loop:
'Selecting cell from a column to the left
ActiveCell.Offset(0, -2).Select
'Condition: cell value is not empty string
Do Until ActiveCell.Value <> ""
'Selecting cell from previous row in the same column
ActiveCell.Offset(-1, 0).Select
Loop
You're leaving the loop before you can .Select a cell.
Use this loop instead:
'Selecting cell from a column to the left
ActiveCell.Offset(0, -2).Select
'Condition: cell value is not empty string
Do
'Selecting cell from previous row in the same column
ActiveCell.Offset(-1, 0).Select
Loop Until ActiveCell.Value <> ""
the issue lays in your keeping relying on ActiveCell after
ActiveCell.Offset(-1, 0).Select
statement, that changes it ...
you're actually playing with fire when using ActiveCell together with Select/Selection coding pattern!
since I cannot see what's behind the code you showed, I must keep using ActiveCell reference and amend your code as per comments:
Dim cellToCopy As Range
With ActiveCell 'reference currently "active" cell
If .Offset(0, -2) = "" Then 'if the cell two columns left of referenced (i.e. "active") cell is empty...
Set cellToCopy = .Offset(0, -2).End(xlUp) '... set cell to copy as the first not empty one above the cell two columns left of referenced (i.e. "active") cell
Else '... otherwise
Set cellToCopy = .Offset(0, -2) 'set cell to copy as the one two columns left of referenced (i.e. "active") cell
End If
cellToCopy.Copy Destination:=Range("V" & .Row) 'copy the cell set as the one to be copied and paste it column V cell same row as reference (i.e. "active") cell
End With
Try not to use ActiveCell Your code can do quite unpredictable things to your worksheet if the wrong cell was selected, and so can my "improvement" thereof below.
Sub FindAndCopy()
Dim Ws As Worksheet
Dim R As Long, C As Long
With ActiveCell
Set Ws = .Worksheet
R = .Row
C = .Column
End With
With Ws
If Len(Trim(.Cells(R, C - 2).Value)) = 0 Then
'Go up find contract number copy
Do Until Len(.Cells(R, C - 2).Value)
R = R - 1
Loop
.Cells(R, "M").Copy Destination:=.Cells(ActiveCell.Row, "V")
End If
End With
End Sub
I think the ActiveCell component in this code is still a source of great danger. However, as you see, at least the code doesn't change it which dispenses with the necessity of going back to it in the end.

Paste the same blck of data below its self twice (or more)

I'm working on a formatting project for a monthly template. The data in column "E" will be static in each work book but different in other workbooks I'll run the macro on. IE one workbook may have 10K rows in column "E" and another workbook could have 20K in column "E". I need to copy that block of text below its self 2x. So I have a triplication of all that data from "E2:E".
I'm not looking for HUGE solutions with a million unnecessary Dimed variables. I'm close. What am I missing?
Range(Range("E2"), Range("E2").End(xlDown)).Select
Selection.Copy
Cells(Cells(Rows.Count, 1).End(xlUp).Row + 1, 1).Paste<------ ERROR!
Becoming frustrated.
Using < 1million variables...
With ActiveSheet
With .Range(.Range("E2"), .Cells(.Rows.Count, "E").End(xlUp))
.Copy .Offset(.Rows.Count, 0)
.Copy .Offset(.Rows.Count * 2, 0)
End With
End With
Compared to #Tim-Williams answer, this feels positively hideous. However, I present to you:
Range(Range("E2"), Range("E2").End(xlDown)).Copy
For idx = 1 To 2
Cells(Range("E2").End(xlDown).Row + 1, "E").Select
ActiveSheet.Paste
Next
the following is almost there:
Application.CutCopyMode = False
Range(Range("E2"), Range("E2").End(xlDown)).Select
Selection.Copy
I just need it to drop down 1 and paste that, THEN drop to the NEXT blank row and paste it again.
my 0.02 cents
Range.Value approach (pasting values only)
With Range("E2", Cells(Rows.Count, "E").End(xlUp))
.Offset(.Rows.Count).Value = .Value
.Offset(.Rows.Count * 2).Value = .Value
End With
formula approach (and pasting values only)
With Range("E2", Cells(Rows.Count, "E").End(xlUp))
.Offset(.Rows.Count).Resize(.Rows.count * 2).FormulaR1C1 = "=R[" & -.Rows.Count & "]C" '<--| have 2x referenced range cells below it with its values
.Resize(.Rows.Count * 2).Value = .Resize(.Rows.Count * 2).Value '<--| get rid of formulas
End With
Copy/PasteSpecial approach (you can choose what to paste - see PasteSpecial() method)
With Range("E2", Cells(Rows.Count, "E").End(xlUp))
.Copy
.Offset(.Rows.Count).Resize(.Rows.Count * 2).PasteSpecial ' you have some options like xlPasteAll (pastes all) or xlPasteValues (pastes values only)and others
Application.CutCopyMode = False '<--| this to free clipboard and clear highlighted cells
End With

Copy and Paste in VBA using relative references? (Error Code 1004)

New to this forum so sorry if this is off. I'm trying to do a simple copying of cell values from one worksheet in a book to another worksheet, but need to use relative cell references as the number of rows that will be copy/pasted changes depending on the data inputted.
The (very simple) code so far is:
Sub SuitorList()
'Defining Variables
Dim Row As Integer
Row = Sheets("References").Cells(6, 2).Value
'Copying Statistics
Sheets("Charts").Range(Cells(1, 1), Cells(Row, 1)).Value = _
Sheets("Data").Range(Cells(1, 1), Cells(Row, 1)).Value
End Sub
This code works fine when I use absolute cell references (i.e. "B1:B7") but when I use a relative reference I receive error code 1004: Application-defined or object-defined error.
Any thoughts?
Alternative Solution:
If you are not a fan of Loops, use Worksheet.Cells Property
Sub SuitorList()
'Defining Variables
Dim Row As Integer
Set wd = ThisWorkbook.Worksheets("Data")
Set wc = ThisWorkbook.Worksheets("Charts")
Row = Sheets("References").Cells(6, 2).Value
'Copying Statistics
Range(wd.Cells(1, 1), wd.Cells(Row, 1)).Copy Destination:=Range(wc.Cells(1, 1), wc.Cells(Row, 1))
End Sub
If you are copying data from one sheet to another and the amount of data to be copied/pasted is always changing then I would do something like this. Which is filtering the data from your selection sheet then copying it and pasting it to your destination sheet by finding the first blank cell. You may have to mess with this a bit, but it is a good start.
'Defining Variables
Dim Row As Integer
Row = Sheets("References").Cells(6, 2).Value
'switches the sheet
Sheets("Charts").Select
'filters a table based on the value of the Row variable
ActiveSheet.ListObjects("Table1").range.AutoFilter Field:=1, Criteria1:= _
range("Row"), Operator:=xlAnd
'moves to the first cell in the filtered range
range("A1").Select
'selects all values in the range and copies to clipboard
range(Selection, Selection.End(xlDown)).Select
Application.CutCopyMode = False
Selection.Copy
'switches the sheet back to data sheet
Sheets("Data").Select
'finds the first blank cell in the declared range you want to paste into
ActiveSheet.range("A:A").Find("").Select
'pastes the selection
ActiveSheet.Paste
Thanks for the help. I was able to find a work around using the following code:
Sub SuitorList()
'Defining Variables
Dim Row As Integer
Row = Sheets("References").Cells(6, 2).Value
'Copying Statistics
For i = 1 To Row
Sheets("Charts").Range("A" & i).Value = Sheets("Data").Range("A" & i).Value
Next
End Sub

VBA loop and variables - find blank row and put row number in variable

I'm writing a Macro which loops though the Excel data, which is sorted by column A and inserts a blank row if the the values for coulmn are different from the one above. This separates my data in groups by column A.
I then want to sum the value of column d of the separated groups. I have most of my code working underneath, however its the startCell variable I am having trouble with. I know what I want to do, but cant get the logic right, can someone please help sum up those individual groups.
Many thanks
Sub PutARowInWithFormula()
Range("A3").Select
Dim startCell As Integer
Dim endCell As Integer
startCell = 3
endCell = 0
Do Until ActiveCell.Value = ""
If ActiveCell.Value = ActiveCell.Offset(-1, 0).Value Then
ActiveCell.Offset(1, 0).Select
Else
' I need the bottom code to execute only once in the loop
' startCell = ActiveCell.Row
ActiveCell.EntireRow.Insert
' move to column d
ActiveCell.Offset(0, 3).Select
endCell = ActiveCell.Row - 1
ActiveCell.Formula = "=Sum(d" & startCell & ":d" & endCell & ")"
' move back to column a
ActiveCell.Offset(0, -3).Select
'move 2 rows down
ActiveCell.Offset(3, 0).Select
End If
Loop
End Sub
I am too wondering, why you don't use a PivotTable or just create this using worksheet functions, which is possible too. Also I do not really like this attempt with selections, but its your way, and I respect that. It even seems to be a quite good example of a situation, when it might be a good idea to use them. Because right now, any other way I could think of, to do this in VBA, seems to be more complicated.
So here is a fix up of your code:
Sub PutARowInWithFormula()
Range("A2").Select
Do Until ActiveCell.Value = ""
If ActiveCell.Value = ActiveCell.Offset(-1, 0).Value Then
ActiveCell.Offset(1, 0).Select
Else
ActiveCell.EntireRow.Insert
'you can use the offset directly
'by using an improved formula, you do not need to know start and end row.
ActiveCell.Offset(0, 3).Formula = _
"=SUMIF(A:A,OFFSET(INDIRECT(""A""&ROW()),-1,0),D:D)"
' move back to column a and move 2 rows down
ActiveCell.Offset(2, 0).Select
End If
Loop
End Sub
Edit
Ok, found a way easier way to do nearly the same thing:
Public Sub demo()
UsedRange.Subtotal GroupBy:=1, Function:=xlSum, TotalList:=4
End Sub
This function is also available through the ribbon-menu -> data -> sumsum
To avoid the error-message, you just need to have a title-row for your data, like:
DATE | NAME | COUNTER | VALUE