How to end infinite "change" loop in VBA - vba

I have a problem with visual basic. I want to make a macro/function that will multiply a number I enter by 3 and give a result in the same cell. I tried something like this:
Sub Worksheet_Change(ByVal Target As Range)
If Target.Address = "$Q$21" Then
Target.Value = Target.Value * 3
End If
End Sub
but it doesn't work - I'm getting results like "xE25" because it keeps multiplying.
I'd like it to stop after first iteration or work only when I press "enter" not with every change in the cell.
It's quite easy to put a result in different cell, but it's not my point.-----Edit:
I edited "If" line to :
If (Target.Column = 5 Or Target.Column = 11 Or Target.Column = 17 Or Target.Column = 23) And (Target.Row >= 19 And Target.Row <= 24) And Target.Value <> "" Then so it would work on all cells that I need. After that, the best solution is the way given by #Chrismas007, because it doesn't prompt an error when trying to delete data in few cells at once.

With error handling to ensure .EnableEvents goes back to True:
Sub Worksheet_Change(ByVal Target As Range)
On Error GoTo CleanExit
If Target.Address = "$Q$21" Then
Application.EnableEvents = False
Target.Value = Target.Value * 3
End If
CleanExit:
Application.EnableEvents = True
On Error GoTo 0
End Sub

When a Worksheet_Change event macro changes a value, you need to set Application.EnableEvents to false or risk triggering another event and having the macro run on top of itself.
Sub Worksheet_Change(ByVal Target As Range)
If Target.Address = "$Q$21" Then
Application.EnableEvents = False
Target.Value = Target.Value * 3
Application.EnableEvents = True
End If
End Sub
While I would usually involve some error control in order that the .EnableEvents was always reset to true, the above should get you started.

You need to disable events to prevent recursion:
Sub Worksheet_Change(ByVal Target As Range)
If Target.Address = "$Q$21" Then
application.enableevents = false
Target.Value = Target.Value * 3
application.enableevents = true
End If
End Sub
Just as an alternative, you can also use your own variable rather than tinkering with EnableEvents, though it will mean your code responds twice even though it doesn't actually do anything the second time round:
Dim bSkipEvents as Boolean
Sub Worksheet_Change(ByVal Target As Range)
If bSkipEvents then exit sub
If Target.Address = "$Q$21" Then
bSkipEvents = True
Target.Value = Target.Value * 3
bSkipEvents = False
End If
End Sub
This is also the sort of approach you need with userforms and most events for activex controls on worksheets.

Related

VBA for data validation

Recently, I found a code on a book for data validation, which is:
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Address = “$A$1” Then
If Not IsNumeric(Target) Then
MsgBox “Enter a number in cell A1.”
Range(“A1”).ClearContents
Range(“A1”).Activate
End If
End If
End Sub
I would like to change it to validate my custom format in column A which is XY & 6 number (XY123456) and modified the code. But the MsgBox will pop up continuously and I cannot close it when the format is wrong. Could someone give me some advice. Thanks
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Column = 1 Then
If Left(Target.Value, 2) <> "XY" Or
Not IsNumeric(Right(Target.Value,6)) Or
Len(Target.Value) <> 8 Then
MsgBox “Wrong Format”
Target.ClearContents
Target.Activate
End If
End If
End Sub
Change your code to
Private Sub Worksheet_Change(ByVal Target As Range)
On Error GoTo EH
If Target.Column = 1 Then
If Left(Target.Value, 2) <> "XY" Or Not IsNumeric(Right(Target.Value, 6)) Or Len(Target.Value) <> 8 Then
Application.EnableEvents = False
MsgBox "Wrong Format"
Target.ClearContents
Target.Activate
End If
End If
EH:
Application.EnableEvents = True
End Sub
You need to turn off events otherwise Target.ClearContents will trigger the event again and again until you run out of stack space. In order to make it a little bit more bullet proof I also added an error handler to make sure the event handler gets turned on again in case of an error.

Keeping cell unchanged under specified conditions

I am looking for way to keep a formula in a cell every time another cell is active.
Private Sub Worksheet_Change(ByVal Target As Range)
If ActiveSheet.Range("AL2").Value = 1 Then
ActiveSheet.Range("AK14").Value = ActiveSheet.Range("AL8").Value
Else
End If
End Sub
So if Cell AL2 is equal to 1 (so my desired active cell) I want to have a certain value in cell AK14.
If cell AL2 is NOT equal to 1 I want to just keep value in AK14 unchanged (so for example somebody can overwrite it).
At the moment Excel seems to get lost with the second part: if AL2 = 0 and I am getting an error.
If I need two conditions, do I just put another If?
Private Sub Worksheet_Change(ByVal Target As Range)
Application.EnableEvents = False
If Range("AL2").Value = 1 Then Range("F11").Value = Range("AK7").Value
Application.EnableEvents = True
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
Application.EnableEvents = False
If Range("AL2").Value = 2 Then Range("J11").Value = Range("AL7").Value
Application.EnableEvents = True
End Sub
so I want to have those two macros..
When you change value in a cell, in a Worksheet_Change event, you should disable the events. Otherwise, it starts calling itself:
Private Sub Worksheet_Change(ByVal Target As Range)
Application.EnableEvents = False
If Range("AL2").Value = 1 Then Range("AK14").Value = Range("AL8").Value
Application.EnableEvents = True
End Sub
Then, as a next step it is really a good practice to use Error-catcher here with the .EnableEvents:
Private Sub Worksheet_Change(ByVal Target As Range)
On Error GoTo Worksheet_Change_Error
Application.EnableEvents = False
If Range("AL2").Value = 1 Then Range("AK14").Value = Range("AL8").Value
Application.EnableEvents = True
On Error GoTo 0
Exit Sub
Worksheet_Change_Error:
Debug.Print "Error " & Err.Number & " (" & Err.Description & ") "
Application.EnableEvents = True
End Sub

Excel sheet: Lock cell and write "1" on it based on a dropdown option

I want to lock cell B20 and write "1" on it if I choose a certain option ("T") on the dropdown of B18. If I choose any other option, I want to be able to fill it normally without any limitations. Here is the best code I tried:
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim ws As Worksheet
Set ws = ActiveSheet
If Target.Address = "$B$18" Then
ws.Unprotect
Application.EnableEvents = False
Target.Offset(1).Locked = Target.Value = "T"
If Target.Value = "T" Then
Target.Offset(1).Value = 1
Else
If Target.Offset(1).Value = 1 Then Target.Offset.Value = ""
End If
Application.EnableEvents = True
ws.Protect
End If
End Sub
This code is doing exactly what I want but its performing this on the cell B19 instead of B20.
Can somebody help me please?
There is a bunch of code and text all saying different things in your question, so I based this code on your comment and the text of your question.
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Address = "$B$18" Then
ws.Unprotect
Application.EnableEvents = False
Target.Offset(2).Locked = Target.Value = "T"
If Target.Value = "T" Then
Target.Offset(2).Value = 1
Else
If Target.Offset(2).Value = 1 Then Target.Offset.Value = ""
End If
Application.EnableEvents = True
ws.Protect
End If
End Sub

Excel VBA Issue

Using the following code to auto upper two columns,
Private Sub Worksheet_Change(ByVal Target As Range)
If Not (Application.Intersect(Target, Range("C2:C5000", "P3:P5000")) _
Is Nothing) Then
With Target
Application.EnableEvents = False
.Value = UCase(.Value)
Application.EnableEvents = True
End With
End If
End Sub
Works perfectly, the problem is, if a user selects multiple cells, and hits delete, it errors, then the user hits End and the function no longer works. protected. Run-time error 13, type mismatch.
Doesn't matter if the cell is empty or not, still get the error.
Thanks in advance.
The answer of #ScottHoltzman solves the issue of the current problem, where an error is raised when you apply UCASE to an Array. When the Target range has more than one cell its .Value is an array, and UCase does not accept an array parameter.
Your routine will exit this line (.Value = UCase(.Value)) and will miss the next line that resets Application.EnableEvents = True. After that, you end up working with events disabled, so all your event handling routines will stop working, not only this one (in case you had other such routines).
To avoid these situations the good approach is to implement proper error handling in event handlers, following this structure
Sub my_Handler()
On Error Goto Cleanup
Application.EnableEvents = False: Application.ScreenUpdating = False ' etc..
''''''''''''''''''''''''''''''''''
'
' normal code of the routine here
'
''''''''''''''''''''''''''''''''''
Cleanup:
if Err.Number <> 0 Then MsgBox Err.Description
Application.EnableEvents = True, Application.ScreenUpdating = True ' etc..
End Sub
To apply it to your routine:
Private Sub Worksheet_Change(ByVal Target As Range)
On Error GoTo Cleanup
Application.EnableEvents = False: Application.ScreenUpdating = False ' etc..
If Not (Application.Intersect(Target, Range("C2:P5000")) Is Nothing) Then
Target.value = UCase(Target.value)
End If
Cleanup:
If Err.Number <> 0 Then msgBox Err.Description
Application.EnableEvents = True: Application.ScreenUpdating = True ' etc..
End Sub
Importantly, don't use this structure automatically for all you routines, only Event handlers or eventually macros ythat you would invoke from the GUI. Other routines are usually called from these handlers or macros, so you can write them normally.
I tried putting this in a comment to the answer, but was too long, so sorry..
#a-s-h #a.s.h
This one worked the best, with a slight modification. Thanks!
Private Sub Worksheet_Change(ByVal Target As Range)
On Error GoTo Cleanup
Application.EnableEvents = False: Application.ScreenUpdating = False ' etc..
If Not (Application.Intersect(Target, Range("C2:C5000", "P3:P5000")) Is Nothing) Then
Target.Value = UCase(Target.Value)
End If
Cleanup:
If Err.Number <> 0 Then GoTo EndLine
EndLine:
Application.EnableEvents = True: Application.ScreenUpdating = True ' etc..
End Sub
Performs uppercase, and deletes multiples at once without any errors, or MsgBox's.
If they are selecting multiple cells then my thinking is that you would want to use SelectionChange macro instead, like this
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim StartTime As Double
Dim SecondsElapsed As Double
StartTime = Timer
If ((Target.Address = Target.EntireRow.Address Or _
Target.Address = Target.EntireColumn.Address)) Then Exit Sub
Application.EnableEvents = False
If Not (Application.Intersect(Target, Range("C2:C5000", "P3:P5000")) _
Is Nothing) Then
On Error GoTo endItAll
For Each aCell In Target.Cells
Range(aCell.Address) = UCase(Range(aCell.Address))
Next aCell
End If
endItAll:
Application.EnableEvents = True
SecondsElapsed = Round(Timer - StartTime, 2)
Debug.Print SecondsElapsed
End Sub
Or you could change it back to the worksheet_Change macro like below and it will not error if the user selects multiple cells or deletes cells without causing an error. The error handler is there - Like in A.S.H. 's solution, but I haven't yet seen it needed in my testing.
Private Sub Worksheet_Change(ByVal Target As Range)
Application.EnableEvents = False
If Not (Application.Intersect(Target, Range("C1:C5000", "D1:D5000")) _
Is Nothing) Then
On Error GoTo endItAll
For Each aCell In Target.Cells
Range(aCell.Address) = UCase(Range(aCell.Address))
Next aCell
End If
endItAll:
Application.EnableEvents = True
End Sub
Account for multiple cells this way:
Private Sub Worksheet_Change(ByVal Target As Range)
If Not (Application.Intersect(Target, Range("C2:C5000", "P3:P5000")) _
Is Nothing) Then
Dim rCell as Range
Application.EnableEvents = False
For each rCell in Target
rCell.Value = UCase(rCell.Value)
Next
Application.EnableEvents = True
End If
End Sub

VBA Code. Trying to fix the "IF" PARTS

So I have this written in my VBA:
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Range("G9:G28")) Is Nothing Then
Application.EnableEvents = False
With Target
If IsNumeric(.Value) Then .Value = .Value / 100
End With
Application.EnableEvents = True
End If
End Sub
When I try to erase the data in the "G" Cells a 0% stays locked in. I think bc ".Value/100" is the 0% that my code says must go inside that cell. The above code is suppose to turn any number into a percentage but I think I wrote it and when its suppose to be blank it shows a "0%" but I want it to be blank.
I would use a loop:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim r As Range
If Not Intersect(Target, Range("G9:G28")) Is Nothing Then
Application.EnableEvents = False
For Each r In Intersect(Target, Range("G9:G28"))
If IsNumeric(r.Value) Then r.Value = r.Value / 100
Next r
Application.EnableEvents = True
End If
End Sub
This code will allow more than one cell to be changed.
Try the code below:
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Range("G9:G28")) Is Nothing Then
If Target.Count <= 1 Then ' make sure not more than 1 cell is changed
Application.EnableEvents = False
With Target
If IsNumeric(.Value) Then
If .Value = 0 Then
.Value = ""
Else
.Value = .Value / 100
End If
End If
End With
Application.EnableEvents = True
End If
End If
End Sub
Target can be SEVERAL cells.
And an array divided by 100 makes no sense.
Just test if the cell in question is empty with IsEmpty(Target.Value) before performing the conversion to percent, like this:
If Not IsEmpty(.Value) And IsNumeric(.Value) Then .Value = .Value / 100