'28' Out of Stack Space. Worksheet_Change and Application.ScreenUpdating - vba

thanks in advance for any clarity you can offer.
In an Excel Workbook with many modules and worksheets, at the bottom of the VBA code for SHEET2, there is this Subroutine:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim TargetCells As Range
Set TargetCells = Range("B1000:B1029")
If Not Application.Intersect(TargetCells, Range(Target.Address)) Is Nothing Then
Call SpecificSubRoutine
End If
End Sub
My understanding of this code is that it watches the entire sheet for ANY changes. If ANYTHING is changed, anywhere on the sheet, it runs the If statement. The If statement fails in the event that any of the changes made to the sheet take place outside of the specified TargetCells range, but this Sub still tries to validate the If statement EVERY time ANYTHING is changed on the sheet.
Now, you might be able to guess that my problem is some stack overflow. (Run-time error '28': Out of Stack Space)
Whenever the Worksheet_Change Sub runs, if the changes to the sheet were made inside of the TargetCells range, it calls SpecificSubRoutine which populates cells, which triggers the Worksheet_Change Sub for every time SpecificSubRoutine populates ANY cell. (SpecificSubRoutine also calls different modules, which of course populate cells, which of course trigger the Worksheet_Change Sub)
Not so good.
Also, most of the subroutines throughout the application are wrapped in Application.ScreenUpdating = False / Application.ScreenUpdating = True, which I mistakenly thought would limit the number of times Worksheet_Change is called to once, immediately after Application.ScreenUpdating = True runs.
NOTE OF IMPORTANCE: Neither SpecificSubRoutine nor any of the Subroutines called by it populate cells in the TargetCells range. I'm not quite that dim...
Here are my questions:
Is there a way to narrow the scope of what triggers the Worksheet_Change Sub, so that only changes in the TargetCells range triggers it? (instead of changes anywhere in the sheet)
Is there a way to do what I mistakenly thought that Application.ScreenUpdating would do? (make changes to the sheet all in one bulk update, as opposed to triggering a change with nearly every step)
Also, as an extra curiosity, is there a way to have Worksheet_Change watch 2 specific ranges (instead of the whole sheet?) Knowing how to do this would be paramount, and would likely solve all of the problems on this sheet.
My intuition is to add an End to the last part of SpecificSubRoutine, or to the end of any/all of the Subroutines called by it, but I'm just not sure this will circumvent the looping through Worksheet_Change multiple times, since Application.ScreenUpdating doesn't bulk update like I thought.
Ideas?

Part 1: No - the event handler responds to all changes on the sheet: any filtering in how you respond to that change must occur in the handler itself.
Part 2: answered by #simoco
Part 3 (and incorporating simoco's suggestion):
Private Sub Worksheet_Change(ByVal Target As Range)
Application.EnableEvents=False
If Not Application.Intersect(Me.Range("B1000:B1029"), Target) Is Nothing Then
Call SpecificSubRoutine
End If
If Not Application.Intersect(Me.Range("D1000:D1029"), Target) Is Nothing Then
Call SomeOtherSpecificSubRoutine
End If
Application.EnableEvents=True
End Sub

Related

How to trigger VBA Workbook_SheetCalculate Event?

I tried Workbook_SheetCalculate Event and tried to trigger it, but it did not work, although I recalculated the worksheet!
How to trigger this Event?
here is an example, in the worksheet for the event have the following code:
Private Sub Worksheet_Calculate()
MsgBox "Calculating"
End Sub
Then in the sheet, in any cell, enter =RAND()
The formula causes a recalculation and triggers the event.
Or from a standard module use the following:
Public Sub Test()
'Application.Calculate ''could use this event for the workbook
With Worksheets("Sheet5") 'sheet containing the event code
.Calculate
End With
End Sub
The key seems to be that there is something in the sheet to calculate e.g. =RAND().
I remembered from another post, at some point, a link to the following Excel’s Smart Recalculation Engine
A quick extract says:
Excel normally only calculates the minimum number of cells possible.
Excel’s smart recalculation engine normally minimises calculation
time by tracking changes and only recalculating
Cells, formulae, values or names that have changed or are flagged as needing recalculation.
Cells dependent on other cells, formulae, names or values that need recalculation.
So, if you just had constants in the sheet, even if you issue a Worksheet.Calculate the msgbox wouldn't appear. You could test this by removing the =RAND() from the sheet and just putting 1 in the cell.
If I have two sheets each with a single non-volatile formula, and this in the workbook module:
Private Sub Workbook_SheetCalculate(ByVal Sh As Object)
Debug.Print Sh.Name
End Sub
I see both sheets names on calling:
Application.CalculateFull
or:
Application.CalculateFullRebuild
but no output with:
Application.Calculate
If I add a volatile formula to one of the sheets then I get that sheet when calling Application.Calculate.
If you're still having problems then you'd need to post a few more details including your event code and what types of formulas you have on your sheets.

Run macro when cell changes pass value

I have this certain code on a worksheet event cell change. It finds certain data that I have on a Worksheet when I type an id_parameter on cell A1.
After data is found, it writes it on this worksheet.
Now I'd like to run a different macro when I change the values on column C of the data.
I'm not able to find the proper procedure. Do_something() receives the value changed in column C. I hope I explained it clearly enough.
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Range("A1")) Is Nothing Then
Call Search_data
End If
'Data is written in B2:E10
If Not Intersect(Target, Range("C2:C10")) Is Nothing Then
Call Do_something(Target.Value)
End If
End Sub
The code you have posted works as intended. The issue is most probably due to Search_Data and or Do_Something.
You change the cell A1 which then changes all cells from B2:E10. Including the changed C2:C10 so make sure that the change_event cannot be triggered again by this.
Option Explicit
Public EventsDisabled As Boolean
Private Sub Worksheet_Change(ByVal Target As Range)
If Not EventsDisabled Then
EventsDisabled = True
If Not Intersect(Target, Range("A1")) Is Nothing Then
Call search_data
End If
'Data is written in B2:E10
If Not Intersect(Target, Range("C2:C10")) Is Nothing Then
Call do_something(Target.Value)
End If
End If
End Sub
The Public variable EventsDisabled will make sure that the change event runs when a user input is given but not if a Worksheet_Change event changes the worksheet. Otherwise you will run into all sorts of weird loopings which might be infinite or at least take a long time. Your issue will most probably also be related to this. Make sure that your Do_Something sub has the right input type. You should set a few breakpoints and open the local window under:
view>local window here you can see all variables and objects and their type. You can also type:
debug.print typename(Target.value)
into the direct window and then see what type you need to type into the Do_Something signature.

Turning off a worksheet change-based event macro in Excel

I've been stuck on this for awhile. I have a macro that runs every time a change is made and creates a data table that updates after each change with information like : Old Value, New Value, Data Changes, etc.
I am trying to kind of pause the worksheet change macro when I don't want it to be run anymore. Ex: If I am plugging in blank information, I don't need to document the fact that it used to be blank, and now it has data, etc.
I made some specifications for if I deleted an entire row as shown here:
If Target.Address = Target.EntireRow.Address Then Exit Sub
but that only dealt with one case. I just now thought of a very simple solution that I will post as the answer, so I hope this will help other people as much as it helped me
It's actually a very simple solution.
To begin pick a cell that has no information in but can be clearly located. For the purpose of this lets say A1.
Now pick key words that you will use to turn the macro on and off. Ex: I just chose True and False (capitals need to be consistent so pick what you want)
At this point, go to the beginning of your worksheet change macro and type in this right after the sub starts:
Private Sub Worksheet_Change(ByVal Target As Range)
If Range("A1").Value = "False" Then
Exit Sub
End If
Now, before your macro starts, it will always verify that the cell doesn't contain False, so if you want it on, put anything else but False, and if you want it off, keep the cell as False. Hope this helps someone.
You want to disable events while ensuring that you enable them later. To do this, use an error handler to catch all errors and set the state back at the end.
Public Sub example()
On Error GoTo errHandler
Application.EnableEvents = False ' => disable events
'The code ...
errHandler:
Application.EnableEvents = True ' => enable events
End Sub

Circle Invalid-data Macro

I have a worksheet with many dependant dropdowns that are liable to having invalid data if the initial drop down is altered. I would like a simple auto-macro that would automatically run the "circle invalid data" command every time a cell change within a specified range (D8:T800) is detected.
It sounds fairly straightforward but I am not sure how to do this.
Question - as this macro will run every single time a cell is amended would this macro slow down the worksheet?
EDIT:
Also: as this might be slow,is there a way we can run this command over a selected range?
Your thoughts thanks.
Try this
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Me.Range("D8:T800")) Is Nothing Then
Me.CircleInvalid
End If
End Sub
Note that CircleInvalid applies to the whole sheet, so while this code only triggers when a cell inside D8:T800 changes, all invalid cells on the sheet will be circled
Try placing this code inside your Worksheet:
Private Sub Worksheet_Change(ByVal Target As Range)
' Check target range has changed
If (Not Intersect(Target, Range("D8:T800")) Is Nothing) Then
' Prevent recusrive looping
Application.EnableEvents = False
' Refresh validation circles
Target.Worksheet.CircleInvalid
Application.EnableEvents = True
End If
End Sub
Note that this will NOT work if the values in those cells change due to a calculation from outside the specified range.
Also, the CircleInvalid method applies to the whole worksheet.
You could try editing the code in the conditional to 'do something' if the Target is validated — this would result in you changing the format of invalid cells instead of having the red circles around them.
**PSEUDO-CODE**
For each cell in Target.Range
cell.colour = bright red
Next cell

Call a function when only a specific Excel cell changes on Formula Recalculation

As far as i know, Worksheet_Calculate is called when the value of any cell in a worksheet changes on Formula recalculation.
Is there a way so that i need a function to be called only when a specific cell changes on Formula Recalculation
To make something happen when a specific cell is changed, you need to embed the relevant selection change event within the file Worksheet_Change(byval target as Range). We can re-calculate a worksheet when your cell changes as follows:
Private Sub Worksheet_Change(byval target as range)
If target.address = Range("YourCell").Address Then Application.Calculate
End Sub
Now what you want to do is switch off calculations the rest of the time. If you only want to switch off calculations on the single sheet (and not your whole file), you will need to turn calculations off when it is activated, and on when deactivated e.g.
Private Sub Worksheet_Activate
Application.Calculation = xlCalculationManual
End Sub
Private Sub Worksheet_Deactivate
Application.Calculation = xlCalculationAutomatic
End Sub
Of course, your requirements to re-calculate may be considerably more complex than the example above. Firstly, you may open the file whilst on the sheet in question in which case you should use the Workbook_Open event to detect your sheet, and set calculations accordingly
Then you may have several cells that may require some sort of calculation. Presumably the reason you want to switch off calculations is that the file is running too slowly. If so, and you are identifying all the input cells, you could enter the outputs using code. One method would be to enter the formulas using this guide to entering formulas in Excel Visual Basic. You could then replace the formula with the calculated value e.g. Range("YourCell") = Range("YourCell").Value...so stopping the number of formulas in the sheet constantly growing
Let me see if I interpret your question correctly. You want to know if it is possible to only kickoff a macro if a particular cell (or group of cells) is changed.
The answer is Yes. To tweak Ed's code a little.
Private Sub Worksheet_Change(byval target as range)
If Not Intersect(target.address, Range("YourCells")) is Nothing Then
MyMacro()
End If
End Sub
I think your use of "function" is throwing people off. That's why Ed's answer is so elaborate.
Ok. It is possible that you stated your question correctly and you just want to gain efficiency. In that case, Ed's answer solves your immediate problem, but will cause the spreadsheet NOT to calculate automatically when you change other cells.