change the range of function automatically when rows are added - excel

[1
I wrote =sum(A2:A11) in cell A1, and I wrote random numbers in A2:A11. Then I deleted some rows and then the A1 cell's range changed automatically. But I don't understand why the range does not change automatically when I add new rows and intert new values. How can I make it change automatically? Do I have to use vba to do this?

A Worksheet Change Event: Monitor Change in Column's Data
I personally would go with JvdV's suggestion in the comments.
On each manual change of a cell, e.g. in column A, it will check the formula
=SUM(A2:ALastRow) in cell A1 and if it is not correct it will overwrite it with the correct one.
You can use this for multiple non-adjacent columns e.g. "A,C:D,E".
Nothing needs to be run. Just copy the code into the appropriate sheet module e.g. Sheet1 and exit the Visual Basic Editor.
Sheet Module e.g. Sheet1 (not Standard Module e.g. Module1)
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
UpdateFirstRowFormula Target, "A"
End Sub
Private Sub UpdateFirstRowFormula( _
ByVal Target As Range, _
ByVal ColumnList As String)
On Error GoTo ClearError
Dim ws As Worksheet: Set ws = Target.Worksheet
Dim Cols() As String: Cols = Split(ColumnList, ",")
Application.EnableEvents = False
Dim irg As Range, arg As Range, crg As Range, lCell As Range
Dim n As Long
Dim Formula As String
For n = 0 To UBound(Cols)
With ws.Columns(Cols(n))
With .Resize(.Rows.Count - 1).Offset(1)
Set irg = Intersect(.Cells, Target.EntireColumn)
End With
End With
If Not irg Is Nothing Then
For Each arg In irg.Areas
For Each crg In arg.Columns
Set lCell = crg.Find("*", , xlFormulas, , , xlPrevious)
If Not lCell Is Nothing Then
Formula = "=SUM(" & crg.Cells(1).Address(0, 0) & ":" _
& lCell.Address(0, 0) & ")"
With crg.Cells(1).Offset(-1)
If .Formula <> Formula Then .Formula = Formula
End With
End If
Next crg
Next arg
Set irg = Nothing
End If
Next n
SafeExit:
If Not Application.EnableEvents Then Application.EnableEvents = True
Exit Sub
ClearError:
Debug.Print "Run-time error '" & Err.Number & "': " & Err.Description
Resume SafeExit
End Sub

Use a nested function as below:
=SUM(OFFSET(A2,,,COUNTA(A2:A26)))

Related

How to correct code so that it runs or inserts formula in column to left of Range for cells that = "Metered"

I have a worksheet where column C has a formula that looks up value if column D = "Metered".
Users, who are mostly farm workers, have the ability to overwrite it (or possibly delete it using the Make Correction button). Unless column D = "Metered", I don't care if column C is overwritten because data validation makes sure entry is OK. Users are supposed to Tab past column C unless load is "Metered". As a failsafe, I duplicated the "Metered" lookup formula elsewhere and the results are in column S. I don't get any errors on code below, but it doesn't do anything -- previous versions would do things but not the right things. Clearly, I cannot solve this on my own and very much appreciate any help you can provide. I want to run the failsafe once a day when the workbook is opened (running on laptops and speed is important).
Private Sub Workbook_Open()
Application.OnTime TimeValue("02:57:00"), "SaveBeforeDailyRestart"
Application.MoveAfterReturnDirection = xlToRight
Call MeteredLookupRefreshFormula
End Sub
Sub MeteredLookupRefreshFormula()
Sheet1.Unprotect Password:="Cami8"
Dim bng As Range
Set bng = Range("D8:D10009")
For Each cell In bng
If Value = "Metered" Then
bng.Offset(0, -1).Select
Selection.Value = "S & ActiveCell.Row)"
Else
End If
Next
Sheet1.Protect Password:="Cami8"
End Sub
Loop Through Cells
A Quick Fix (Slow)
To not be dependent on the offset you could additionally do:
cell.EntireRow.Columns("C").Value = cell.EntireRow.Columns("S").Value
Sub MeteredLookupRefreshFormulaQuickFix()
With Sheet1
.Unprotect Password:="Cami8"
With .Range("D8:D10009")
Dim cell As Range
For Each cell In .Cells
If StrComp(CStr(cell.Value), "Metered", vbTextCompare) = 0 Then
cell.Offset(0, -1).Value = cell.EntireRow.Columns("S").Value
End If
Next cell
End With
.Protect Password:="Cami8"
End With
End Sub
An Improvement (Fast)
If you have many cells containing formulas evaluating to an empty string ="" at the bottom of column D, replace xlFormulas with xlValues for these cells not to be processed and speed up even more.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Refreshes...
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Calls: RefColumn,GetRange.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Sub MeteredLookupRefreshFormula()
Const cfcAddress As String = "D8"
Const dCol As String = "C"
Const sCol As String = "S"
Const Criteria As String = "Metered"
Const pw As String = "Cami8"
Sheet1.Unprotect Password:=pw
Dim crg As Range: Set crg = RefColumn(Sheet1.Range(cfcAddress))
If crg Is Nothing Then Exit Sub ' no data
Dim cData As Variant: cData = GetRange(crg)
Dim drg As Range: Set drg = crg.EntireRow.Columns(dCol)
Dim dData As Variant: dData = GetRange(drg)
Dim sData As Variant: sData = GetRange(crg.EntireRow.Columns(sCol))
Dim r As Long
For r = 1 To UBound(cData, 1)
If StrComp(CStr(cData(r, 1)), Criteria, vbTextCompare) = 0 Then
dData(r, 1) = sData(r, 1)
End If
Next r
drg.Value = dData
Sheet1.Protect Password:=pw
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Creates a reference to the one-column range ('crg') whose first
' cell is defined by the first cell of the range ('FirstCell')
' and whose last cell is the bottom-most non-empty cell
' of the first cell's worksheet column.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function RefColumn( _
ByVal FirstCell As Range) _
As Range
Const ProcName As String = "RefColumn"
On Error GoTo ClearError
With FirstCell.Cells(1)
Dim lCell As Range
Set lCell = .Resize(.Worksheet.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
If lCell Is Nothing Then Exit Function
Set RefColumn = .Resize(lCell.Row - .Row + 1)
End With
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the values of a range ('rg') in a 2D one-based array.
' Remarks: If ˙rg` refers to a multi-range, only its first area
' is considered.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function GetRange( _
ByVal rg As Range) _
As Variant
Const ProcName As String = "GetRange"
On Error GoTo ClearError
If rg.Rows.Count + rg.Columns.Count = 2 Then ' one cell
Dim Data As Variant: ReDim Data(1 To 1, 1 To 1): Data(1, 1) = rg.Value
GetRange = Data
Else ' multiple cells
GetRange = rg.Value
End If
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function
It appears as though the contents of your FOR loop is all screwed up. This is untested but change this ...
For Each cell In bng
If Value = "Metered" Then
bng.Offset(0, -1).Select
Selection.Value = "S & ActiveCell.Row)"
Else
End If
Next
... to this ...
For Each cell In bng
If cell.Value = "Metered" Then
cell.Offset(0, -1).Value = cell.Worksheet.Range("S" & cell.Row).Value
End If
Next
... and it should help.

How can I get this Excel VBA to run for all rows, instead of just the first row?

So darn new to VBA, almost got this working. I'm trying to reset some Data Validation lists so that if someone changes a Country selection, then a couple of cells will reset. Example, if I pick USA then I want the corresponding State and Shift column to display "Please select..." and if the user changes the country to something other than USA then it doesn't say anything in the State column, only in the Shift column. I got this working but it only runs for the first row. I'm not sure if my range is wrong or if I'm supposed to loop, both of which I'm totally ignorant on.
Option Explicit
'The way this works is if the Payroll Country changes then the sub selections
'of State and Shift should reset based on if the country is USA
'Payroll Country = A column
'State = X column
'Shift = Y column
Private Sub Worksheet_Change(ByVal Target As Range)
'"If Target.Count > 1 Then Exit Sub" is the VBA code to prevent an error if user highlights the range and deletes the data
If Target.Count > 1 Then Exit Sub
If Target.Address = "$A$6" And Target.Value = "USA" Then
Range("X6").Value = "Please select..."
Range("Z6").Value = "Please select..."
ElseIf Target.Address = "$A$6" And Target.Value <> "USA" Then
Range("X6").Value = ""
Range("Z6").Value = "Please select..."
End If
End Sub
Try this:
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.CountLarge > 1 Then Exit Sub 'CountLarge handles larger ranges...
'check Target column and row...
If Target.Column = 1 and Target.Row >= 6 Then
With Target.EntireRow
.Columns("X").Value = IIf(Target.Value = "USA", _
"Please select...", "")
.Columns("Z").Value = "Please select..."
End With
End If
End Sub
A Worksheet Change
If you out-comment the line Application.EnableEvents = False you will notice that there will be more Debug.Print lines (one more for USA, two more for non-USA) i.e. after writing (crg.Value = InitialString), the code calls itself (actually the worksheet change event calls the InitializeCountry procedure) again but 'luckily' exits after the line If irg Is Nothing Then Exit Sub since it is writing to a non-intersecting range, but nevertheless debug-printing the Source Range Address once more, before it continues with the line Debug.Print "Criteria Range (Both Columns): " & crg.Address(0, 0). This should help you to fully understand why the disabling of events is necessary.
Out-comment or delete the range addresses related Debug.Print lines when done testing because they're slowing down the code.
Sheet Module e.g. Sheet1
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
InitializeCountry Target
End Sub
Standard Module e.g. Module1
Option Explicit
Sub InitializeCountry( _
ByVal Target As Range)
' Declare constants.
Const SourceFirstCellAddress As String = "A6"
Const StateColumn As String = "X"
Const ShiftColumn As String = "Z"
Const CriteriaString As String = "USA"
Const InitialString As String = "Please select..."
' Read.
Dim ws As Worksheet: Set ws = Target.Worksheet
Dim sfCell As Range: Set sfCell = ws.Range(SourceFirstCellAddress)
Dim srg As Range: Set srg = sfCell.Resize(ws.Rows.Count - sfCell.Row + 1)
Debug.Print "Source Range Address: " & srg.Address(0, 0)
Dim irg As Range: Set irg = Intersect(srg, Target) ' Intersect Range
If irg Is Nothing Then Exit Sub
Debug.Print "Intersect Range Address: " & irg.Address(0, 0)
' ShiftColumn
Dim crg As Range ' Criteria Range
Set crg = irg.EntireRow.Columns(ShiftColumn)
Debug.Print "Criteria Range (ShiftColumn): " & crg.Address(0, 0)
' StateColumn
Dim erg As Range ' Empty Range
Dim iCell As Range ' Current Intersect Cell
For Each iCell In irg.Cells
If iCell.Value = CriteriaString Then
Set crg = Union(crg, iCell.EntireRow.Columns(StateColumn))
Else
If erg Is Nothing Then
Set erg = iCell.EntireRow.Columns(StateColumn)
Else
Set erg = Union(erg, iCell.EntireRow.Columns(StateColumn))
End If
End If
Next iCell
' Write.
Application.ScreenUpdating = False
' This is crucial to not retrigger the event procedure when writing!
Application.EnableEvents = False
On Error GoTo ClearError
crg.Value = InitialString
Debug.Print "Criteria Range (Both Columns): " & crg.Address(0, 0)
If Not erg Is Nothing Then
erg.ClearContents ' erg.Value = Empty
Debug.Print "Empty Range (StateColumn): " & erg.Address(0, 0)
End If
SafeExit:
Application.EnableEvents = True
Application.ScreenUpdating = True
Exit Sub
ClearError:
' Don't uncomment this line!
Debug.Print "Run-time error '" & Err.Number & "': " & Err.Description
Resume SafeExit
End Sub
' Test multi-range (only possible with VBA).
Sub InitializeCountryTEST()
Dim rg As Range: Set rg = Range("A6:A20,A31:A50,A61:A100")
rg.Value = "USA"
End Sub
' Debug.Print result for 'USA' (no 'Empty Range'):
'Source Range Address: A6:A1048576
'Intersect Range Address: A6:A20,A31:A50,A61:A100
'Criteria Range(ShiftColumn): Z6:Z20
'Criteria Range (Both Columns): Z6:Z20,X6:X20,X31:X50,X61:X100
' Debug.Print result otherwise:
'Source Range Address: A6:A1048576
'Intersect Range Address: A6:A20,A31:A50,A61:A100
'Criteria Range(ShiftColumn): Z6:Z20
'Criteria Range (Both Columns): Z6:Z20
'Empty Range (StateColumn): X6:X20,X31:X50,X61:X100

Auto-Updated Validated Cell When Source Value Changes

I'm trying to update cells that have data validation restrictions on them automatically.
For example - Sheet1 has below column (Column E):
Package Identifier
A
B
C
where the values are taken from the same named column (Column D) in Sheet2.
The below code works for MANUAL changes only
Sheet2 Code:
Private Sub Worksheet_Change(ByVal Target As Range)
Application.ScreenUpdating = False
Dim count_cells As Integer
Dim new_value As String
Dim old_value As String
Dim rng As Range
For count_cells = 1 To Range("D1").CurrentRegion.Rows.Count - 1
Set rng = Worksheets("Sheet1").Range("E3:E86")
If Intersect(Target, Range("D" & count_cells + 1)) Is Nothing Then
Else
Application.EnableEvents = False
new_value = Target.Value
Application.Undo
old_value = Target.Value
Target.Value = new_value
rng.Replace What:=old_value, Replacement:=new_value, LookAt:=xlWhole
Target.Select
End If
Next count_cells
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub
So, if i manually change value B to Z, all the corresponding values that were B on Sheet1 now change to Z. The problem is, Package Identifier on Sheet2 is dictated by concatenating other columns
=CONCATENATE(B35, "-", "Package", "-", TEXT(C35, "#000"))
This piece of code breaks when trying to use it with the above formula. How can i make this set of code trigger on this formula based output?
Assuming this is how the Validation sheet looks
and this is how the Source sheet looks
Let's say user selects first option in Validation sheet.
Now go back to Source sheet and change 1 to 2 in cell C2.
Notice what happens in Validation sheet
If this is what you are trying then based on the file that you gave, test this code.
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim aCell As Range
Dim NewSearchValue As String
Dim OldSearchValue As String
Dim NewArrayBC As Variant
Dim OldArrayA As Variant, NewArrayA As Variant
Dim lRow As Long, PrevRow As Long
On Error GoTo Whoa
Application.EnableEvents = False
If Not Intersect(Target, Range("B:C")) Is Nothing Then
lRow = Range("A" & Rows.Count).End(xlUp).Row
'~~> Store new values from Col A, B and C in an array
NewArrayBC = Range("B1:C" & lRow).Value2
NewArrayA = Range("A1:A" & lRow).Value2
Application.Undo
'~~> Get the old values from Col A
OldArrayA = Range("A1:A" & lRow).Value2
'~~> Paste the new values in Col B/C
Range("B1").Resize(UBound(NewArrayBC), 2).Value = NewArrayBC
'~~> Loop through the cells
For Each aCell In Target.Cells
'~~> Check if the prev change didn't happen in same row
If PrevRow <> aCell.Row Then
PrevRow = aCell.Row
NewSearchValue = NewArrayA(aCell.Row, 1)
OldSearchValue = OldArrayA(aCell.Row, 1)
Worksheets("Validation").Columns(2).Replace What:=OldSearchValue, _
Replacement:=NewSearchValue, Lookat:=xlWhole
End If
Next aCell
End If
Letscontinue:
Application.EnableEvents = True
Exit Sub
Whoa:
MsgBox Err.Description
Resume Letscontinue
End Sub
A different approach from Sid's...
Instead of updating values in the DV cells when the source range changes, this replaces the selected value with a link to the matching cell in the DV source range.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rngV As Range, rng As Range, c As Range, rngList As Range
Dim f As Range
On Error Resume Next
'any validation on this sheet?
Set rngV = Me.Cells.SpecialCells(xlCellTypeAllValidation)
On Error GoTo 0
If rngV Is Nothing Then Exit Sub 'no DV cells...
Set rng = Application.Intersect(rngV, Target)
If rng Is Nothing Then Exit Sub 'no DV cells in Target
For Each c In rng.Cells
If c.Validation.Type = xlValidateList Then 'DV list?
Set rngList = Nothing
On Error Resume Next
'see if we can get a source range
Set rngList = Evaluate(c.Validation.Formula1)
On Error GoTo 0
If Not rngList Is Nothing Then
Application.EnableEvents = False
'find cell to link to
Set f = rngList.Find(c.Value, LookIn:=xlValues, lookat:=xlWhole)
If Not f Is Nothing Then
Application.EnableEvents = False
c.Formula = "='" & f.Parent.Name & "'!" & f.Address(0, 0)
Application.EnableEvents = True
End If
Else
Debug.Print "No source range for " & c.Address
End If
End If
Next c
End Sub

How do I execute instructions only if the cell value change?

I have a ByVal code to clear the contents of a specific range inside a table, it works. But I need to add a condition for the instructions execute if the RANGE VALUE (content) change, not if I only place the cursor on it.
Also, someone knows how to reference a table column in VBA? Now I'm using an estimated range "C1:C999" but I'll like to use his name "A_[OPERATION]".
This is the code:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Not Application.Intersect(Range("C1:C999"), Range(Target.Address)) Is Nothing Then
Range(Selection, Selection.End(xlToRight)).ClearContents
End If
End Sub
You could use the change event instead.
Here's a link to the documentation:
https://learn.microsoft.com/en-us/office/vba/api/excel.worksheet.change
Alternatively, you could save the value of your target cell in a variable and check if the value changed before executing your clear contents.
For your second question, you should probably ask about it in a separate post.
A Worksheet Change
Adjust the table name (tName) and the header (column) name (hName).
I have adjusted it to clear contents in the cells after the column.
If you really need to clear the contents of the column too, then replace cel.Offset(, 1) with cel.
In a table, the current setup will automatically clear the contents of all the cells to the right of the specified column, if a value in the column is manually or via VBA changed. This will not work if the column contains formulas. Non-contiguous deletions are also supported.
The code
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Const ProcName As String = "Worksheet_Change"
On Error GoTo clearError
Const tName As String = "A_"
Const hName As String = "OPERATION"
Dim rng As Range
Set rng = Range(tName & "[" & hName & "]")
Set rng = Intersect(rng, Target)
If rng Is Nothing Then GoTo ProcExit
Application.EnableEvents = False
With ListObjects(tName).HeaderRowRange
Dim LastColumn As Long
LastColumn = .Columns(.Columns.Count).Column
End With
Dim cel As Range
For Each rng In rng.Areas
For Each cel In rng.Cells
With cel.Offset(, 1)
.Resize(, LastColumn - .Column + 1).ClearContents
End With
Next cel
Next rng
CleanExit:
Application.EnableEvents = True
GoTo ProcExit
clearError:
Debug.Print "'" & ProcName & "': " & vbLf _
& " " & "Run-time error '" & Err.Number & "':" & vbLf _
& " " & Err.Description
On Error GoTo 0
GoTo CleanExit
ProcExit:
End Sub
A change in cell value is captured by the Worksheet_Change event-handle.
However, this handle will trigger even if it is a false change. For example, if the cell value before the change is "A", and user just entered "A" again in the cell, the Change event procedure will be triggered anyhow.
To avoid this, we can use Worksheet_Change and Worksheet_SelectionChange together.
Using Worksheet_SelectionChange, we record the old value somewhere, say a Name. Then using Worksheet_Change, we can compare what the user has entered against the Name to see if a true change is made.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim Nm As Name: Set Nm = ActiveWorkbook.Names("OldValue")
If Target.Count = 1 Then
'This only record one old cell value.
'To record multiple cells old value, use a hidden Worksheet to do so instead of a Name.
Nm.Comment = Target.Value2
else
Nm.Comment = Target.Value2(1, 1)
End If
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
Dim Nm As Name: Set Nm = ActiveWorkbook.Names("OldValue")
If Target.Value2 <> Nm.Comment Then
Debug.Print "True change"
Else
Debug.Print "False change"
End If
End Sub
You can access a table's methods and properties through the listobject object. Below is an example how to do so.
Sub Example()
Dim lo As ListObject
Dim lc As ListColumn
Set lo = Range("Table1").ListObject
Set lc = lo.ListColumns("Column2")
End Sub
That said, for your case, it would be Range("A_").ListObject.ListColumns("OPERATION").DataBodyRange.

VBA type mismatch when value is calculated by a formula in the cell

Private Sub Worksheet_Change(ByVal Target As Range)
Dim KeyCells As Range
Set KeyCells = Range("F2:F220")
If Not Application.Intersect(KeyCells, Range(Target.Address)) _
Is Nothing Then
If Range("G2:G220").Value < 0 Then
MsgBox "Cell " & Target.Address & " has changed."
End If
End If
End Sub
There's a formula in column G that calculates the value from numbers in column F. I wanted a popup when a result in G has a negative value. The type mismatch is on the line If Range("G2:G220") ... The column is formatted as Number, but it shows as Variant/Variant. I assume this is because the cell contents are actually a formula?
Is there a way round this without introducing 'helper' columns?
This is only my second bit of VBA so I'm happy to hear if you spot any other errors!
Restrict the Number of Results
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Const KeyAddress As String = "F2:F220"
Const CheckColumn As Variant = "G" ' e.g. "A" or 1
Const MaxResults As Long = 1
' Define 'KeyCells'.
Dim KeyCells As Range: Set KeyCells = Range(KeyAddress)
' Define the range of cells that have changed and are part of 'KeyCells'.
Dim rng As Range: Set rng = Application.Intersect(Target, KeyCells)
' Check if there are any cells that have changed and are part of 'KeyCells'.
If rng Is Nothing Then Exit Sub
' Check if there is more than 'MaxResults' cells that have changed and
' are part of 'KeyCells'.
If rng.Cells.Count > MaxResults Then GoSub checkMoreCells
' Calculate the offset between 'Key' and 'Check' columns.
Dim ColOff As Long: ColOff = Columns(CheckColumn).Column - KeyCells.Column
Dim cel As Range
For Each cel In rng.Cells
' Check if the value in 'Check' column is negative.
If cel.Offset(, ColOff).Value < 0 Then
MsgBox "Cell '" & cel.Address(False, False) & "' has changed " _
& "to '" & cel.Value & "'."
End If
Next cel
Exit Sub
checkMoreCells:
Dim msg As Variant
msg = MsgBox("There could be '" & rng.Cells.Count & "' results. " _
& "Are you sure you want to continue?", _
vbYesNo + vbCritical, _
"More Than One Cell")
If msg = vbYes Then Return
Exit Sub
End Sub
Something like this should work:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rng As Range, c As Range
Set rng = Application.Intersect(Range("F2:F220"), Target) 'Target is already a Range...
'any changed cells in F2:F220 ?
If Not rng Is Nothing Then
'loop over the changed cell(s)
For Each c in rng.Cells
'check value in ColG...
If c.Offset(0, 1).Value < 0 Then
MsgBox "Cell " & c.Address & " has changed."
End If
Next c
End If
End Sub
Edit: I realize it's not exactly clear whether you want to know if there's any negative numbers in Col G, or if you want to track row-by-row. This code does the latter.

Resources