How do I fire an Excel VBA Macro whenever a cell value is updated? - vba

I have a Sub that I would like to run whenever cells are updated to contain a certain value.
Right now I'm using code like the following:
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
If Target.Cells.Count = 1 Then
If Target.Value = XYZ Then
my_sub a, b, c
End If
End If
End Sub
The issue right now is that the macro only fires when I edit these cells directly, not when changes in other cells force these cells to change.
Additionally, these cells are not well defined, so I can not hard code "when A5 changes", for example. I need this to fire every time any cell in my workbook is updated (manually or through formulas) to meet my condition.

Provided your target is only a single cell with a formula that needs to be monitored, this will work:
Option Explicit
Dim tarVal As Variant
Private Sub Worksheet_Activate()
tarVal = ActiveSheet.Range("A1").Value ' change range parameter to the address of the target formula
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
Dim tempVal As Variant
tempVal = ActiveSheet.Range("A1").Value
If tempVal <> tarVal Then
tarVal = tempVal
' your code here
MsgBox "The value of A1 has changed" ' for testing purposes only, delete later
End If
End Sub
Edit
The following code works for an entire range of cells, but only if automatic calculation is turned on. In case the monitored cells are non-contiguous, just use union statements when defining the target range. (The target range is A1:A10 in this example). This is under the assumption that only one of formulas in the target range can change its value at a time. If multiple target formulas can do that, then remove Exit for in the Worksheet_Change subroutine.
Option Explicit
Dim tarCellCount As Long
Dim tarRng As Range
Dim tarVals As Variant
Private Sub Worksheet_Activate()
Dim i As Long
Dim cll As Range
Set tarRng = ActiveSheet.Range("A1:A10") ' change range parameter to the addresses of the target formulas
tarCellCount = tarRng.Cells.count
ReDim tarVals(1 To tarCellCount) As Variant
For Each cll In tarRng
i = i + 1
tarVals(i) = cll.Value
Next cll
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
Dim changeBool As Boolean
Dim i As Long
Dim cll As Range
Dim tempVal As Variant
For Each cll In tarRng
tempVal = cll.Value
i = i + 1
If tempVal <> tarVals(i) Then
tarVals(i) = tempVal
changeBool = True
Exit For
End If
Next cll
If changeBool Then
' your code here
MsgBox "The value of one of the cells in the target range has changed" ' for testing purposes only, delete later
End If
End Sub

Add your cells to be tracked to a named formula (named range). I used rngValue
Use a static variable to track how many times the value you want to track occurs in this range
Use the Calculate event to check if the number of occurences changes
code
Private Sub Worksheet_Calculate()
Dim StrIn As String
Static lngCnt As Long
Dim lngCnt2 As Long
StrIn = "apples"
lngCnt2 = Application.WorksheetFunction.CountIf(Range("rngValue"), StrIn)
If lngCnt2 <> lngCnt Then
lngCnt = lngCnt2
Call mysub
End If
End Sub

Target is a range that CAN contain more cells. This code should work for you.
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
For Each cell In Target.Cells
If cell.Value = XYZ Then
my_sub a, b, c
End If
Next cell
End Sub
Edit: I see that you want to fire that also when formula is updated defined value. It can be slow if you will check every cell, but really depends on the size of your file. Here is some code to give you idea how to do it.
Private Sub Workbook_SheetCalculate(ByVal sh As Object)
For Each cell In sh.Cells.SpecialCells(xlCellTypeFormulas).Cells
If cell.Value = XYZ Then
my_sub a, b, c
End If
Next cell
End Sub

Related

Unable to Activate Macro when Active Cell Changes Through formula

My aim is to trigger the advanced filter macro when cell B2 changes (a part of the filtering criteria). B2 is linked to another cell(in another worksheet) which dynamically gets data from external sources. The problem I am facing is that the macro does not activate automatically. Only when I manually change something in B2 is the macro activated. Otherwise the old criteria remains in place. A1 to G1 has the 7 categories and A2-G2 has the inputs for the filter. Only B2 changes effectively. I have not coded in VBA before so most of this code is copied from websites and modified for my workbook. Below is my code. Appreciate any help on this.
Option Explicit
'Create variable to hold values
Dim Monitored()
Sub Advanced_Filtering()
Range("A7:G730").AdvancedFilter Action:=xlFilterCopy, CriteriaRange:=Range("A1:G2"), CopyToRange:=Sheets("Sheet3").Range("L1:R1")
End Sub
Private Sub Worksheet_Activate()
Monitored = Range("B2:C2").Value 'Read in value prior to any changes
End Sub
Private Sub Worksheet_Calculate()
Dim Xrg As Range, c As Range, x As Integer
Set Xrg = Range("B2:C2")
If Not Intersect(Xrg, Range("B2:C2")) Is Nothing Then
Application.EnableEvents = False
'Compare monitored cell with initial value
x = 1
For Each c In Range("B2:C2")
If c.Value <> Monitored(x, 1) Then
Call Advanced_Filtering
Monitored(x, 1) = c.Value
End If
x = x + 1
Next c
'Reset events
Application.EnableEvents = True
End If
End Sub
Probably the easiest fix would be to place the Worksheet_Change event under the cells that generate the value on your cell B2, as changes in formula values don't trigger the Change event... or you can change it to Worksheet_Calculate event instead, this will pick up changes in formula results as below:
Option Explicit
'Create variable to hold values
Dim Monitored
Sub Advanced_Filtering()
Range("A7:G730").AdvancedFilter Action:=xlFilterCopy, CriteriaRange:=Range("A1:G2"), CopyToRange:=Sheets("Sheet3").Range("L1:R1")
End Sub
Private Sub Worksheet_Activate()
Monitored = Range("B2").Value 'Read in value prior to any changes
End Sub
Private Sub Worksheet_Calculate()
Dim Xrg As Range
Set Xrg = Range("B2")
If Not Intersect(Xrg, Range("B2")) Is Nothing Then
Application.EnableEvents = False
'Compare monitored cell with initial value
If Range("B2").Value <> Monitored Then
'Do things as a result of a change
Call Advanced_Filtering
'Reset Variable with new monitored value
Monitored = Range("B2").Value
End If
'Reset events
Application.EnableEvents = True
End If
End Sub
UPDATE:
To use a Range of cells instead of a single one, you should change the following:
Option Explicit
'Create variable to hold values
Dim Monitored()
Sub Advanced_Filtering()
Range("A7:G730").AdvancedFilter Action:=xlFilterCopy, CriteriaRange:=Range("A1:G2"), CopyToRange:=Sheets("Sheet3").Range("L1:R1")
End Sub
Private Sub Worksheet_Activate()
Monitored = Range("B2:C2").Value 'Read in value prior to any changes
End Sub
Private Sub Worksheet_Calculate()
Dim Xrg As Range, c As Range, x As Integer
Set Xrg = Range("B2:C2")
If Not Intersect(Xrg, Range("B2:C2")) Is Nothing Then
Application.EnableEvents = False
'Compare monitored cell with initial value
x = 1
For Each c In Range("B2:C2")
If c.Value <> Monitored(1, x) Then
Call Advanced_Filtering
Monitored(1, x) = c.Value
End If
x = x + 1
Next c
'Reset events
Application.EnableEvents = True
End If
End Sub

Excel VBA Worksheet Change Event assigned

Using Excel 2007, I understand that I can create worksheet_change event on the worksheet it's created.
But how do I assign a global sub change events to a newly created worksheet?
e.g.
Public Sub DataChange(ByVal Target As Range)
' this will check and see if the user or operator has change the column field
' if they fill in "X", mark the whole row to red color
' otherwise leave it black
Dim KeyCells As Range
Dim LastRow As Long
LastRow = Cells(Rows.Count, 1).END(xlUp).Row
Set KeyCells = Range("L2:L" & LastRow)
If Not Application.Intersect(KeyCells, Range(Target.Address)) _
Is Nothing Then
If Target.Value = "X" Or Target.Value = "x" Then
Target.EntireRow.Font.color = vbRed
Else
Target.EntireRow.Font.color = vbBlack
End If
End If
End Sub
Then in a separate sub procedure in Module1...
Public Sub CreateWorkSheet()
Dim ws As Worksheet
Set ws = Sheets.Add
ws.Name = "Test1"
' Here where I want to set the event but I do not know the syntax
' ws.OnChange = DataChange
Debug.Print "Done"
End Sub
I'm used to assign events on the fly when creating controls (C#/WPF/Pascal), so I figured there would be one in Excel world. Any advice or help would be greatly appreciated.
As mentioned by Jeeped, probably the easiest way would be to copy the sheet that already had the Private Sub Worksheet_Change code behind it, but there is also another way, if you place the following code under ThisWorkbook, whenever a new sheet is created it will add the desired code behind it:
Private Sub Workbook_NewSheet(ByVal Sh As Object)
Dim NewSheet As Worksheet
Set NewSheet = Sheets(ActiveSheet.Name)
Code = "Private Sub Worksheet_Change(ByVal Target As Range)" & vbCrLf
Code = Code & "MsgBox ""your code here""" & vbCrLf
Code = Code & "End Sub"
With ThisWorkbook.VBProject.VBComponents(NewSheet.Name).CodeModule
NextLine = .CountOfLines + 1
.InsertLines NextLine, Code
End With
End Sub
The drawback here is that the Trust Settings for Macros would need to be changed by clicking on the Trust access to the VBA project object model:
EDIT:
You could also copy code from one worksheet to another using a similar method:
Sub test()
Dim CodeCopy As VBIDE.CodeModule
Dim CodePaste As VBIDE.CodeModule
Dim numLines As Long
Set CodeCopy = ActiveWorkbook.VBProject.VBComponents("Sheet1").CodeModule
Set CodePaste = ActiveWorkbook.VBProject.VBComponents("Sheet2").CodeModule
numLines = CodeCopy.CountOfLines
'Use this line to erase all code that might already be in sheet2
'If CodePaste.CountOfLines > 1 Then CodePaste.DeleteLines 1, CodePaste.CountOfLines
CodePaste.AddFromString CodeCopy.Lines(1, numLines)
End Sub
I'd go for the last #Jeeped's suggestion
place this code in ThisWorkbook code pane
Option Explicit
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
DataChange Target ' this sub will be called at any change of any worksheet passing the chenged range
End Sub
then place this in the same code pane or in any other Module
Public Sub DataChange(ByVal Target As Range)
' this will check and see if the user or operator has change the column field
' if they fill in "X", mark the whole row to red color
' otherwise leave it black
Dim KeyCells As Range
Set KeyCells = Range("L2:L" & Cells(Rows.Count, 1).End(xlUp).Row)
If Not Application.Intersect(KeyCells, Target) Is Nothing Then Target.EntireRow.Font.color = IIf(UCase(Target.Value2) = "X", vbRed, vbBlack)
End Sub

Display cell content in a text box in excel using VBA

I have a range of cells with data. I want a text box to show the cell content when I click on any cell in the text box. Is this possible? Thanks
You could just use something like this:
Option Explicit
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim i As Long
Dim lRow As Long
lRow = Cells(Rows.Count, 1).End(xlUp).Rows
For i = 1 To lRow
If Cells(i, 1).Count = 1 Then
If Cells(i, 1) = "" Then
Else
If Not Intersect(Target, Cells(i, 1)) Is Nothing Then
MsgBox (i)
End If
End If
End If
Next i
End Sub
This will show the value in a message box, not a text box. Not sure why you need a text box.
i refers to the row and change the 1 in lRow = Cells(Rows.Count, 1).End(xlUp).Rows to the correct column number you are working in
Add this to the worksheet (see the black arrow):
Option Explicit
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
MsgBox Target.Value
End Sub
In general, if you want to check for a specific range, you can define the range in the event:
Option Explicit
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim rngPreselected As Range
Set rngPreselected = Range("A1:B10")
If Not Intersect(Target, rngPreselected) Is Nothing Then
MsgBox Target.Value
End If
End Sub
In this case, A1:B10 is the defined range.
That's called Event. See more about events here: http://www.cpearson.com/excel/events.aspx

Multiple cell value change to trigger macros

I have different set of values in cells G3:G4 and D15:D10000 in a single sheet. I want run two separate codes when G columns or D columns are changed. How I can identify which set of columns are changed?
out this in your worksheet code pane
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Range("G3:G4")) Is Nothing Then
'code when some cell in range "G3:G4" is changed
ElseIf Not Intersect(Target, Range("D15:D10000")) Is Nothing Then
'code when some cell in range "D15:D10000" is changed
End If
End Sub
Put the code below in your relevant worksheet, in the Worksheet_Change event.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim WatchRange As Range
Dim IntersectRange As Range
Set WatchRange = Application.Union(Range("G3:G4"), Range("D15:D10000"))
Set IntersectRange = Intersect(Target, WatchRange)
If Not IntersectRange Is Nothing Then
Select Case Target.Column
Case 4 ' column D
Call A
Case 7 ' column G
Call B
End Select
End If
End Sub
Below are examples of Sub A and Sub B:
Sub A()
MsgBox "Running Sub A"
End Sub
Sub B()
MsgBox "Running Sub B"
End Sub

How make Excel select the same cell when changing sheets?

I have Excel workbook with 3 sheets. I want to use a macro which will select the same cell when changing sheets.
Example:
I am in sheet1 cell A3 when I switch to sheet2. I want A3 in sheet2 to be selected. Same thing when I switch to sheet3.
Is it possible?
I tried using events sheet_activate, sheet_deactivate, and sheet_change. The last one is surely wrong.
You were close. This uses a module-level variable to store the ActiveCell address any time the SheetSelectionChange event fires:
Dim ActiveCellAddress As String
Private Sub Workbook_SheetActivate(ByVal Sh As Object)
Application.ScreenUpdating = False
Sh.Range(ActiveCellAddress).Activate
Application.ScreenUpdating = True
End Sub
Private Sub Workbook_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
ActiveCellAddress = ActiveCell.Address
End Sub
Here is a one-way example. If you start on Sheet1 and select either Sheet2 or Sheet3, you will stay on the same address as you were on Sheet1.
In a standard module, include the single line:
Public addy As String
In the Sheet1 code area, include the following event macro:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
addy = ActiveCell.Address
End Sub
In both the Sheet2 and Sheet3 code areas, include the following event macro:
Private Sub Worksheet_Activate()
If addy <> "" Then
Range(addy).Select
End If
End Sub
I use the following macro to select cell A1 on all sheets within a workbook. I assigned this macro to a button on a toolbar. You can modify it to make it work for when you change sheets.
Sub Select_Cell_A1_on_all_Sheets()
Application.ScreenUpdating = False
On Error Resume Next
Dim J As Integer
Dim NumSheets As Integer
Dim SheetName As String
CurrentSheetName = ActiveSheet.Name
NumSheets = Sheets.Count
For J = 1 To NumSheets
SheetName = Sheets(J).Name
Worksheets(SheetName).Activate
Range("A1").Select
Next J
Worksheets(CurrentSheetName).Activate