Unable to Activate Macro when Active Cell Changes Through formula - vba

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

Related

Worksheet Change to run macro

My workbook contains several sheets, each with multiple checkboxes. All checkboxes in all worksheets have the linked cell in row 80. In a worksheet called "Info" I am using countif to count the total number of times the text "TRUE" occurs in row(s) 80 for all worksheets. The total is in Info!B8.
I need to call a macro each time cell Info!b8 changes. So in other words; every time a checkbox is clicked, the linked cell changes, cell Info!b8 goes up or down and I need a macro to run.
This is the code I am using, but it doesn't do anything. I have researched this and from what I can tell it should work??
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Address = "$B$8" Then
Call CreateFinalWorksheet
End If
End Sub
Assuming all your CheckBoxes are of Form Controls, with a bit of altering the CheckBox Creation, you can achieve what you want without the need of LinkedCell and CountIfs etc.
Example: CreateCheckBoxes() below will create a check box for each cell in Range("D1:D5"), Name it with a prefix and the cell address, assigns the Sub CheckBoxClicked when clicked.
In Sub CheckBoxClicked(), it will go through all worksheets (except named "Info"), then increment a counter if the value of checkbox named ending D3 is 1 (ticked). After that, if threshold is met, it calls the Sub CreateFinalWorksheet().
Option Explicit
Private Const ChkBoxPrefix As String = "cbx_"
Private Const ThresholdToCreateFinalWorksheet As Long = 3
Sub CreateChkBoxes()
Dim myCBX As CheckBox, c As Range
For Each c In Range("D1:D5") 'rngCB
With c
Set myCBX = ActiveSheet.CheckBoxes.Add(Top:=.Top, Width:=.Width, Height:=.Height, Left:=.Left)
End With
With myCBX
.Name = ChkBoxPrefix & c.Address(0, 0)
.Caption = "Check Box " & c.Address(0, 0) 'strCap
.OnAction = "CheckBoxClicked" ' "CheckBox_Click"
End With
Next c
End Sub
Sub CheckBoxClicked() ' CheckBox_Click()
Dim oWS As Worksheet, lChecked As Long
On Error Resume Next ' Just in case the named CheckBox does not exist
lChecked = 0
For Each oWS In ThisWorkbook.Worksheets
If oWS.Name <> "Info" Then
' If you need to keep track of more than 1 checkbox in each worksheet, go through them
' If you need all of them to be checked before CreateFinalWorksheet, exit when a checkbox.value = 0
With oWS.CheckBoxes(ChkBoxPrefix & "D3") ' <-- Change to what you need to keep track of
lChecked = lChecked + IIf(.Value = 1, 1, 0)
End With
End If
Next
On Error GoTo 0
If lChecked >= ThresholdToCreateFinalWorksheet Then CreateFinalWorksheet
End Sub
Private Sub CreateFinalWorksheet()
Debug.Print "CreateFinalWorksheet()"
End Sub
Alternatively you put the event Sub Worksheet_Calculate() into Info module, and check if the Info!B8 is large enough to call CreateFinalWorksheet.

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

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

Use cell value as range to hide columns

I have a spreadsheet that there is a checkbox the purpose of the checkbox is to hide the name of clients in two adjacent columns. Because the spreadsheet changes from time to time the position of the columns changes thus it is currently P:Q but a year ago it was H:I.
I want to store the 'range' in a cell and reference that from my vba and get that to hide the columns. The checkbox is a simple toggle. I have tried various incarnations without success and my latest effort tells me that I have not se up the range properly. The cel I am using for teh range is F4. The code is currently:
Private Sub CheckBox2_Click()
Dim c As Range
Dim Visy As Integer
Dim My_range As String
'My_range is the range of filled rows stored as a range in cell F4
'Visy stores the state of the checkbox
If CheckBox2.Value = True Then
Visy = 1
Else
Visy = 0
End If
'Stop any use of the spread sheet and set variable initial states
Application.EnableEvents = False
My_range = Sheet9.Cells(4, 6).Value
'Hide the columns
Range(My_range).Hidden = Visy
'Sheet9.colums(My_range).Hidden = True
'Re enable application
On Error GoTo 0
Application.EnableEvents = True
End Sub
This is within a single sheet:
Sub qwerty()
My_range = Cells(4, 6).Value
Range(My_range).EntireColumn.Hidden = True
End Sub
Your Private Sub CheckBox2_Click should be in a worksheet's code sheet. I believe this is the worksheet identified by the Sheet9 worksheet .CodeName property.
A Private Sub in a worksheet codesheet does not have to explicitly reference the .Parent worksheet property on any Range object or
Range.Cells object unless you want to reference another worksheet's cells. These are bound to the cells on the worksheet whose codesheet you are on regardless of the ActiveSheet property.
Private Sub CheckBox2_Click()
Range(Cells(4, "F").Text).EntireColumn.Hidden = CBool(Me.Value)
End Sub
Do not confuse a worksheet's Private Sub behavior with a Private Sub on a module code sheet. A module codesheet should always explicitly reference the parent worksheet (and often the parent workbook) regardless of whether the Sub is Public or Private.
You have to use code in context:
Private Sub CheckBox2_Click()
Dim wsh As Worksheet
Dim sRangeName As String
'context!
Set wsh = ThisWorkbook.Worksheets("TypeNameHere")
sRangeName = wsh.Range("F4")
wsh.Range(sRangeName).EntireColumn.Hidden = CheckBox2.Value
Set wsh = Nothing
End Sub
Thanks to all who responded it helped a lot and put me on the right track. As several of you noted context is important and I was mixing private sub and sub and so had a scope problem when it came to ranges. I also from another source had the suggestion to use a named range rather than read a cell value since the columns were always adjacent. I have published the code below in case it is of value to anyone in the future.
Private Sub CheckBox2_Click()
'Requires ClientNameCol to be set to the range to be hidden
Dim Visy As Boolean
'Stop any use of the spread sheet and set variable initial states
Application.EnableEvents = False
'Check if sheet is to be hidden or not
If Worksheets("Client 16").CheckBox2.Value = True Then
Visy = True
Else
Visy = False
End If
'Hide/unhide the columns
With ThisWorkbook
.Worksheets("Client 16").Range("ClientNameCol").EntireColumn.Hidden = Visy
End With
On Error GoTo 0
Application.EnableEvents = True
End Sub

How to use VBA to execute a macro based on an IF statement?

I want to run an Excel VBA code (all it will do is delete specific cells within the same row and I've turned on relative reference so that I can apply the Excel VBA code to all rows, if there's the appropriate "X") based on whether there is an X in a certain cell.
Here's what I've tried so far (the Excel VBA code is just called "Biology"):
If Range("C22").Value = "X" Then
Call macro_Biology
End If
I should add that I'm writing this under VBA section "GetATPLabel". Like I said, total noob, but I think I'm close, so any help is appreciated.
It sounds as if it is important that the Biology (or macro_Biology) macro needs to know which row it is supposed to work on. You can pass this information across to it with a parameter. Example:
Sub start_with_this()
Dim rw As Long, lr As Long
With ActiveSheet
lr = .Cells(Rows.Count, "C").End(xlUp).Row
For rw = 2 To lr
If UCase(.Cells(rw, "C").Value) = "X" Then
Call macro_Biology(rw)
End If
Next rw
End With
End Sub
Sub macro_Biology(r As Long)
' r is the row that was passed in.
' do something with r
End Sub
After initially starting the start_with_this macro, it goes through each cell in column C from row 2 to the last row with anything in it. If it finds an X or an x (case-sensitivity is removed by forcing the cell value to upper case before comparing) then it calls the second macro, macro_Biology and tells it what row to deal with.
Lets assume that Biology() is a sub in a standard module:
Sub Biology()
MsgBox "study biology!"
End Sub
To call this as you want, run:
Sub TestIt()
If Range("C22").Value = "X" Then
Call Biology
End If
End Sub
To call Biology() automatically if the user types an X in cell C22, Insert the following event macro in the worksheet code area:
Private Sub Worksheet_Change(ByVal Target As Range)
Set intrs = Intersect(Target, Range("C22"))
If Not intrs Is Nothing Then
If intrs.Value = "X" Then
Application.EnableEvents = False
Call Biology
Application.EnableEvents = True
End If
End If
End Sub
To call Biology() automatically if a formula gives an X in cell C22, Insert the following event macro in the worksheet code area:
Private Sub Worksheet_Calculate()
If Range("C22").Value = "X" Then
Application.EnableEvents = False
Call Biology
Application.EnableEvents = True
End If
End Sub

Using textboxes within userform to define variables?

I currently run a macro to compare the most recent sheet of data to the report immediately prior and highlight changes. It works fine on its own. Now, however, we would like to be able to compare selected sheets from any time period. My idea was to pop up a simple userform with two textboxes that the user can use to specify which two reports he wants to compare. I am quite lost though with the idea of trying to declare public variables; what I've got atm is:
Option Explicit
Public shtNew As String, shtOld As String, _
TextBox1 As TextBox, TextBox2 As TextBox
Sub SComparison()
Const ID_COL As Integer = 31 'ID is in this column
Const NUM_COLS As Integer = 31 'how many columns are being compared?
Dim rwNew As Range, rwOld As Range, f As Range
Dim X As Integer, Id
shtNew = CSManager.TextBox1
shtOld = CSManager.TextBox2
'Row location of the first employee on "CurrentMaster" sheet
Set rwNew = shtNew.Rows(5)
Do While rwNew.Cells(ID_COL).Value <> ""
Id = rwNew.Cells(ID_COL).Value
Set f = shtOld.UsedRange.Columns(ID_COL).Find(Id, , xlValues, xlWhole)
If Not f Is Nothing Then
Set rwOld = f.EntireRow
For X = 1 To NUM_COLS
If rwNew.Cells(X).Value <> rwOld.Cells(X).Value Then
rwNew.Cells(X).Interior.Color = vbYellow
rwNew.Cells(33) = "UPDATE"
Else
rwNew.Cells(X).Interior.ColorIndex = xlNone
End If
Next X
End If
Set rwNew = rwNew.Offset(1, 0) 'next row to compare
Loop
Call SUpdates
End Sub
My Suggestion would be to use Comboboxes instead of TextBoxes. Create a userform with two command buttons and two comboboxes and populate the comboboxes in the UserForm_Initialize() event using this code.
Private Sub UserForm_Initialize()
Dim ws As Worksheet
For Each ws In ActiveWorkbook.Sheets
ComboBox1.AddItem ws.Name: ComboBox2.AddItem ws.Name
Next
End Sub
And then use this code in the OK button to do the comparison.
Private Sub CommandButton1_Click()
Dim shtNew As Worksheet, shtOld As Worksheet
If ComboBox1.ListIndex = -1 Then
MsgBox "Please select the first sheet"
Exit Sub
End If
If ComboBox2.ListIndex = -1 Then
MsgBox "Please select the Second sheet"
Exit Sub
End If
Set shtNew = Sheets(ComboBox1.Value)
Set shtOld = Sheets(ComboBox2.Value)
'~~> REST OF THE CODE HERE NOW TO WORK WITH THE ABOVE SHEETS
End Sub
Private Sub CommandButton2_Click()
Unload Me
End Sub
HTH
Sid
For an easy fix, couldn't you just colour (sorry, I'm English!) the worksheets that you want to refer to, then do something like:
Sub ListSheets()
'lists only non-coloured sheets in immediate window
'(could amend to add to combo boxes)
Dim w As Worksheet
'loop over worksheets in active workbook
For Each w In Worksheets
If w.Tab.Color Then
'if tab color is set, print
Debug.Print w.Name
End If
Next w
Let me know if this solves your problem.