Excel VBA Event Handlers - vba

1) Can someone please tell me what the issue could be with this code?
2) I need this code actually to run on a worksheet update, but # first-time load of the workbook, i'm running an update using the Workbook_Open event handler. Won't this trigger my Worksheet_Change event as well? Is there any way to avoid this
Private Sub Worksheet_Change(ByVal Target As Range)
Dim ws As Worksheet
Dim rowCount As Integer
Set Worksheet = "datasheet"
Set rowCount = ws.Cells(Rows.Count, "A").End(xlUp)
If Not Intersect(Target, Range("M3:M" & CStr(rowCount))) Is Nothing Then
MsgBox ("Hi")
End If
Else
If Not Intersect(Target, Range("T3:T" & CStr(rowCount))) Is Nothing Then
MsgBox ("Hi")
End If
Else
If Not Intersect(Target, Range("X3:X" & CStr(rowCount))) Is Nothing Then
MsgBox ("Hi")
End If
Else
If Not Intersect(Target, Range("AB3:AB" & CStr(rowCount))) Is Nothing Then
MsgBox ("Hi")
End If
Else
If Not Intersect(Target, Range("AI3:AI" & CStr(rowCount))) Is Nothing Then
MsgBox ("Hi")
End If
End Sub
I'm getting a Compiler error saying "object required" when i change my data with this handler. On the other hand, if i give values instead of taking a rowcount, I get no issues.

I always recommend this when using Worksheet_Change
You do not need the sheet name. It is understood that the code is to be run on current sheet unless you are trying to use another sheet row as a reference as correctly mentioned by brettdj in the comments below.
Whenever you are working with Worksheet_Change event. Always switch Off events if you are writing data to the cell. This is required so that the code doesn't go into a possible endless loop
Whenever you are switching off events, use error handling else if you get an error, the code will not run the next time.
Here is an example
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
On Error GoTo Whoa
Application.EnableEvents = False
'
'~~> Rest of the code
'
LetsContinue:
Application.EnableEvents = True
Exit Sub
Whoa:
MsgBox Err.Description
Resume LetsContinue
End Sub
So using the above, your code becomes (UNTESTED)
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rowCount As Long
On Error GoTo Whoa
Application.EnableEvents = False
With ActiveSheet
rowCount = .Cells(.Rows.Count, "A").End(xlUp).Row
End With
If Not Intersect(Target, Range("M3:M" & rowCount)) Is Nothing Then
MsgBox ("Hi")
ElseIf Not Intersect(Target, Range("T3:T" & rowCount)) Is Nothing Then
MsgBox ("Hi")
ElseIf Not Intersect(Target, Range("X3:X" & rowCount)) Is Nothing Then
MsgBox ("Hi")
ElseIf Not Intersect(Target, Range("AB3:AB" & rowCount)) Is Nothing Then
MsgBox ("Hi")
ElseIf Not Intersect(Target, Range("AI3:AI" & rowCount)) Is Nothing Then
MsgBox ("Hi")
End If
LetsContinue:
Application.EnableEvents = True
Exit Sub
Whoa:
MsgBox Err.Description
Resume LetsContinue
End Sub
EDIT:
Regarding your 2nd question. As I mentioned in the comment above, you can use a Public variable to check if the worksheet change event is being caused by the workbook open.
Place this code in a module.
Public WasWbOpened As Boolean
Place this code in the workbook code area
Option Explicit
Private Sub Workbook_Open()
WasWbOpened = True
'
'~~> Rest of the code
'
WasWbOpened = False
End Sub
And change your worksheet change event to
Private Sub Worksheet_Change(ByVal Target As Range)
If WasWbOpened = True Then Exit Sub
Dim rowCount As Long
On Error GoTo Whoa
Application.EnableEvents = False
With ActiveSheet
rowCount = .Cells(.Rows.Count, "A").End(xlUp).Row
End With
If Not Intersect(Target, Range("M3:M" & rowCount)) Is Nothing Then
MsgBox ("Hi")
ElseIf Not Intersect(Target, Range("T3:T" & rowCount)) Is Nothing Then
MsgBox ("Hi")
ElseIf Not Intersect(Target, Range("X3:X" & rowCount)) Is Nothing Then
MsgBox ("Hi")
ElseIf Not Intersect(Target, Range("AB3:AB" & rowCount)) Is Nothing Then
MsgBox ("Hi")
ElseIf Not Intersect(Target, Range("AI3:AI" & rowCount)) Is Nothing Then
MsgBox ("Hi")
End If
LetsContinue:
Application.EnableEvents = True
Exit Sub
Whoa:
MsgBox Err.Description
Resume LetsContinue
End Sub

You have several issues
As per Sid's comment you should use rowCount = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
You should be using Set ws = Sheets("datasheet") not Set Worksheet = "datasheet"
Your Else statements are causing errors as they are orphaned. If you want to exit on the first "Hi" rather than continuing to test you should try something like this
Sid has covered your other point. You can use a Boolean variable for this
updated code
this tests the intersect on columns M, T, X, AB and AI from row 3 to row rowCount in a single line
Private Sub Worksheet_Change(ByVal Target As Range)
Dim ws As Worksheet
Dim rowCount As Long
Set ws = Sheets("datasheet")
rowCount = ws.Cells(Rows.Count, "A").End(xlUp).Row
If Not Intersect(Target, Range("M3:M" & CStr(rowCount) & ",T3:T" & CStr(rowCount) & ",X3:X" & CStr(rowCount) & ",AB3:AB" & CStr(rowCount) & ",AI3:AI" & CStr(rowCount))) Is Nothing Then MsgBox ("Hi")
End Sub

Related

Excel VBA Clearing range after copy and pasting range to another sheet

I'm using a code where the workbook detects if the current month has a sheet assigned to it or not and if not then the workbook will create a new sheet with the current month. After creating a new sheet it would copy and paste a certain range from the main sheet onto the new one. My problem is that after doing so I use a Range.Clear to clean the range that I copy pasted however it seems to be clearing it BEFORE copy-pasting.
Private Sub Worksheet_Change(ByVal Target As Range)
nowMonth = Month(Now)
nowYear = Year(Now)
sheetNameStr = nowMonth & "," & nowYear
sheetExists = False
For Each Sheet In Worksheets
If sheetNameStr = Sheet.Name Then
sheetExists = True
End If
Next Sheet
If sheetExists = False Then
Set ws = ThisWorkbook.Sheets.Add(After:=ThisWorkbook.Sheets(ThisWorkbook.Sheets.Count))
ws.Name = sheetNameStr
MsgBox ("New sheet named " & sheetNameStr & "was created")
End If
Sheets("Main").Activate
Worksheets("Main").Range("A4:D300").Copy Worksheets(sheetNameStr).Range("A1")
Worksheets("Main").Range("A6:D300").Clear
End Sub
Any help would be great thank you.
Here's what happens: the .Clear method causes Worksheet_Change to fire again; the Copy operation is repeated, clearing the destination; then the second Clear doesn't change anything, the source having been cleared already, and both Worksheet_Change procedures exit.
You have to surround your code with:
Application.EnableEvents = False
and
Application.EnableEvents = True
Here's the updated code:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim nowMonth As Integer
Dim nowYear As Integer
Dim sheetNameStr As String
Dim oSheet As Excel.Worksheet
Dim oNewSheet As Excel.Worksheet
Dim sheetExists As Boolean
On Error GoTo errHandler
Application.EnableEvents = False
nowMonth = Month(Now)
nowYear = Year(Now)
sheetNameStr = nowMonth & "," & nowYear
sheetExists = False
For Each oSheet In ThisWorkbook.Worksheets
If sheetNameStr = oSheet.Name Then
sheetExists = True
Exit For 'Found, can exit the loop.
End If
Next
If Not sheetExists Then
Set oNewSheet = ThisWorkbook.Worksheets.Add(After:=ThisWorkbook.Worksheets(ThisWorkbook.Sheets.Count))
oNewSheet.Name = sheetNameStr
MsgBox "New sheet named " & sheetNameStr & " was created."
End If
Me.Activate
Me.Range("A4:D300").Copy ThisWorkbook.Worksheets(sheetNameStr).Range("A1")
Me.Range("A6:D300").Clear
Recover:
On Error Resume Next
Set oNewSheet = Nothing
Set oSheet = Nothing
Application.EnableEvents = True
Exit Sub
errHandler:
MsgBox Err.Description, vbExclamation + vbOKOnly, "Error"
Resume Recover
End Sub
Notice that Worksheets is now qualified by ThisWorkbook; otherwise, your code would be referring to whichever workbook is active. Also, Sheets("Main") was replaced by Me as I assume your code is behind the Main worksheet and Me, from there, is the worksheet itself. Finally, whenever you turn EnableEvents off, you must provide adequate error handling to turn it back on in case of issues.
Edit
Here's the original code with just minimal changes to handle EnableEvents:
Private Sub Worksheet_Change(ByVal Target As Range)
On Error GoTo errHandler
Application.ScreenUpdating = False
nowMonth = Month(Now)
nowYear = Year(Now)
sheetNameStr = nowMonth & "," & nowYear
sheetExists = False
For Each Sheet In Worksheets
If sheetNameStr = Sheet.Name Then
sheetExists = True
Exit For
End If
Next Sheet
If Not sheetExists Then
Set ws = ThisWorkbook.Sheets.Add(After:=ThisWorkbook.Sheets(ThisWorkbook.Sheets.Count))
ws.Name = sheetNameStr
MsgBox ("New sheet named " & sheetNameStr & "was created")
End If
Sheets("Main").Activate
Worksheets("Main").Range("A4:D300").Copy Worksheets(sheetNameStr).Range("A1")
Worksheets("Main").Range("A6:D300").Clear
Recover:
On Error Resume Next
Application.ScreenUpdating = True
Exit Sub
errHandler:
MsgBox Err.Description, vbExclamation + vbOKOnly, "Error"
Resume Recover
End Sub

Running VBA caps/replace script on multiple lines of pasted data

I am running a VBA script to auto-capitalize and remove hyphens from pasted data into Excel. This script works great on single-line pastes (single-cell), but will not run (does nothing to change the data) if multiple lines of data are pasted in. The following is my code:
Private Sub worksheet_change(ByVal target As Range)
Application.EnableEvents = False
With target
On Error Resume Next
Dim rng As Range
Set rng = Range("A:U")
If Not Intersect(target, rng) Is Nothing Then
If Not .HasFormula Then
.Value = UCase(.Value)
.Value = Replace(.Value, "-", "")
End If
End If
End With
Application.EnableEvents = True
End Sub
Try this
Private Sub worksheet_change(ByVal target As Range)
Application.EnableEvents = False
With target
On Error Resume Next
Dim rng As Range
Dim cell As Range
Set rng = Range("A:U")
If Not Intersect(target, rng) Is Nothing Then
For Each cell in target
If Not cell.HasFormula Then
cell.Value = UCase(cell.Value)
cell.Value = Replace(cell.Value, "-", "")
End If
next cell
End If
End With
Application.EnableEvents = True
End Sub

IF Null exit sub code

I'm trying to check that if cells are empty or null, that it would display a message and exit sub. Here's my code:
With Worksheets(1).[D3:D4, D6:D14]
If WorksheetFunction.CountA(.Cells) = 0 Then
MsgBox "Nothing to generate" & vbNewLine & _
"Set parameters and click generate."
Exit Sub
End If
End With
But the code only works if the entire [D3:D4, D6:D14] are empty. I want it to exit sub even just one of the cells are empty. All cells needs have content for it to continue sub. Please help.
You need to seach inside the Range, try the code below:
Sub CheckEmptyCellsinRange()
Dim Rng As Range
Dim cell As Range
Set Rng = Worksheets(1).[D3:D4, D6:D14]
For Each cell In Rng
If IsEmpty(cell) Or IsNull(cell) Then
MsgBox "Nothing to generate" & vbNewLine & _
"Set parameters and click generate."
Exit Sub
End If
Next cell
End Sub
You can also use SpecialCells:
Sub Check()
Dim rng1 As Range
On Error Resume Next
With Worksheets(1).[D3:D4, D6:D14]
Set rng1 = .SpecialCells(xlBlanks)
If Not rng1 Is Nothing Then
MsgBox "Nothing to generate" & vbNewLine & _
"Set parameters and click generate."
Exit Sub
End If
End With
End Sub
Follow-up question
Sub Check2()
Dim rng1 As Range
Dim rng2 As Range
With Worksheets(1)
Set rng1 = .Range(.[D3:D4], .[D6:D14])
End With
On Error Resume Next
Set rng2 = rng1.SpecialCells(xlBlanks)
On Error GoTo 0
If Not rng1 Is Nothing Then
MsgBox rng1.Cells.Count - rng2.Cells.Count & " were used"
Else
MsgBox "all cells used"
End If
End Sub

Worksheet_BeforeDoubleClick for a selection

i have the below code that doesn't work for a selection:
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
Dim Rng1 As Range
Dim I As Integer
If Selection.Count > 1 Then
For Each cell In Selection
If I = 0 Then
Set Rng1 = Range(cell.Address)
Else
Set Rng1 = Union(Range(cell.Address), Rng1)
End If
I = I + 1
Next cell
MsgBox "You have selected the range " & Rng1.Address(False, False)
Rng1.End(xlDown).Offset(0, 1).Activate
Else
MsgBox "you are in " & ActiveCell.Address(False, False)
ActiveCell.Offset(1, 0).Activate
End If
End Sub
I tried selecting a range of cells and then double clicking, does anybody know if that is even possible?
Because double-clicking collapses any multi-cell selection you have previously made, you'd need to keep track of it, and then check whether Target is within it.
Something like:
Dim rng As Range
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
If Not rng Is Nothing Then
If Not Application.Intersect(Target, rng) Is Nothing Then
Debug.Print "Clicked in selected range: " & rng.Address()
Else
Debug.Print "Cell: " & Target.Address()
End If
Set rng = Nothing
Else
Debug.Print "No previous range: clicked in " & Target.Address()
End If
End Sub
'keeping track of the last multi-cell range selected....
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Target.Count > 1 Then Set rng = Target
End Sub

Combine two subs with changing ranges

I want to combine these two lines of code but cannot figure out how to get it to work. They both work separately, but I want the first line of code to be the first operation and then the second sub to be the second operation. These should execute whenever there is a change to the worksheet. The first routine should only cause a msg box when the corresponding cell in the "S" range updates in the same row as the cell that was updated in column A or B.
The second operation should look for any change in range "T7:T26" and prompt a msg box.
Code is below:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim myRng As Range
Dim lRow As Long
If Target.CountLarge > 1 Then Exit Sub
On Error GoTo Whoa
Set myRng = Range("A7:B26")
Application.EnableEvents = False
If Not Intersect(Target, myRng) Is Nothing Then
lRow = Target.Row
If Range("S" & lRow).Value >= 16 Then sVar = _
MsgBox("Will Enough Pre-Wave Resources be Available?", 4, "Attention!")
If sVar = 7 Then Application.Undo
End If
Letscontinue:
Application.EnableEvents = True
Exit Sub
Whoa:
MsgBox Err.Description
Resume Letscontinue
End Sub
and
Private Sub Worksheet_Change(ByVal Target As Range)
Dim myRng As Range
Set myRng = ThisWorkbook.Sheets("SMT 5").Range("T7:T26")
For Each mycell In myRng
If mycell.Value = "ISSUE" Then sVar = MsgBox("Possible Pre-Wave Manpower Issue on 2nd or 3rd Shift. Will Enough Resources be Available?", 4, "Attention!")
If sVar = 7 Then
Application.Undo
End If
Exit For
Next
End Sub
If both of them work on their own, you can copy the code into a module and give them two distinct names.
Then, in the Worksheet_Change sub you just use Call to run both subs.
Is this what you are trying?
Const sMsg1 As String = "Will Enough Pre-Wave Resources be Available?"
Const sMsg2 As String = "Possible Pre-Wave Manpower Issue on " & _
"2nd or 3rd Shift. Will Enough Resources be Available?"
Private Sub Worksheet_Change(ByVal Target As Range)
Dim myRng As Range, othrRng As Range, aCell As Range
Dim lRow As Long
Dim sVar
If Target.CountLarge > 1 Then Exit Sub
On Error GoTo Whoa
Set myRng = Range("A7:A26")
Set othrRng = Range("T7:T26")
Application.EnableEvents = False
If Not Intersect(Target, myRng) Is Nothing Then
lRow = Target.Row
If Range("S" & lRow).Value >= 16 Then sVar = _
MsgBox(sMsg1, 4, "Attention!")
If sVar = 7 Then Application.Undo
End If
For Each aCell In othrRng
If aCell.Value = "ISSUE" Then _
sVar = MsgBox(sMsg2, 4, "Attention!")
If sVar = 7 Then
Application.Undo
Exit For
End If
Next
Letscontinue:
Application.EnableEvents = True
Exit Sub
Whoa:
MsgBox Err.Description
Resume Letscontinue
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
Dim myRng As Range
Dim lRow As Long
If Target.CountLarge > 1 Then Exit Sub
On Error GoTo Whoa
Set myRng = Range("A7:B26")
Application.EnableEvents = False
If Not Intersect(Target, myRng) Is Nothing Then
lRow = Target.Row
If Range("S" & lRow).Value >= 16 Then sVar = _
MsgBox("Will Enough Pre-Wave Resources be Available?", 4, "Attention!")
If sVar = 7 Then Application.Undo
End If
Set othrRng = Range("T7:T26")
For Each aCell In othrRng
If aCell.Value = "ISSUE" Then sVar = MsgBox("Possible Pre-Wave Manpower Issue on 2nd or 3rd Shift. Will Enough Resources be Available?", 4, "Attention!")
If sVar = 7 Then
Application.Undo
Exit For
End If
Next
Letscontinue:
Application.EnableEvents = True
Exit Sub
Whoa:
MsgBox Err.Description
Resume Letscontinue
End Sub