How to get VBA code to run on Multiple Sheets? - vba

I was wondering how to get this code to work on every sheet and new sheets that will be made in the Excel workbook. The new sheet Thank you to everyone that helps.
Dim cmt As Comment
Dim charCount As String
Dim prevTarget As Range
Sub Worksheet_C(ByVal Target As Range)
If Target.Value <> Empty Or Target.Value <> "0" Then
If Target.Value <> Empty Then
Set prevTarget = Target
Set Target = Target
End If
Set cmt = prevTarget.Comment
If Target = Empty Then
Set prevTarget = Target
End If
If cmt Is Nothing Then
'MsgBox "There is no comment"
ElseIf Len(cmt.Text) > 150 Then
charCount = Len(cmt.Text)
MsgBox "Character Limit is 150. Your comment contains " + charCount + "."
End If
End If
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Call Worksheet_C(Target)
End Sub

The Workbook_SheetSelectionChange() in ThisWorkbook witll execute for all sheets:
In ThisWorkbook module:
Option Explicit
Private Sub Workbook_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
RestrictCommentSize Target
End Sub
Module1 (a new generic VBA module) can contain your initial Sub Worksheet_C (cleaned up a bit)
Option Explicit
Public Sub RestrictCommentSize(ByVal Target As Range)
With Target
If .CountLarge = 1 Then
If Len(.Value2) <> 0 And .Value2 <> "0" And Not .Comment Is Nothing Then
If Len(.Comment.Text) > 150 Then
Dim msg As String
msg = "Your comment contains " & Len(.Comment.Text) & " characters"
MsgBox msg & vbCrLf & vbCrLf & "(more than the max of 150)"
End If
End If
End If
End With
End Sub

Worksheet_SelectionChange is a worksheet specific routine.
If new sheets are created manually, you cannot easily control the sheet's VBA content (unless you run a background process to poll the sheets and check if the code exists).
However, if the sheets are created by code, you can add code that will also create the sheet specific VBA code.

Related

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

Excel Add .xlam module inject code into new sheet

I have a module which is creating a sheet. It builds a worksheet with a series of tables.
I'd like to add a feature that uses the OnChange event for a cell to validate that the user entered a decimal. The following code does this If I can just inject into the new worksheet. That's the only thing I can't figure out.
Given 's' is the current Worksheet we've just created is there any way to inject the following code into the sheet code module of 's'?
Private Sub Worksheet_Change(ByVal Target As Range)
Const CELL_ADDRESS = "$R$4:$AQ$500"
If Not Application.Intersect(Target, Range(CELL_ADDRESS)) Is Nothing Then
If Not IsNumeric(Target.Value) Then
MsgBox "Please enter numbers only", vbCritical, "Invalid Entry"
Target.Value = vbNullString
End If
End If
End Sub
EDIT: Showing the chosen solution to the problem (chose Wedge's solution).
(Answer:) We will add a public function to the Addin which we will call from the Template and therefore all sheets created from the template.
Using a template and copying it will allow us to have custom code built into new sheets without having to change security settings.
Calling a public function allows us to make modifications to the sheet without putting the protected password in the sheet's code.
(Public Function Call inside the sheet)
Private Sub Worksheet_Change(ByVal Target As Range)
Dim wb As Workbook
Set wb = ActiveWorkbook
Dim ws As Worksheet
Set ws = wb.ActiveSheet
Application.Run "numberaddin.Validate_Input", wb, ws, Target
End Sub
(Public Function built into Addin which will be called by the sheet when a user modifies data.)
-- All this function does is make sure our cells store only numbers AS NUMBERS and with formatting. Any non-value text becomes a 0 in the cell. This works even if the user copy-pastes the data.
Public Function Validate_Input(wb As Workbook, ws As Worksheet, r As Range)
CELL_ADDRESS = Cells(1, 2).Value ''''we'll use the locked Cell B1 to specify the Validation Range
Dim rCell As Range
Dim eCell As Range
Dim numErr As Boolean
numErr = False
Set rCell = Range(CELL_ADDRESS)
If Not Application.Intersect(rCell, r) Is Nothing Then
ActiveSheet.Protect Password:="pw", UserInterfaceOnly:=True
Application.EnableEvents = False
For Each eCell In rCell.Cells
If Not eCell Is Nothing And eCell.Locked = False And Not Application.Intersect(eCell, r) Is Nothing Then
If IsNumeric(eCell.Value) = False Or IsEmpty(eCell.Value) = True Or eCell.Value <> eCell.Value + "0" Then
If Not IsNumeric(eCell.Value) Then
numErr = True
End If
eCell.Value = Val(eCell.Value)
End If
eCell.Interior.Color = RGB(255, 255, 153)
eCell.NumberFormat = "_(* #,##0_);_(* (#,##0);_(* "" - ""??_);_(#_)"
If eCell.Value > 1000000 Then
eCell.Columns.AutoFit
eCell.ColumnWidth = eCell.ColumnWidth * 1.2
End If
End If
Next eCell
Application.EnableEvents = True
ActiveSheet.Protect Password:="pw", UserInterfaceOnly:=False
End If
If numErr = True Then
MsgBox "Only numbers are allowed here.", vbCritical, "Invalid Entry"
End If
End Function
First of all you must enable the "Trust access to the VBA project object model" setting in the Trust Center.
After that you'll have to write something like this:
Sub AddModule()
Dim Module As VBComponent
Dim ModuleString As String
ModuleString = "Sub Test()" & vbCrLf & _
" Msgbox(""Test"")" & vbCrLf & _
"End Sub"
Set Module = Workbooks(2).VBProject.VBComponents.Add(vbext_ct_StdModule)
Module.CodeModule.AddFromString ModuleString
End Sub
Obviously, you will have change the workbook reference and the ModuleString. Also be careful with the trust change. It is there for a reason.
It's not exactly what you were asking, but I would think that you could just create a hidden "template" sheet with the code you want in it (there is an xlVeryHidden option you can use to keep the template sheet from being unhidden from the UI even). Then instead of creating a new worksheet, you create a copy of that "template sheet", which should copy over the sheet VBA code with it.

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.

Looping INDEX-MATCH VBA with Sub or Function not defined error

I have a looping INDEX-MATCH VBA that I am trying to debug, as it is constantly throwing a Sub or Function not defined error. With what I have already found on this site, I was able to check my references, but I still seem to be missing something ("Solver" is checked). At this point, an extra pair of eyes would be most helpful!
Private Sub Looping_Index_Match()
Application.ScreenUpdating = False
Dim rng As Range
Dim cell as range
Dim IndexRange as range
Dim MatchRange as range
Set rng = ActiveSheet.Range("D42:D241")
With Workbook("WorkCenter.xlsm").Sheets(ComboBox1.Value)
Set IndexRange=Range(.Range("M2"),.Range(“M2”).end(xlup))
Set MatchRange= Range(.Range("L2"),.Range(“L2”).end(xlup))
End With
For Each cell In rng
Cell.Offset(0,1)=Application.WorksheetFunction.Index(IndexRange,Application.WorksheetFuntion.Match(cell,Application.WorksheetFunction.Match(cell,MatchRange,0))
Next
Application.ScreenUpdating=True
End Sub
Notes: There are two workbooks involved. Data from the "Work Center" workbook column M is being retrieved and entered into the "Summary" workbook, as matched by serial numbers found in column L.
i will share a quick example i built:
i made a simple userform with 1 combobox and one command button. to the combobox i only added the name of the first sheet. the command button calls another macro stored in a separate module call Looping_Index, passing the combobox value
Private Sub CommandButton1_Click()
Call Looping_Index(UserForm1.ComboBox1.Value)
End Sub
Private Sub UserForm_Initialize()
ComboBox1.AddItem "Sheet1"
End Sub
Sub Looping_Index(hoja As String)
Sheets(hoja).Activate
Unload UserForm1
End Sub
this a simple example on how to work with userforms and passing values. hope it helps
Private Sub CommandButton1_Click()
Dim yoursheet As Worksheet
Dim yourworkbook As Workbook
Set yourworkbook = Workbooks("Book3.xlsx")
Set yoursheet = yourworkbook.Sheets(ComboBox1.Value)
With yoursheet
Set IndexRange = Range(.Range("M2"), .Range("M2").End(xlDown))
Set MatchRange = Range(.Range("L2"), .Range("L2").End(xlDown))
End With
ActiveWorkbook.ActiveSheet.Range("e42").Formula = "=index([" & yourworkbook.Name & "]" & yoursheet.Name & "!" & IndexRange.Address & ",match(d42" & ",[" & yourworkbook.Name & "]" & yoursheet.Name & "!" & MatchRange.Address & ",0))"
ActiveWorkbook.ActiveSheet.Range("e42:e241").FillDown
ActiveWorkbook.ActiveSheet.Range("e42:e241").Copy
ActiveWorkbook.ActiveSheet.Range("e42:e241").PasteSpecial xlValues
Application.CutCopyMode = False
Unload UserForm1
End Sub
Private Sub UserForm_Initialize()
ComboBox1.AddItem "Sheet2"
End Sub
This code below is working for me(same module):
Private Sub CommandButton1_Click()
Dim yoursheet As Worksheet
Set yoursheet = Sheets(ComboBox1.Value)
With yoursheet
Set IndexRange = Range(.Range("M2"), .Range("M2").End(xlDown))
Set MatchRange = Range(.Range("L2"), .Range("L2").End(xlDown))
End With
ActiveSheet.Range("e42").Formula = "=index(" & yoursheet.Name & "!" & IndexRange.Address & ",match(d42" & "," & yoursheet.Name & "!" & MatchRange.Address & ",0))"
ActiveSheet.Range("e42:e241").FillDown
Unload UserForm1
End Sub
Private Sub UserForm_Initialize()
ComboBox1.AddItem "Sheet2"
End Sub

VBA trigger macro on cell value change

This should be simple. When the value of a cell changes I want to trigger some VBA code. The cell (D3) is a calculation from two other cells =B3*C3. I have attempted 2 approaches:
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Column = 4 And Target.Row = 3 Then
MsgBox "There was a change in cell D3"
End If
End Sub
Since the cell is a calculation this is not triggered when the value changes, because the calculation remains the same. I also tried:
Private Sub Worksheet_Calculate()
MsgBox "There was a calculation"
End Sub
But I have multiple calculations on the sheet and it triggers multiple times. Is there a way I can identify which calculation changed on the calculation event? Or is there another way I can track when D3 changes?
Could you try something like this? Change the formula to =D3AlertOnChange(B3*C3).
Private D3OldVal As Variant
Public Function D3AlertOnChange(val)
If val <> D3OldVal Then MsgBox "Value changed!"
D3OldVal = val
D3AlertOnChange = val
End Function
Or try
Private Sub Worksheet_Change(ByVal Target As Range)
Dim numdependences As Integer
On Error Resume Next
HasDependents = Target.Dependents.Count
If Err = 0 Then
If InStr(Target.Dependents.Address, "$D$3") <> 0 Then
MsgBox "change"
End If
End If
On Error GoTo 0
End Sub
You need the error control in case you change a cell that has not dependents.
try this:
Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Target.Worksheet.Range("B1")) Is Nothing Then
Call macro
End If
End Sub
looks for a change in value of cell B1, then executes "macro"
If you are only looking at if the Worksheet_Change then it will count a change for anything entered even if it is the same as the previous value. To overcome this I use a Public variable to capture the starting value and compare it.
This is my code to do this. It also allows you omit parts of the worksheet or you can use it to evaluate every cell in the worksheet.
Place this code in the Worksheet.
Public TargetVal As String 'This is the value of a cell when it is selected
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Target.Cells.CountLarge > 1 Then 'If more then one cell is selected do not save TargetVal. CountLarge is used to protect from overflow if all cells are selected.
GoTo EXITNOW
Else
TargetVal = Target 'This sets the value of the TargetVal variable when a cell is selected
End If
EXITNOW:
End Sub
Sub Worksheet_Change(ByVal Target As Range)
'When a cell is modified this will evaluate if the value in the cell value has changed.
'For example if a cell is entered and enter is pressed the value is still evaluated
'We don't want to count it as a change if the value hasn't actually changed
Dim ColumnNumber As Integer
Dim RowNumber As Integer
Dim ColumnLetter As String
'---------------------
'GET CURRENT CELL INFO
'---------------------
ColumnNumber = Target.Column
RowNumber = Target.Row
ColumnLetter = Split(Target.Address, "$")(1)
'---------------------
'DEFINE NO ACTION PARAMETERS
' IF CELL CHANGED IS IN NO ACTION RANGE, EXIT CODE NOW FOR PERFORMANCE IMPROVEMENT OR TO NOT TAKE ACTION
'---------------------
If ColumnNumber <> 4 Then 'This would exempt anything not in Column 4
GoTo EXITNOW
ElseIf RowNumber <> 3 Then 'This would exempt anything not in Row 3
GoTo EXITNOW
'Add Attional ElseIf statements as needed
'ElseIf ColumnNumber > 25 Then
'GoTo EXITNOW
End If
'---------------------
'EVALUATE IF CELL VALUE HAS CHANGED
'---------------------
Debug.Print "---------------------------------------------------------"
Debug.Print "Cell: " & ColumnLetter & RowNumber & " Starting Value: " & TargetVal & " | New Value: " & Target
If Target = TargetVal Then
Debug.Print " No Change"
'CALL MACRO, FUNCTION, or ADD CODE HERE TO DO SOMETHING IF NOT CHANGED
Else
Debug.Print " Cell Value has Changed"
'CALL MACRO, FUNCTION, or ADD CODE HERE TO DO SOMETHING IF CHANGED
End If
Debug.Print "---------------------------------------------------------"
EXITNOW:
End Sub