Prevent EventChange Sub running unexpectedly - excel

Advice would be gratefully appreciated. I am developing a spreadsheet using Excel 2016/Windows.
I have written 4 eventchange subroutines and all work well. The VBA Code for a worksheet checks for 4 events. Event 1, 2 and 3 enter today's date in a cell if data is entered in another cell (code not included below)
Code for EventChange works fine, but sometimes works when not expected to!
EventChange4 moves a value from one cell to another if another cell contains the text in Column J is "THIS Month – Payment Due" or "Issued But Not Paid. The second part of this eventchange4 moves a zero value to 2 cells if the data in column j contains text "not going ahead"
I am new to VBA. The problem is that eventchange4 runs for no apparent reason, e.g. copying a cell value in column H down to another cell in column h. How can I modify the code such that that eventchange4 only runs when the data in Column J Changes??? All advice gratefully accepted!!!!
Private Sub Worksheet_Change(ByVal Target As Range)
Call EventChange_1(Target)
Call EventChange_2(Target)
Call EventChange_3(Target)
Call EventChange_4(Target)
End Sub
Sub EventChange_1(ByVal Target As Range)
'Update on 11/11/2019 -If data changes in column L, insert
'today's date into column M
End Sub
Sub EventChange_2(ByVal Target As Range)
'Update on 15/01/2020 -If data changes in column P, insert today's date
'into next Column Q
End Sub
Sub EventChange_3(ByVal Target As Range)
'Update on 15/01/2020 -If data changes in column R, insert today's date
'into next Column S
End Sub
Sub EventChange_4(ByVal Target As Range)
On Error Resume Next
Application.EnableEvents = False
' this works !
If Target.Column = 10 And (Target.Value = "THIS Month – Payment Due" Or Target.Value = "Issued But Not Paid") Then
Range("K" & Target.Row).Value = Range("I" & Target.Row).Value
Range("I" & Target.Row).Clear
MsgBox "Moved Commission Due to Month Paid"
End If
If Target.Column = 10 And (Target.Value = "Not Going Ahead") Then
Range("I" & Target.Row).Value = 0
Range("K" & Target.Row).Value = 0
MsgBox "Moved ZERO to Initial Commisson and Month Paid"
End If
Application.EnableEvents = True
End Sub

Ideally you should update your code so it can properly handle a Target range which is not just a single cell:
Sub EventChange_4(ByVal Target As Range)
Dim rng As Range, c As Range, v
'any part of Target in Column J?
Set rng = Application.Intersect(Target, Me.Columns(10))
If Not rng Is Nothing Then
'have some cells to process...
On Error GoTo haveError
Application.EnableEvents = False
'process each affected cell in Col J
For Each c In rng.Cells
v = c.Value
If v = "THIS Month – Payment Due" Or v = "Issued But Not Paid" Then
Range("K" & c.Row).Value = Range("I" & c.Row).Value
Range("I" & c.Row).Clear
MsgBox "Moved Commission Due to Month Paid"
End If
If v = "Not Going Ahead" Then
Range("I" & c.Row).Value = 0
Range("K" & c.Row).Value = 0
MsgBox "Moved ZERO to Initial Commisson and Month Paid"
End If
Next c
End If
haveError:
Application.EnableEvents = True
End Sub
NOTE: this is assumed to be in the relevant worksheet code module - otherwise you should qualify the Range() calls with a specific worksheet reference.
All your "change" handlers should follow a similar pattern.

Tim apologies. I am new to this and was anxious to get a solution. Thank you for your response. Advice Noted. T
When I attempt to insert or delete a row in the spreadsheet, the VBA code identifies a worksheet event and attempts to run the code. The spreadsheet crashes. I have attempted to add code that will prevent this by checking at the beginning of the module if a row has been inserted or deleted before the other worksheet change event if statements are checked
Thank you
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim aCell As Range
Dim wsInc As Worksheet
Dim count As Integer
Dim lRow As Long
Dim ans As Variant
Dim tb As ListObject
On Error GoTo Whoa
Application.EnableEvents = False
Set tb = ActiveSheet.ListObjects(1)
MsgBox Target.Rows.count
If tb.Range.Cells.count > count Then
count = tb.Range.Cells.count
' GoTo Whoa
ElseIf tb.Range.Cells.count < count Then
count = tb.Range.Cells.count
' GoTo Whoa
'~~> Check if the change happened in Col A
ElseIf Not Intersect(Target, Columns(1)) Is Nothing Then
For Each aCell In Target.Cells
With aCell
If Len(Trim(.Value)) = 0 Then
.Offset(, 1).ClearContents
Else
.Offset(, 1).NumberFormat = "dd/mm/yyyy"
.Offset(, 1).Value = Now
With .Interior
.Pattern = xlNone
.TintAndShade = 0
.PatternTintAndShade = 0
End With
End If
End With
Next
'~~> Check if the change happened in Col L
ElseIf Not Intersect(Target, Columns(12)) Is Nothing Then
Set wsInc = Sheets("Income")
lRow = wsInc.Range("A" & wsInc.Rows.count).End(xlUp).Row + 1
For Each aCell In Target.Cells
With aCell
If Len(Trim(.Value)) = 0 Then
.Offset(, 1).ClearContents
Else
.Offset(, 1).NumberFormat = "dd/mm/yyyy"
.Offset(, 1).Value = Now
With .Interior
.Pattern = xlNone
.TintAndShade = 0
.PatternTintAndShade = 0
End With
'~~> Check of the value is Fees Received, Policy No. Issued
If .Value = "Fees Received" Or .Value = "Policy No. Issued" Then
ans = MsgBox("Do you want to copy this client to the Income Worksheet?", vbQuestion + vbYesNo)
If ans = False Then Exit For
wsInc.Range("A" & lRow).Value = Range("A" & aCell.Row).Value
End If
End If
End With
Next
End If
Letscontinue:
Application.EnableEvents = True
Exit Sub
Whoa:
MsgBox Err.Description
Resume Letscontinue
End Sub

Related

Worksheet_Change handling different actions for different columns of a worksheet not looping correctly

In The following worksheet macro, I am attempting to perform different actions, depending on the column selected. In 2 cases the action performed depends on the Column selected and the column value.
For example, if a name is entered in column A, the date is automatically entered in column B.
When a drop down value is entered in Column L, date is entered in Column M. If data in column L = "Fees Received" or "Policy No. Issued" data is copied to another worksheet and the date is entered in column m.
All individual components are working. However not all the time.
I need the macro to identify the column and perform the correct action such that I can move from column to column and the macro to constantly run in the background and working correctly for all selected columns.
Private Sub Worksheet_Change(ByVal Target As Range)
'Dim C As Range, V
Dim answer As Integer
Dim LRowCompleted As Integer
Application.EnableEvents = False
MsgBox "Target Column is " & Target.Column
MsgBox "Target Value is " & Target.Value
If Target.Column = 1 Then
GoTo AddEntryDate
End If
If Target.Column = 12 Then
GoTo AddWorkStatusDate
End If
If (Target.Column = 12 And Target.Value = "Fees Received") Then
GoTo FeesReceived
End If
If (Target.Column = 12 And Target.Value = "Policy No. Issued") Then
GoTo PolicyNoIssued
End If
Exit Sub
AddEntryDate:
'Update on 11/11/2019 -If data changes in column L Activity , insert
'today's date into column M - Date of Activity
Dim WorkRng As Range
Dim rng As Range
Dim xOffsetColumn As Integer
Set WorkRng = Intersect(Application.ActiveSheet.Range("A:A"), Target)
xOffsetColumn = 1
If Not WorkRng Is Nothing Then
Application.EnableEvents = False
For Each rng In WorkRng
If Not VBA.IsEmpty(rng.Value) Then
rng.Offset(0, xOffsetColumn).Value = Now
rng.Offset(0, xOffsetColumn).NumberFormat = "dd/mm/yyyy"
rng.Offset(3, xOffsetColumn).Select
With Selection.Interior
.Pattern = xlNone
.TintAndShade = 0
.PatternTintAndShade = 0
End With
Else
rng.Offset(0, xOffsetColumn).ClearContents
End If
Next
Application.EnableEvents = True
End If
Exit Sub
AddWorkStatusDate:
'Update on 11/11/2019 -If data changes in column L Activity , insert
'today's date into column M - Date of Activity
Dim WorkRng2 As Range
Dim rng2 As Range
Dim yOffsetColumn As Integer
Set WorkRng2 = Intersect(Application.ActiveSheet.Range("L:L"), Target)
yOffsetColumn = 1
If Not WorkRng2 Is Nothing Then
Application.EnableEvents = False
For Each rng2 In WorkRng2
If Not VBA.IsEmpty(rng2.Value) Then
rng2.Offset(0, yOffsetColumn).Value = Now
rng2.Offset(0, yOffsetColumn).NumberFormat = "dd/mm/yyyy"
Else
rng2.Offset(0, yOffsetColumn).ClearContents
End If
Next
Application.EnableEvents = True
End If
Exit Sub
PolicyNoIssued:
Sheets("Income").Select
LRowCompleted = Sheets("Income").Cells(Rows.Count, "A").End(xlUp).Row '
'Request confirmation from the user, in form of yes or no
answer = MsgBox("Do you want to copy this client to the Income Worksheet?", vbQuestion + vbYesNo)
If answer = vbYes Then
Range("A" & Target.Row & ":A" & Target.Row).Copy
Sheets("Income").Range("A" & Rows.Count).End(xlUp).Offset(1).Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
Application.EnableEvents = True
Else
MsgBox "This client will not be copied to the Income Worksheet"
Application.EnableEvents = True
End If
Exit Sub
FeesReceived:
'Define last row on Income worksheet to know where to place the row of data
Sheets("Income").Select
LRowCompleted = Sheets("Income").Cells(Rows.Count, "A").End(xlUp).Row
'Request confirmation from the user, in form of yes or no
answer = MsgBox("Do you want to copy this client to the Income Worksheet?", vbQuestion + vbYesNo)
If answer = vbYes Then
Range("A" & Target.Row & ":A" & Target.Row).Copy
Sheets("Income").Range("A" & Rows.Count).End(xlUp).Offset(1).Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
Application.EnableEvents = True
Else
MsgBox "This client will not be copied to the Income Worksheet"
Application.EnableEvents = True
End If
Application.EnableEvents = True
End Sub
From what I can see, you need to monitor only 2 columns. Rest of your requirements is just subsets of those requirements.
Your code can be re-written as below (UNTESTED) Let me know if you get any error? Also since you are working with Worksheet_Change, you may want to see THIS.
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim aCell As Range
Dim wsInc As Worksheet
Dim lRow As Long
Dim ans As Variant
On Error GoTo Whoa
Application.EnableEvents = False
'~~> Check if the change happened in Col A
If Not Intersect(Target, Columns(1)) Is Nothing Then
For Each aCell In Target.Cells
With aCell
If Len(Trim(.Value)) = 0 Then
.Offset(, 1).ClearContents
Else
.Offset(, 1).NumberFormat = "dd/mm/yyyy"
.Offset(, 1).Value = Now
With .Interior
.Pattern = xlNone
.TintAndShade = 0
.PatternTintAndShade = 0
End With
End If
End With
Next
'~~> Check if the change happened in Col L
ElseIf Not Intersect(Target, Columns(12)) Is Nothing Then
Set wsInc = Sheets("Income")
lRow = wsInc.Range("A" & wsInc.Rows.Count).End(xlUp).Row + 1
For Each aCell In Target.Cells
With aCell
If Len(Trim(.Value)) = 0 Then
.Offset(, 1).ClearContents
Else
.Offset(, 1).NumberFormat = "dd/mm/yyyy"
.Offset(, 1).Value = Now
With .Interior
.Pattern = xlNone
.TintAndShade = 0
.PatternTintAndShade = 0
End With
'~~> Check of the value is Fees Received, Policy No. Issued
If .Value = "Fees Received" Or .Value = "Policy No. Issued" Then
ans = MsgBox("Do you want to copy this client to the Income Worksheet?", vbQuestion + vbYesNo)
If ans = False Then Exit For
wsInc.Range("A" & lRow).Value = Range("A" & aCell.Row).Value
End If
End If
End With
Next
End If
Letscontinue:
Application.EnableEvents = True
Exit Sub
Whoa:
MsgBox Err.Description
Resume Letscontinue
End Sub

Automatically adding a month-summery row at the end of each month?

I'm using an excel sheet to track my working hours. Each row represents a working day and has "date", "time in", "time out", "total working hours", and "daily salary".
I'd like the sheet to automatically create a month summery line every time I write an entry in the next month (i.e if a row with the date 13/3/16 is followed by a row with the date 2/4/16, the second row will be pushed down, and a row with the summery (i.e: total monthly hours, total monthly salary) will be created in between. it should look something like that:
Is that possible? if so, how do I do it?
Thank you for your input!
You should place this code in Sheet Code Module. It worked for me on data organized like your.
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim ThisSheet As Worksheet
Dim NewRow As Long
Dim OldTotalRange As Range
Dim OldTotalRow As Long
Set ThisSheet = ActiveSheet
'If not single cell is changed, exit sub
If Target.Cells.Count = 1 Then
'Disable events for prevent recursion
Application.EnableEvents = False
If Target.Column = 1 And Target.Row <> 1 And Target.value <> "" Then
If IsDate(Target.value) And IsDate(Target.Offset(-1, 0).value) Then
If Month(Target.value) <> Month(Target.Offset(-1, 0).value) Then
With ThisSheet
NewRow = Target.Row
On Error Resume Next
Set OldTotalRange = .Columns(1).Find(What:="Total", After:=Target, SearchDirection:=xlPrevious)
OldTotalRow = OldTotalRange.Row
'It's for first 'Total' when there isn't 'totals' before.
If OldTotalRow = 0 Then
OldTotalRow = 1
End If
.Rows(NewRow).Insert
.Cells(NewRow, 1) = "Total"
.Cells(NewRow, 4).FormulaR1C1 = "=SUM(R[-" & NewRow - OldTotalRow - 1 & "]C:R[-1]C)"
.Cells(NewRow, 5).FormulaR1C1 = "=SUM(R[-" & NewRow - OldTotalRow - 1 & "]C:R[-1]C)"
'It's formatting, you can delete it or change
.Range(.Cells(NewRow, 1), .Cells(NewRow, 5)).Interior.Color = RGB(196, 215, 155)
.Range(.Cells(NewRow, 1), .Cells(NewRow, 5)).Font.Bold = True
End With
End If
End If
End If
Else
Exit Sub
End If
'Enable events
Application.EnableEvents = True
End Sub

Cancel macro 1 if macro 2 is running

I have two macros altogether, one macro assigned to my Private Worksheet_Change event and the other assigned to my Private Worksheet_SelectionChange event like so:
Macro 1
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
'If Hours Column Selected
If Not Intersect(Target, Range("Z" & ActiveCell.Row)) Is Nothing And Range("Z" & ActiveCell.Row).Value <> "" Then
NewValue = Application.InputBox("Please Enter Your Delegated Reference:")
If NewValue <> vbNullString Then
Dim rw2 As Long, cell2 As Range
rw2 = ActiveCell.Row
With Worksheets("Data").Columns("I:I")
Set cell2 = .find(What:=NewValue, LookIn:=xlFormulas, _
LookAt:=xlWhole, MatchCase:=False, SearchFormat:=False)
If Not cell2 Is Nothing Then
Application.DisplayAlerts = False
cell2.Offset(0, 4).Value = Sheet1.Range("Y" & ActiveCell.Row).Value
cell2.Offset(0, 5).Value = Sheet1.Range("H" & ActiveCell.Row).Value
cell2.Offset(0, 6).Value = Sheet1.Range("I" & ActiveCell.Row).Value
MsgBox "Found"
Sheet1.Range("Y" & ActiveCell.Row).Value = cell2.Offset(0, 1).Value
Sheet1.Range("H" & ActiveCell.Row).Value = cell2.Offset(0, 2).Value
Sheet1.Range("I" & ActiveCell.Row).Value = cell2.Offset(0, 3).Value
Application.DisplayAlerts = True
Else
MsgBox "Not Found"
Sheet1.Range("A5").Select
End If
End With
Else
If NewValue = vbNullString Then
MsgBox "Not Found"
Sheet1.Range("A5").Select
End If
End If
End If
End Sub
Macro 2
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Range("Y" & ActiveCell.Row)) Is Nothing Then
myValue3 = MsgBox("This is a message")
End If
End Sub
The problem I have is when I click on my active cell row in column Z I am running macro 1 and asking it to update the value of my active cell row in column Y. However when the information in column Y is updated, it is causing macro 2 to run where I get a msgbox displaying and I don't want this to happen.
Whilst I still require macro 2 and do want the msgbox to display, I only want it to display when I click on the cell in column Y. So in other words, I want to be able to cancel out macro 2 if macro 1 is running.
I have tried using application.displayevents = false in macro 1 but this doesn't work.
Please can someone show me the best way to do this?
you can use
Application.EnableEvents = False
.. at the start of your macro1 to disable events and
Application.EnableEvents = True
To turn it back on again at the end of macro1.
Please try the following:
[1] Add a new Module to your VBA Project, with the following:
Public EventRunning as Boolean
[2] Modify your Worksheet_SelectionChange macro as follows:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
EventRunning=True
...
EventRunning=False
End Sub
[3] Modify your Worksheet_Change as follows:
Private Sub Worksheet_Change(ByVal Target As Range)
If EventRunning Then Exit Sub
...
Best wishes Ben

Excel VBA Error in comparing two cells for dates

In my sheet columns B:C allow dates. I'm trying to create a check to see whether a date entered in C is more recent than B, if so fine, else alert the user and clear contents.
My code returns a run-time error 91 in the application.intersect line:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim Dates As Range
Set Dates = Range("C4:C12")
If Target.Cells.Count > 1 Or IsEmpty(Target) Then
Exit Sub
End If
If Not Application.Intersect(Dates, Range(Target.Address)).Value > ActiveCell.Offset(0, -1).Value Then
GoTo DatesMissMatch
Else
Exit Sub
End If
DatesMissMatch:
Target.ClearContents
ActiveCell.Value = "A2"
MsgBox "Please re-check dates"
End Sub
I changed your method, but this seems to be working.
I also noticed that you were writing A2 to ActiveCell instead of Target. Did you want the cell in column C to update if invalid data is entered or did you intend for it to be whichever cell you move to that gets changed?
At any rate, here's a way I came up with it
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Cells.Count > 1 Or IsEmpty(Target) Then
Exit Sub
End If
If Target.Column = 3 Then 'Check to see if column C was modified
If Target.Value < Target.Offset(0, -1).Value Then
Target.ClearContents
Target.Value = "A2"
MsgBox "Please re-check dates"
End If
End If
End Sub
If you want to stick with the way you are currently doing it, then I think you need to check that the Intersection is not empty as another answer concludes.
I believe you just have to check the intersect than do the compare.
Sub Worksheet_Change(ByVal Target As Range)
Dim Dates As Range
Set Dates = Range("C4:C12")
If Target.Cells.Count > 1 Or IsEmpty(Target) Then
Exit Sub
End If
If Not Application.Intersect(Dates, Range(Target.Address)) Is Nothing Then
If Target.Value < Target.Offset(0, -1).Value Then
GoTo DatesMissMatch
Else
Exit Sub
End If
End If
DatesMissMatch:
Target.ClearContents
ActiveCell.Value = "A2"
MsgBox "Please re-check dates"
End Sub
You can just loop the rows and compare the dates.
Dim ws As Excel.Worksheet
Set ws = Application.ActiveSheet
Dim lRow As Long
lRow = 4
Do While lRow <= ws.UsedRange.Rows.count
If ws.Range("C" & lRow).Value > ws.Range("B" & lRow).Value then
GoTo DatesMissMatch
End if
lRow = lRow + 1
Loop

timestamp once all cells are filled in

Here is the situation
Columns A through G are designed as drop down lists (data validation): Name, Number, ID, Phone, etc. Upon arrival to the office, each employee must fill their information into each cell of the row, in columns A to G.
What I want from a VBA code:
Only when each cell is filled in A:G, the date and time is stamped in the corresponding cell, in column H. It is permanent. It doesn't change ever. And once the date is stamped, the cells Columns A:G are locked as well.
My coding so far:
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Column = 1 Then
Target.Offset(0,1) = Now
End If
End Sub
This timestamp only works when cells in column A are changed :(
Should I be using a "case select" statement?
Is this what you are trying? (TRIED AND TESTED)
Option Explicit
'~~> Change this to the relevant password
Const MYPASSWORD As String = "BlahBlah"
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Cells.CountLarge > 1 Then Exit Sub
On Error GoTo Whoa
Application.EnableEvents = False
Dim rng As Range
Dim nRow As Long
nRow = Target.Row
Set rng = Range("A" & nRow & ":G" & nRow)
'~~> Check if all cell from A-G are filled and
'~~> There is no time stamp already there
If Application.WorksheetFunction.CountA(rng) = 7 _
And Len(Trim(Range("H" & nRow).Value)) = 0 Then
ActiveSheet.Unprotect MYPASSWORD
Range("H" & nRow).Value = Now
Range("A" & nRow & ":H" & nRow).Locked = True
ActiveSheet.Protect MYPASSWORD
End If
Letscontinue:
Application.EnableEvents = True
Exit Sub
Whoa:
MsgBox Err.Description
Resume Letscontinue
End Sub

Resources