Cleaning up a messy VBA formula - vba

I'm very new to Excel VBA (started about a day ago!) but I'm slowly struggling through. I've created a formula that copies a selection of three cells to another part of the sheet if column D contains the value "(2)", then assigns the value "0" to some more cells in the same row.
The trouble is, I've used a mixture of recording and typing my macro so the end result is pretty messy. Currently the macro takes a while to complete (it moves everything around and then a little hourglass appears for a good 15 seconds or so). I'm assuming this is in part due to my use of "Select" (I'm aware this is a bad thing!) but I'm just trying to work out what I can strip from the formula to make it more efficient while retaining the same outcome.
Sub MoveNames()
Dim SrchRng As Range, cel As Range
Set SrchRng = Range("D:D")
For Each cel In SrchRng
If InStr(1, cel.Value, "(2)") > 0 Then
cel.Offset(0, 1).Range("A1:C1").Select
Selection.Copy
ActiveCell.Offset(-1, 40).Range("A1").Select
ActiveSheet.Paste
Application.CutCopyMode = False
ActiveCell.Offset(0, -4) = "0"
ActiveCell.Offset(0, -5) = "0"
ActiveCell.Offset(0, -6) = "0"
ActiveCell.Offset(0, -7) = "0"
ActiveCell.Offset(0, -10) = "0"
ActiveCell.Offset(0, -12) = "0"
End If
Next cel
End Sub
Any help would be much appreciated.

If I understand what you're trying to do, this should do the same thing without having to use any objects or any copy/paste methods:
Sub MM_MoveNames()
For i = 2 To Cells(Rows.count, 4).End(xlUp).Row
If InStr(Cells(i, 4).value, "(2)") Then
Cells(i - 1, 44).Resize(1, 3).value = Cells(i, 5).Resize(1, 3).value
Cells(i, 37).Resize(1, 4).value = 0
Cells(i, 34).value = 0
Cells(i, 32).value = 0
End If
Next
End Sub
More importantly though - if your code is working, and you just want advice for improvements then you should post your code on Code Review, not on Stack Overflow.

try this
Sub MoveNames()
Dim SrchRng As Range
lastrow = Range("D" & Rows.Count).End(xlUp).Row
Set SrchRng = Range("D1:D" & lastrow)
For Each cel In SrchRng
If InStr(1, cel.Value, "(2)") > 0 Then
With cel.Offset(0, 1).Range("A1:C1")
.Copy cel.Offset(-1, 40).Range("A1")
End With
With cel.Offset(-1, 40)
.Offset(0, -4) = "0"
.Offset(0, -5) = "0"
.Offset(0, -6) = "0"
.Offset(0, -7) = "0"
.Offset(0, -10) = "0"
.Offset(0, -12) = "0"
End With
End If
Next cel
End Sub

Give this a shot, you can definitely clean it up more by combining the multiple offsets and ranges.
Sub test()
Dim rngIndex As Range
For Each rngIndex In Range("D:D")
If InStr(1, rngIndex.Value, "(2)") > 0 Then
rngIndex.Offset(0, 1).Range("A1:C1").Copy _
rngIndex.Offset(0, 1).Range("A1:C1").Offset(-1, 40).Range("A1")
With rngIndex.Offset(0, 1).Range("A1:C1")
Range(.Offset(0, -4), .Offset(0, -7)).Value = 0
.Offset(0, -10) = "0"
.Offset(0, -12) = "0"
End With
End If
Next rngIndex
End Sub

Instead of going throug each cell in column D, you can go through just the used range, like this:
Set SrchRng = Range("D1:D" & ActiveSheet.UsedRange.Rows.Count)
Which should speed it up quite a bit.
You can use Select, I found that easier when I was learning VBA myself. In time you will learn to avoid it.
To speed up macro execution when using Select, you can add Application.ScreenUpdating = False at the beginning and Application.ScreenUpdating = True at the end of your procedure.
Disabling automatic calculations is also beneficial, you can do it by adding Application.Calculation = xlManual and Application.Calculation = xlManual at the beginning and end respectively.
Hope that helps. if you have more questions, just ask.

My turn - instead of looking at each cell, just jump to the ones containing (2).
Sub MoveNames()
Dim SrchRng As Range, cel As Range
Dim rFound As Range
Dim sFirstAddress As String
Set SrchRng = ThisWorkbook.Worksheets("Sheet1").Range("D:D")
Set rFound = SrchRng.Find("(2)", LookIn:=xlValues, LookAt:=xlPart, SearchDirection:=xlNext)
If Not rFound Is Nothing Then
sFirstAddress = rFound.Address
Do
rFound.Offset(, 1).Resize(, 3).Copy Destination:=rFound.Offset(-1, 41)
rFound.Offset(-1, 34).Resize(, 4) = 0
rFound.Offset(-1, 29) = 0
rFound.Offset(-1, 31) = 0
Set rFound = SrchRng.FindNext(rFound)
Loop While Not rFound Is Nothing And rFound.Address <> sFirstAddress
End If
End Sub

Related

VBA loop to change cell value (pos/neg) and font color based on adjacent cell

I'm very new to VBA and I'm having trouble understanding how to accomplish 2 tasks in one loop. I greatly appreciate your help.
I have been able to change the numeric value in column 2 based on data in column 3, but I dont understand how to change the font of the negative values to red.
The size of the table changes monthly based on days in the month (if that matters). Thank you!
Dim AQRng As Range, Cel As Range, p_AQend As Object
Set p_AQend = Range("AQ2").End(xlDown)
Set AQRng = Range("AQ2", p_AQend)
For Each Cel In AQRng
If Cel.Value <> 0 Then
If Cel.Offset(0, 1).Text = "Negative" Then
Cel.Value = Abs(Cel.Value) * -1
ElseIf Cel.Offset(0, 1) <> "Negative" Then
Cel.Value = Abs(Cel.Value)
End If
End If
Next Cel
Try this:
Sub Test()
Dim rng As Range, cl As Range
Set rng = Range("AQ2:QA" & Range("A2").End(xlDown).Row)
For Each cl In rng
If cl.Value <> 0 Then
If cl.Offset(0, 1) = "Negative" Then
cl = Abs(cl) * -1
cl.Font.Color = vbRed
Else
cl = Abs(cl)
End If
End If
Next cl
End Sub
Try this : I added Cel.Font.Color = RGB(255, 0, 0) in your negative condition
Dim AQRng As Range, Cel As Range, p_AQend As Object
Set p_AQend = Range("AQ2").End(xlDown)
Set AQRng = Range("AQ2", p_AQend)
For Each Cel In AQRng
If Cel.Value <> 0 Then
If Cel.Offset(0, 1).Text = "Negative" Then
Cel.Value = Abs(Cel.Value) * -1
Cel.Font.Color = RGB(255, 0, 0)
ElseIf Cel.Offset(0, 1) <> "Negative" Then
Cel.Value = Abs(Cel.Value)
End If
End If
Next Cel
I hope I'm understanding this correctly. The Cel that you are changing the value on (by multiplying by -1) is the Cel you would like to change the color on, correct?
If so,
For Each Cel In AQRng
If Cel.Value <> 0 Then
If Cel.Offset(0, 1).Text = "Negative" Then
Cel.Value = Abs(Cel.Value) * -1
Cel.Font.ColorIndex = 3
ElseIf Cel.Offset(0, 1) <> "Negative" Then
Cel.Value = Abs(Cel.Value)
End If
End If
Next Cel
I have added a line under your negative condition:
Cel.Font.ColorIndex = 3. The .Font.ColorIndex will change the color of the font to whatever you choose - ColorIndex = 3 happens to change it to red.
Please Read Here for more information on the various font colors you can choose using ColorIndex.
here's a no-loop solution:
With Range("AR2", cells(Rows.Count, "AR").End(xlUp))
.Replace what:="Positive", replacement:="", lookat:=xlWhole
With .SpecialCells(xlCellTypeBlanks)
.Offset(, 1).Value = .Offset(, -1).Value
.Offset(, -1).FormulaR1C1 = "=ABS(RC[2])"
.Value = "Positive"
End With
.Replace what:="Negative", replacement:="", lookat:=xlWhole
With .SpecialCells(xlCellTypeBlanks)
.Offset(, 1).Value = .Offset(, -1).Value
.Offset(, -1).FormulaR1C1 = "=-ABS(RC[2])"
.Value = "Negative"
.Font.Color = vbRed
End With
.Offset(, -1).Value = .Offset(, -1).Value
.Offset(, 1).ClearContents
End With
this supposes column AS can be written, but the code is easily changeable to use a different helper column
Fastest way to change negative values to red.
Columns("AQ:AQ").NumberFormat = "0.00_ ;[Red]-0.00 "

Excel 2010 VBA Code not running

I'm currently using Excel 2010 and am trying to run some code I put together in VBA for Applications (after hitting alt+F11). I typed up the code in a notepad that appeared after double clicking the project I wanted to work on. I also saved everything as Excel Macro Enabled Workbook (*.xlsm).
I am trying to color the backgrounds of Column D either green or red if columns S, T, and U meet the criteria. If the columns all have a value of 0 then Cell D should be colored green. If not, it should be colored red.
Sub GreenOrRed()
Dim i As Integer
For i = 2 To i = 27293
If (Cells(i, "S").Value = 0 And Cells(i, "T").Value = 0 And Cells(i, "U").Value = 0) Then
Cells(i, "D").Interior.ColorIndex = 10
Else
Cells(i, "D").Interior.ColorIndex = 9
End If
Next i
End Sub
The code runs and doesn't throw any error but it also doesn't do anything. What am I doing wrong?
You are using counter in For loop incorrectly. It should be like this...
For i = 2 To 27293
Changed For condition.
Try this:-
Sub GreenOrRed()
Dim i As Integer
For i = 2 To 27293
If (Cells(i, "S").Value = 0 And Cells(i, "T").Value = 0 And Cells(i, "U").Value = 0) Then
Cells(i, "D").Interior.ColorIndex = 10
Else
Cells(i, "D").Interior.ColorIndex = 9
End If
Next i
End Sub
A slightly different approach:
Sub GreenOrRed()
Dim r As Range, rr As Range
Set rr = Range("D1:D27293")
For Each r In rr
If r.Offset(0, 15).Value = 0 And r.Offset(0, 16).Value = 0 And r.Offset(0, 17).Value = 0 Then
r.Interior.ColorIndex = 10
Else
r.Interior.ColorIndex = 9
End If
Next r
End Sub
You might consider setting one (or two) conditional formatting rules.
Option Explicit
Sub GreenOrRed()
With ActiveSheet
With .Range(.Cells(2, "D"), .Cells(.Rows.Count, "D").End(xlUp))
.Interior.ColorIndex = 9
.FormatConditions.Delete
With .FormatConditions.Add(Type:=xlExpression, Formula1:="=and(sum($S2)=0, sum($T2)=0, sum($U2)=0)")
.Interior.ColorIndex = 10
.StopIfTrue = True
End With
End With
End With
End Sub
I've used individual SUM functions to ensure that any text returns a numerical value of zero.
Alternate AutoFilter method.
Sub GreenOrRedFiltered()
With ActiveSheet
If .AutoFilterMode Then .AutoFilterMode = False
With .Range(.Cells(1, "D"), .Cells(.Rows.Count, "D").End(xlUp)).Resize(, 18)
.Resize(.Rows.Count - 1, .Columns.Count).Offset(1, 0).Columns(1).Interior.ColorIndex = 9
.AutoFilter Field:=16, Criteria1:=0, Operator:=xlOr, Criteria2:=vbNullString
.AutoFilter Field:=17, Criteria1:=0, Operator:=xlOr, Criteria2:=vbNullString
.AutoFilter Field:=18, Criteria1:=0, Operator:=xlOr, Criteria2:=vbNullString
With .Resize(.Rows.Count - 1, .Columns.Count).Offset(1, 0)
If CBool(Application.Subtotal(103, .Cells)) Then
.Columns(1).SpecialCells(xlCellTypeVisible).Interior.ColorIndex = 10
End If
End With
End With
If .AutoFilterMode Then .AutoFilterMode = False
End With
End Sub

VBA search in two ranges

I'm more than new at this, and I'm having trouble sorting out For...Next loops.
I want to track to two text variables in two columns, so that when both variables are found in a row text is added to that row in a different column.
This is what I have so far:
Sub AB()
Dim Rng1 As Range
Dim Rng2 As Range
Set Rng1 = Range("B1:B100")
Set Rng2 = Range("A1:A100")
For Each cel In Rng1
If InStr(1, cel.Value, "A") > 0 Then
For Each cel In Rng2
If InStr(1, cel.Value, "B") > 0 Then
cel.Offset(0, 5).Value = "AB"
End If
Next
End If
Next cel
End Sub
You might even be able to just do this?
Sub AB()
With ActiveSheet
For I = 1 To 100
If InStr(1, .Cells(I, 2), "A") > 0 And InStr(1, .Cells(I, 1), "B") > 0 Then
.Cells(I, 6).Value = "AB" 'i think offset 5 is column F?
End If
Next
End With
End Sub
Appreciate you have an answer now, but here's a different method using Find. Always good to know several ways to do something.
Sub AB()
Dim rng As Range
Dim itemaddress As String
With Columns(1)
Set rng = .Find("A", searchorder:=xlByRows, lookat:=xlWhole)
If Not rng Is Nothing Then
itemaddress = rng.Address
Do
If rng.Offset(0, 1) = "B" Then
rng.Offset(0, 2).Value = "AB"
End If
Set rng = .FindNext(rng)
Loop While Not rng Is Nothing And itemaddress <> rng.Address
End If
End With
End Sub
You're using `cel' to step through each loop - the inner loop will get confused.
Along the vein of #findwindow answer (appeared as I was typing this). Loop just once and when a match is found check the cell next to it.
Sub AB()
Dim Rng1 As Range
Dim Rng2 As Range
Dim cel1 As Range
'Be specific about which sheet your ranges are on.
With ThisWorkbook.Worksheets("Sheet1")
Set Rng1 = .Range("B1:B100")
Set Rng2 = .Range("A1:A100")
End With
For Each cel1 In Rng1
'Check each value in column B.
If InStr(1, cel1.Value, "A") > 0 Then
'If a match is found, check the value next to it.
If InStr(1, cel1.Offset(, -1), "B") > 0 Then
cel1.Offset(, 4).Value = "AB"
End If
End If
Next cel1
End Sub

VBA error next but without for each

Hi I find error when running this routine in VBA.
Sub interest()
Dim newRange As Range
Dim rng As Range
Dim cel As Range
Dim ws As Worksheet
Range("D9").Select
Range(ActiveCell, ActiveCell.End(xlDown)).Select
For Each cel In rng.Cells
If cell.Value = "Cr" Then
cell.Offset(0, -1).Value = cell.Offset(0, -1).Value
Else
cell.Offset(0, -1).Value = cell.Offset(0, -1).Value * (-1)
Next
End Sub
You forgot an End If before Next cel
You didn't attributed anything to rng! You only selected some range of the spreadsheet.
The code could be fixed like this:
...
Range("D9").Select
Range(ActiveCell, ActiveCell.End(xlDown)).Select
Set rng = Selection
...
You are getting a compile error because you aren't closing the If-block with End If (See iDevlop's answer).
In VBA, you need to write If as follows:
If cell.Value = "Cr" Then
cell.Offset(0, -1).Value = cell.Offset(0, -1).Value
Else
cell.Offset(0, -1).Value = cell.Offset(0, -1).Value * (-1)
End If
Only then you can finish the For-Loop with Next.
PS: As mentioned by Ehsan in his answer, you should assign some values to rng, or you will run into the next error at rng.Cells.

Excel Macro to write value based on value of two other columns in row

I have a pickle that I was hoping to get some help with. I cobbled together a macro the other day following this logic:
If a cell in column B has a particular text, "brand1", and that row has the value "y" in column U, then populate column W of that row with a text string, "sample1".
I had it working the other day, but for some reason it's not working today. Any ideas or thoughts? Totally open to the idea of scrapping this macro anyway and using a different one.
Sub PromoID()
Application.ScreenUpdating = False
Dim lastRow As Long
Dim cell As Range
lastRow = Range("B" & Rows.Count).End(xlUp).Row
For Each cell In Range("B2:B" & lastRow)
If InStr(1, cell.Value, "Brand1") <> 0 Then
InStr(1, cell.Offset(, 21).Value, "y") <> 0 Then
cell.Offset(, 23).Value = "sample1"
End If
End If
Next
Application.ScreenUpdating = True
End Sub
You were not that far from the answer. Keep your eyes peeled for minor errors.
Sub PromoID()
Application.ScreenUpdating = False
Dim lastRow As Long
Dim cell As Range
lastRow = Range("B" & Rows.Count).End(xlUp).Row + 1
For Each cell In Range("B2:B" & lastRow)
If InStr(1, cell.Value, "Brand1") <> 0 Then
If InStr(1, cell.Offset(0, 19).Value, "y") <> 0 Then
cell.Offset(0, 21).Value = "sample1"
End If
End If
Next
Application.ScreenUpdating = True
End Sub
Output:
Try to make everything uppercase. With Ucase()
If ucase(InStr(1, cell.Value, "Brand1") ) <> 0 Then ucase(InStr(1, cell.Offset(, 21).Value, "y") ) <> 0 Then cell.Offset(, 23).Value = "sample1"