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

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.

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.

Apply autofilter using macro after worksheet has calculated?

So I've set up this spreadsheet at work - I end up having to do a lot of fiddly Excel tasks, though I barely know any VBA - and I want a table to automatically filter itself after the worksheet has been edited. The problem is that the column which is being filtered is full of formulas, which change in response to edits made by the user, and the filter gets applied before the column has finished calculating, producing erratic results. So really what I want is to apply AutoFilter after the worksheet has been calculated, rather than after it has been edited.
The macro I have been using is:
Private Sub Worksheet_Change(ByVal Target As Range)
With ActiveWorkbook.Worksheets("Library").ListObjects("Library")
.AutoFilter.ApplyFilter
End With
End Sub
Naively changing the activating event to Worksheet_Calculate() doesn't work - it appears to be repeatedly applying the filter over and over again.
I'm sure this is a relatively simple one and I just don't know what it is I need to do. Can you guys advise?
(PS first time posting here - hope I have done everything right!)
Never mind, have solved it myself. For anyone else with the problem, I just set calculation to manual and then replaced my code with:
Private Sub Worksheet_Change(ByVal Target As Range)
Calculate
ActiveWorkbook.Worksheets("Library").ListObjects("Library").AutoFilter.ApplyFilter
End Sub

Change a date in EXCEL based on changes to other cells

I am trying to automate the updating of a status date in an Excel Worksheet based on if any changes have taken place within certain cells. In my example, I want the date in cell "S6" to equal today's date is any of the data in cells "B6:L34" have been changed/ deleted/ info added. I am not sure what VBA code to use or how. Any clues? This would be for only changes within those cells on that worksheet; not changes throughout the Workbook. Thank you.
I would look into workbook/worksheet events throughout the internet. You can create private sub functions that automatically runs whenever a change is made. It sounds like you need a private sub worksheet_selection or worksheet_change event handler. Look up something simple like "workbook_open VBA excel" and you can get A great feeler for how event handlers work.
Then I suggest you to research the INTERSECT function and play around with it. It allows you to declare a TARGET variable and range for you to manipulate, basically saying that if anything happens to that range, this is how I want to manipulate the TARGET. The TARGET is basically the cell that was being manipulated within the range.
There might be other factors - but this will start you off very quickly
This code will update the cell S6 with today's date when anything inside the range B6:L34 has been edited.
Private Sub Worksheet_Change(ByVal Target As range)
If Not Intersect(Target, Target.Worksheet.range("B6:L34")) Is Nothing Then
Sheets("Sheet1").Range("S6") = Date
End If
End Sub
EDIT:
For this to work, make sure this code is placed within the sheet that you're using (see below):

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

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

Excel VBA Autofilters inside functions

Sub TurnAutoFilterOn()
'check for filter, turn on if none exists
If Not ActiveSheet.AutoFilterMode Then
ActiveSheet.Range("A1").AutoFilter
End If
End Sub
Works well and turns on the AutoFilter.
Function Req(ByVal MCode As String) As Integer
TurnAutoFilterOn
End Function
Doesn't work.
Function Req(ByVal MCode As String) As Integer
'check for filter, turn on if none exists
If Not ActiveSheet.AutoFilterMode Then
ActiveSheet.Range("A1").AutoFilter
End If
End Function
Doesn't work.
Is excel vba autofilters supposed to be working only under SUBs and not in Functions?
The above commenters are right regarding updating the workbook (i.e. any cells) from a function invoked by a cell - it is not allowed/supported.
Excel provides a workbook reclaculation model in which it can precompute inter-cell dependencies based on the cell formulas. This allows for a (relatively) efficient propagation of changes from their original sources to the cells that depend upon them. It propagates changes repeatedly (i.e. recursively) until they've been propagated to cells that are not referenced in other formulas, when the workbook relaculation is completed. It does NOT allow cell formulas to modify any cells in the workbook; if it were to support that it would effectively invalidate (or at least dramatically weaken) the pre-computed formula-based dependency analysis, and require another calculation model (that would likely much less efficient). (Circular cell references (direct or indirectly) are also problematic for this method, which makes history functions a bit tricky.)
However, what you can do is record some data in a VBA data structure to save for later use (in the example below, the very simple public gx, but such data structure can by almost of any complexity). You can then use that recorded data after a workbook recalculation using events. The Worksheet Change Event is way to run some code after the calculation (you write subroutine Worksheet_Calculate and put it in the worksheet), at a time when it will be OK to modify the cells. There is also a Workbook_SheetCalculation which goes in the code for ThisWorkbook, which might be of interest.
In "ThisWorkbook":
Private Sub Workbook_SheetCalculate(ByVal Sh As Object)
MsgBox "gx=" & gx
Application.Worksheets("Sheet1").Range("A1") = gx
End Sub
In "Module1":
Public gx As Long
Function MyFormula(x As Long) As String
gx = x
MyFormula = "hello"
End Function
In Sheet1, cell A5:
=MyFormula(A4)
You'll get a pop up in a context where gx was set to the number in A4 (showing the storing of data from the run of the formula), and, modifying a worksheet as well. Now, modify A4 with another number and you'll see the results because changing A4 triggers a recalculation.
(Note that you may also be interested in the Workbook_SheetChange event as an alternative to the SheetCalculate event.)