Looping & copying cells - vba

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.

Related

Excel VBA macro to remove formulas for a dynamic range not working

I have a range of four cells. The furthest left cell (column A) is a unique value. The next three cells to the right are populated with formulas. Columns B and C are Vlookups that pull values from sheet 2 when column A is populated (otherwise the cells have a value of ""). Column D populates with the current date when a value is put into Column A (otherwise the cells have a value of "" as well).
What I'm trying to do is run a macro when a unique value is put into column A that will kill the formulas in B,C,D and keep their values. Then it automatically selects the cell in the subsequent row in column A. A3 is fixed which is why I used the End(xlDown) method and then Activecell.Offset.
This is what I have that is bombing Excel when it runs:
Private Sub Worksheet_Change(ByVal Target As Range)
Range(ActiveCell, ActiveCell.Offset(0, 3)).Value = Range(ActiveCell, ActiveCell.Offset(0, 3)).Value
Range("A3").Select
Selection.End(xlDown).Select
ActiveCell.Offset(1, 0).Select
End Sub
Always turn off event handling before changing a value in a Worksheet_Change. If you don't, the routine will try to run on top of itself.
If a change in column A is what dictates the need to remove formulas then restrict the processing to when there is a change in column A.
ActiveCell is not a good choice here. Use Target instead. Target may be one or more than one cell.
Look for the first empty cell in column A from the bottom up, not the top down.
Use error control to ensure that the VLOOKUPs have returned values, not errors.
Here is some general code that should get you started.
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Columns("A")) Is Nothing Then
On Error GoTo bm_Safe_Exit
Application.EnableEvents = False
Dim rng As Range
For Each rng In Intersect(Target, Columns("A"))
If Not IsError(rng.Offset(0, 1)) Then
rng.Resize(1, 3) = rng.Resize(1, 3).Value
End If
Next rng
With Cells(Rows.Count, "A").End(xlUp).Offset(1, 0)
'optionally put in new VLOOKUP formulas in column B and C
'.Offset(0, 1).FormulaR1C1 = "=VLOOKUP(RC1, Sheet2!C1:C3, 2, FALSE)"
'.Offset(0, 2).FormulaR1C1 = "=VLOOKUP(RC1, Sheet2!C1:C3, 3, FALSE)"
.Select
End With
End If
bm_Safe_Exit:
Application.EnableEvents = True
End Sub

VBA Do Until string found, then exit step

I have a file that has date values as column headers, and the last column will always be labeled "Grand Total". I want a way to look through the column headers, (which are in cells D4:I4) and input formulas to be filled down later.
For example, if we check D4, and it does not contain "Grand Totals", then I need the below formulas input in Cells L4 and L5:
Range("L4").Select
Application.CutCopyMode = False
ActiveCell.FormulaR1C1 = _
"=""Weekending ""&TEXT(RC[-8],""mm/dd/yyy"")&"" Compliant?"""
Range("L5").Select
ActiveCell.FormulaR1C1 = _
"=IF((IF(RC17=""Duplicate"",SUMIF(C2,RC2,C[-8]),RC[-8]))<27,""Compliant"",
IF((IF(RC17=""Duplicate"",SUMIF(C1,RC1,C[-8]),RC[-8]))<30,""Approaching Limit"",
""Over""))"
Then it would go to cell E4, and if it does not contain "Grand Totals", then the formulas need to be input in cells M4 and M5... Once "Grand Totals" are found [at the top of the loop], I need to exit the loop (but not the sub).
I tried to piece together bits from code I found:
Dim GrTot As String
Dim rng1 As Range
Set rng1 = Range("D4:I4")
GrTot = "Grand Total"
Range("D4").Select
Do While ActiveCell.Value <> GrTot
But I didnt know where to go. Any help is appreciated.
EDIT:
I have since tried another method based on an example found here. This is what I have currently:
Dim x As Integer
Dim y As Integer
With Worksheets("Pivot")
x = 4
Do Until .Cells(4, x).Value = "Grand Total"
For y = 12 To 16
.Cells(4, y).Formula = "=""Weekending ""&TEXT(RC[-8],""mm/dd/yyy"")&""
Compliant?"""
.Cells(5, y).Formula = "=IF((IF(RC17=""Duplicate"",SUMIF(C2,RC2,C[-8]),
RC[-8]))<27,""Compliant"",IF((IF(RC17=""Duplicate"",
SUMIF(C1,RC1,C[-8]),RC[-8]))
<30,""Approaching Limit"",""Over""))"
Next y
x = x + 1
Loop
End With
End Sub
But this one doesn't exit correctly. It will loop through fine, writing the formulas as I requested, but it doesn't exit the loop when it reaches the "Grand Total" column. What am I doing wrong here?
Loop through each cell in the range; if the cell does not have a value you can write whatever to the cell and then something to the offset. As soon as a grand total is found in your range the loop can exit and move to the next range.
For Each item In Range
If item.Value = GrTot Then
Exit For
'Perform action on cell
item.Value = formula
item.offset(0,1).Value = formula
item.offset(0,2).Value = formula
End If

VBA Excel find and replace WITHOUT replacing items already replaced

I am looking to make an excel script that can find and replace data, but for the love of everything I cannot figure out how to write it.
Situation:
A-----------B-----------C
Cat-------Dog------Banana
Dog------Fish------Apple
Fish------Cat-------Orange
So the macro would look at the data in a cell in column B, then look at the adjacent cell in column C, and replace all instances of that data in column A with what if found in C. So the results would be:
A---------------B-----------C
Orange------Dog------Banana
Banana------Fish------Apple
Apple--------Cat-------Orange
But that's not all, I would like it to not change cells in A that already have been changed once! (I'm trying this with changing the background colour)
Any help? I am at a complete loss.
EDIT:
Okay I found out how to do the easy part (replacing), but I cannot find out how to not change cells that already have been changed once. Here is my code:
Sub multiFindNReplace()
Dim myList, myRange
Set myList = Sheets("sheet1").Range("A2:B3") 'two column range where find/replace pairs are
Set myRange = Sheets("sheet1").Range("D2:D5") 'range to be searched
For Each cel In myList.Columns(1).Cells
myRange.Replace what:=cel.Value, replacement:=cel.Offset(0, 1).Value, ReplaceFormat:=True
Next cel
End Sub
As far as I can tell, ReplaceFormat:=True
doesn't do anything ;/ so items that already have been replaced once still are being replaced! Is there a way to somehow make this work?
Here's the answer using your recommendation with color as a one-time limiter:
Sub Replace_Once()
'Find last row using last cell in Column B
LastRow = Range("B" & Rows.Count).End(xlUp).Row
'Clear colors in Column A
Range("A1:A" & LastRow).Interior.ColorIndex = xlNone
'Look at each cell in Column B one at a time (Cel is a variable)
For Each Cel In Range("B1:B" & LastRow)
'Compare the cell in Column B with the Value in Column A one at a time (C is a variable)
For Each C In Range("A1:A" & LastRow)
'Check if the Cell in Column A matches the Cell in Column B and sees if the color has changed.
If C.Value = Cel.Value And C.Interior.Color <> RGB(200, 200, 200) Then
'Colors the cell
C.Interior.Color = RGB(200, 200, 200)
'Updates the value in Column A with the cell to the right of the Cell in Column B
C.Value = Cel.Offset(0, 1).Value
End If
Next
Next
'Uncomment the line below to remove color again
'Range("A1:A" & LastRow).Interior.ColorIndex = xlNone
End Sub

I stink at Loops

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

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