VBA Change Event Calulation - vba

I am trying to use the Worksheet Change Event in Excel VBA, but it doesn't seem to working how I thought it would.
I basically want to calculate a cells value (Q2) when the value of another cell (R2) is changed or vise versa.
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Cells.Count > 1 Then Exit Sub
If Intersect(Target, Range("O:R")) Is Nothing Then Exit Sub
Application.EnableEvents = False
If Target.Column = 3 Then
'User has changed something in column Q:
Target.Offset(0, 1).Value = Cells(2, 3) * Cells(2, 1)
If Target.Column = 4 Then
'User has changed something in column R:
Target.Offset(0, -1).Value = Cells(2, 3) / Cells(2, 1)
End If
Application.EnableEvents = True
End Sub

Don't avoid working with multiple cells as the Target. Intersect can quickly parse down even deleting several full columns to the appropriate range and further restrict to the worksheet's UsedRange.
Add error control, especially to the division operation. A blank cell in A2 will quickly choke the calculation on a 'divide-by-zero'.
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
'deal with multiple cells as bel;ow; don't avoid them
'If Target.Cells.Count > 1 Then Exit Sub
'use the Intersect to determine if relevant cells have been chanmged
'note: columns Q:R, not O:R and restrict to the used range
If Not Intersect(Target, Target.Parent.UsedRange, Range("Q:R")) Is Nothing Then
On Error GoTo Safe_Exit
Application.EnableEvents = False
Dim trgt As Range
For Each trgt In Intersect(Target, Target.Parent.UsedRange, Range("Q:R"))
Select Case trgt.Column
Case 17
'guard against multiplying a number by text
If Not IsError(Cells(2, 3).Value2 * Cells(2, 1).Value2) Then
trgt.Offset(0, 1) = Cells(2, 3).Value2 * Cells(2, 1).Value2
End If
Case 18
'guard against possible #DIV/0! error and divding a number by text
If Not IsError(Cells(2, 3).Value2 / Cells(2, 1).Value2) Then
trgt.Offset(0, -1) = Cells(2, 3).Value2 / Cells(2, 1).Value2
End If
End Select
Next trgt
End If
Safe_Exit:
Application.EnableEvents = True
End Sub
I'm pretty sure that the actual calculation should involve a variable like trgt.Row but your posted calculation only used C2 and A2 as static cell references to divide/multiply against each other.

Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Cells.CountLarge > 1 Then Exit Sub
If Intersect(Target, Range("O:R")) Is Nothing Then Exit Sub
Application.EnableEvents = False
'If Target.Column = 17 Then 'CHANGED HERE!
'User has changed something in column Q:
'Target.Offset(0, 1).Value = Cells(2, 3) * Cells(2, 1)
'End If
'If Target.Column = 18 Then 'CHANGED HERE!
'User has changed something in column R:
'Target.Offset(0, -1).Value = Cells(2, 3) / Cells(2, 1)
'End If
' I leave the If-versions above for info, but Select Case is better sometimes
Select Case Target.Column
Case 17 ' column Q
Target.Offset(0, 1).Value = Cells(2, 3) * Cells(2, 1)
Case 18 ' column R
Target.Offset(0, -1).Value = Cells(2, 3) / Cells(2, 1)
End Select
Application.EnableEvents = True
End Sub
Q Column is number 17, and Column R is number 18 as shown above.

Related

VBA save value from cell before it changes

I have a spreadsheet where i implement a score board.
The behavior i need is when the cell that has the score value rises the cell near it, on column b, changes it's color to green, when the the cell score value goes down the cell near it changes it's color to red.
The cell range where the score is changing is e5:e67
In short:
When the user inputs a number in column f, the score raises in column e, and in column b (on same row) the color must change to green or red
I made this VBA code, but without luck.
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Range("e5:e67")) Is Nothing Then
If Target.Column = 5 Then
thisRow = Target.Row
Dim OldValue As Variant
Application.EnableEvents = False
Application.Undo
OldValue = Target.Value
Application.Undo
Application.EnableEvents = True
If OldValue < Target.Value Then
Range("b" & thisRow).Interior.ColorIndex = 4
ElseIf OldValue > Target.Value Then
Range("b" & thisRow).Interior.ColorIndex = 3
End If
End If
End If
End Sub
Here is a screen capture of my ranking sheet:
Try by intercepting the Worksheet_Calculate event. You need to save the old value in a static local array, that I call oldVal.
Private Sub Worksheet_Calculate()
Static oldVal
If IsEmpty(oldVal) Then
oldVal = Application.Transpose(Range("e5:e67").Value2)
ReDim Preserve oldVal(5 To 67)
Exit Sub
End If
Dim i As Long
For i = LBound(oldVal) To UBound(oldVal)
If oldVal(i) > Cells(i, "E").Value2 Then Cells(i, "B").Interior.ColorIndex = 3
If oldVal(i) < Cells(i, "E").Value2 Then Cells(i, "B").Interior.ColorIndex = 4
oldVal(i) = Cells(i, "E").Value2
Next
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
Dim i As Integer
If Not Intersect(Target, Range("e6:e67")) Is Nothing Then
If Target.Offset(-1) < Target Then
i = 4
Else
i = 3
End If
Range("b" & Target.Row).Interior.ColorIndex = i
End If
End Sub

Autoformat row based on values in each cell using Excel VBA?

I have Table1
Column A has a Date e.g. 30/5/2017
Column B has Status e.g "Success"
Column C has Value e.g 500
Requirement: Apply custom Conditional formatting in VBA when a cell is changed
Let's say the change happened in Columns A, B or C in row 5
Regardless whether the change happened in Columns A, B, or C, the same logic should be executed.
If column A value is less than Now(), then row 5 should be red background and white text. No further checks should run.
Else If column B is "Success", then row 5 should be green background and white text. No further checks should run.
Else If column C has value less than 500, then row 5 should be blue background and white text. No further checks should run.
The VBA code below is to check for change on a cell - it autoformats cell in column b with a hyperlink.
What I need now is to autoformat the whole row based on the criteria above.
Private Sub Worksheet_Change(ByVal Target As Range)
If ((Not Intersect(Target, Range("B:B")) Is Nothing) Or (Not Intersect(Target, Range("F:F")) Is Nothing) Or (Not Intersect(Target, Range("G:G")) Is Nothing) Or (Not Intersect(Target, Range("I:I")) Is Nothing)) Then
End If
End Sub
Try this code:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim Rng As Range, R As Range
Dim fCol As Long, bCol As Long
Set Rng = Application.Intersect(Target, Columns("A:C"))
If Not Rng Is Nothing Then
Set Rng = Application.Intersect(Rng.EntireRow, Columns("A:C"))
fCol = vbWhite
For Each R In Rng.Rows
If R.Cells(1, 1).Value <> vbNullString And R.Cells(1, 1).Value < Now Then
bCol = vbRed
ElseIf R.Cells(1, 2).Value <> vbNullString And R.Cells(1, 2).Value = "Success" Then
bCol = vbGreen
ElseIf R.Cells(1, 3).Value <> vbNullString And R.Cells(1, 3).Value < 500 Then
bCol = vbBlue
Else
bCol = xlNone
fCol = vbBlack
End If
R.EntireRow.Interior.Color = bCol
R.EntireRow.Font.Color = fCol
Next
End If
End Sub
Edit:
I have Table1
If Table1 is a ListObject (Excel tables) then we can modify the above code to make it watch first three columns of this table regardless of where the first column is starting (in column "A" or "B" or etc..), and format only the table row not the EntireRow :
Private Sub Worksheet_Change(ByVal Target As Range)
Dim LObj As ListObject
Dim RngToWatch As Range
Dim Rng As Range, R As Range
Dim fCol As Long, bCol As Long
Set LObj = ListObjects("Table1") ' the name of the table
Set RngToWatch = Range(LObj.ListColumns(1).DataBodyRange, LObj.ListColumns(3).DataBodyRange)
Set Rng = Application.Intersect(Target, RngToWatch)
If Not Rng Is Nothing Then
Set Rng = Application.Intersect(Target.EntireRow, RngToWatch)
fCol = vbWhite
For Each R In Rng.Rows
If R.Cells(1, 1).Value <> vbNullString And R.Cells(1, 1).Value < Now Then
bCol = vbRed
ElseIf R.Cells(1, 2).Value <> vbNullString And R.Cells(1, 2).Value = "Success" Then
bCol = vbGreen
ElseIf R.Cells(1, 3).Value <> vbNullString And R.Cells(1, 3).Value < 500 Then
bCol = vbBlue
Else
bCol = xlNone
fCol = vbBlack
End If
With Application.Intersect(LObj.DataBodyRange, R.EntireRow)
.Interior.Color = bCol
.Font.Color = fCol
End With
Next
End If
End Sub
I am assuming your table (having three columns) are present in Sheet1.
So, add following code in Sheet1 (not in separate module)
Private Sub Worksheet_Change(ByVal Target As Range)
Dim irow As Variant
' First identify the row changed
irow = Target.Row
' Invoke row formatter routine
Call DefineFormat(irow)
End Sub
Then add following piece of code in a module (you may add under Sheet1 as well but it will limit the uses of this module)
Sub DefineFormat(irow) ' Receive the row number for processing
Dim vVal As Variant
Dim Rng As Range
Dim lFont, lFill As Long
' Define the basis for validation
Dim Current, Success, limit As Variant ' Can be defined as constant as well
Current = Date ' Set today's date
Success = "Success" ' Set success status check
limit = 500 ' Set limit for value check
' Set range for the entire row - Columns A(index 1) to Column C (index 3)
Set Rng = Range(Application.ActiveSheet.Cells(irow, 1).Address, Application.ActiveSheet.Cells(irow, 3).Address)
lFont = vbWhite
' Assuming columns A, B and C needs to be formatted
If Application.ActiveSheet.Cells(irow, 1) < Current Then
lFill = vbRed ' Check for col A
Else:
If Application.ActiveSheet.Cells(irow, 2) = Success Then
lFill = vbGreen ' Check for col B
Else
If Application.ActiveSheet.Cells(irow, 3) < limit Then
lFill = vbBlue ' Check for col C
Else ' Default formatting
lFill = xlNone
lFont = vbBlack
End If
End If
End If
Rng.Interior.Color = lFill
Rng.Font.Color = lFont
End Sub
This will format the row as the data is modified (just like conditional formatting)
Also, if you need to format the entire table in one go, then you may call DefineFormat routine in a loop for each row of the table as illustrated by Fadi in his reply.

Auto-populate specific columns on excel based on selected menu using vba

I am trying to auto-populate the next columns based on the selected dropdown option of the 1st column on excel.
Below sample of the initial code that I thought but it appears my approach is incorrect.
Private Sub WorksheetStore_Change(ByVal Target As Range)
Dim i As Integer
Dim intCol As Integer
intCol = shtStoreGroup.Range("A")
If Not IsEmpty(Target.value) And intCol > 1 And Target.Columns.Count = 1 And Target.Column = intCol And Target.Row > Start_Row Then
For i = Target.Row To Target.Row + Target.Rows.Count - 1
If shtStoreGroup.Columns(intCol).Rows(i).value = "Create" Then
shtStoreGroup.Columns(intCol + 2).Rows(i).value = "N/A"
shtStoreGroup.Columns(intCol + 3).Rows(i).value = "Test"
Next i
End If
End Sub
May be you're after this:
Private Sub Worksheet_Change(ByVal target As Range)
If Not ValidateTarget(target, 2) Then Exit Sub '<-- exit if function validating 'Target' returns 'False'
On Error GoTo EXITSUB ' <-- be sure to handle possible errors properly
Application.EnableEvents = False '<--| disable events handling not to run this sub on top of itself
Select Case UCase(target.Value)
Case "CREATE"
target.Offset(, 2).Value = "N/A"
target.Offset(, 3).Value = "Test"
Case "DELETE"
target.Offset(, 2).Value = "???"
target.Offset(, 3).Value = "Test??"
End Select
EXITSUB:
Application.EnableEvents = True '<--| restore events handling
End Sub
Function ValidateTarget(target As Range, Start_Row As Long) As Boolean
With target
If .columns.Count > 1 Then Exit Function
If .Column <> 1 Then Exit Function
If .Row <= Start_Row Then Exit Function
If IsEmpty(.Value) Then Exit Function
ValidateTarget = True
End With
End Function
place the above code in the relevant worksheet ("shtStoreGroup"?) code pane, and NOT in a normal module code pane
I don't think you need worksheet_change() function
with a combobox named cbTest I would do it like
Option Explicit
Sub fill()
cbTest.AddItem ("Value1")
cbTest.AddItem ("Value2")
End Sub
Private Sub cbTest_Change()
Select Case cbTest.Value
Case "Value1"
Cells(1, 1).Value = "Test"
Case "Value2"
Cells(1, 2).Value = "Test2"
End Select
End Sub

Getting numbers to automatically move up when inserting lines

Currently working on an excel sheet to rank projects, we would like it to automatically increase the numbers if we insert a new line and input an existing number rank. If we put in a line and type in 9 for its rank we want the pre existing 9 to move to 10 and the old 10 to move to 11 etc. I have kind of worked it out, however my code automatically numbers the first row as 1 and so forth. This is what I have so far.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim I As Integer
I = 1
Application.EnableEvents = False
For I = 1 To 20
Range("A" & I).Value = I
Next
Range("A21").Value = ""
Application.EnableEvents = True
End Sub
You could loop through every cell in column A and, if its value is greater than (or equal to) the one just changed, increment it by one:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim I As Long
Dim v As Long
Dim r As Range
Set r = Application.Intersect(Range("A:A"), Target)
If r Is Nothing Then
Exit Sub
End If
If r.Count > 1 Then
Exit Sub
End If
If IsEmpty(r.Value) Then
Exit Sub
End If
I = 1
v = r.Value
If Application.CountIf(Range("A:A"), v) > 1 Then ' Only change things if this
' value exists elsewhere
Application.EnableEvents = False
For I = 1 To Cells(Rows.Count, "A").End(xlUp).Row
If Cells(I, "A").Address <> r.Address Then
If IsNumeric(Cells(I, "A").Value) Then ' skip cells that aren't numeric
If Cells(I, "A").Value >= v Then
Cells(I, "A").Value = Cells(I, "A").Value + 1
End If
End If
End If
Next
Application.EnableEvents = True
End If
End Sub

Auto Formatting Race Result Times

I have started creating a VBA Macro that helps me add formatting to rows as I add them with a custom NumberFormat. But since My partner and I sometimes Enter item like "ss.00" and this messed up the cell. So I started writing out another Sub where it checks if its missing the semicolon. How can I add "0:" to the front of this value automatically where the cell would show "0:50.20", Everytime I do it, it ends up being a long number.
Private Sub Worksheet_Change(ByVal Target As Excel.Range)
If Target.Cells.Count = 1 Then
If Target.Column = 1 Then
If Target.Row < 24 And Target.Row > 1 Then
Set FirstRow = Target.Offset(0, 1)
Set LastRow = Target.Offset(0, 11)
If Target.Value <> "" Then
For Each Cel In Range(FirstRow, LastRow)
Cel.NumberFormat = "m:ss.00;#"
Next
Else
If MsgBox("This will erase the row! Are you sure?", vbYesNo) = vbNo Then
Exit Sub
Else
For Each Cel In Range(FirstRow, LastRow)
Cel.ClearContents
Next
End If
End If
End If
End If
Const sCheckAddress As String = "B2:L24"
Dim rngIntersect As Range
On Error Resume Next
Set rngIntersect = Intersect(Me.Range(sCheckAddress), Target)
On Error GoTo 0
If Not (rngIntersect Is Nothing) Then
If Target.Value2 <> "" Then
If InStr(Target.Value2, ":") < 1 Then
End If
End If
End If
End If
End Sub
Maybe this would help you :
If InStr(Target.Value2, ":") < 1 Then
' ":" not found
Target.Value = CStr("0:" & Target.Value)
Else
' ":" found
'Nothing to add
End If