I have a spreadsheet with a list of sales on it.
When a user sets the "Status" column to "Closed" I want to force them to enter a date in the "Closed Date" column.
So I have this;
A1 (Status), B1 (Closed Date)
Open, <blank>
Open, <blank>
Closed, 1/1/2018
Right click on the Sheet Tab --> View code and paste the code given below into the opened code window and save your workbook as Macro-Enabled Workbook.
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.CountLarge > 1 Then Exit Sub
If Target.Column = 1 And Target.Row > 1 Then
If Target <> "" And LCase(Target.Value) = "closed" Then
With Target.Offset(0, 1)
.Select
On Error Resume Next
.Comment.Delete
On Error GoTo 0
.AddComment.Text "Please enter the Closed Date"
.Comment.Visible = True
End With
Else
On Error Resume Next
Target.Offset(0, 1).Comment.Delete
On Error GoTo 0
End If
ElseIf Target.Column = 2 And Target.Row > 1 Then
If LCase(Target.Offset(0, -1)) = "closed" And IsDate(Target) Then
Target.Comment.Delete
End If
End If
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Target.CountLarge > 1 Then Exit Sub
Dim x
Dim i As Long, lr As Long
Application.ScreenUpdating = False
Application.EnableEvents = False
lr = Cells(Rows.Count, 1).End(xlUp).Row
x = Range("A2:B" & lr)
If Target <> "" Then
Application.EnableEvents = True
Exit Sub
End If
For i = 1 To UBound(x, 1)
If LCase(x(i, 1)) = "closed" And Not IsDate(x(i, 2)) Then
Cells(i + 1, 2).Select
Exit For
End If
Next i
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub
Related
Is there a way I can automate the date or time once the I enter the data from the User_Form via "enter/click" button? I've tried this code but it keeps restarting my excel workbook. On top of that, I have a ton of other codes in the Private Sub Worksheet_Change(ByVal Target As Range)
Is it possible it's overloaded ?
So I'm thinking if I could code it with my Userform_click() I'd be better off?
Private Sub Worksheet_Change(ByVal Target As Range)
Dim i As Integer
For i = 16 To 100
If Cells(i, 3).Value <> "" Then
Cells(i, 1).Value = Date & " "
Cells(i, 1).NumberFormat = "mm/dd/yy"
End If
Next
End Sub
You need to add Application.EnableEvents = False in the beginning of your Sub, otherwise it will keep running it every time a value is changed inside the worksheet (like when you change it inside your For i = 16 To 100 loop).
Code
Private Sub Worksheet_Change(ByVal Target As Range)
Dim i As Integer
Application.EnableEvents = False
For i = 16 To 100
If Cells(i, 3).Value <> "" Then
Cells(i, 1).Value = Date & " "
Cells(i, 1).NumberFormat = "mm/dd/yy"
End If
Next i
Application.EnableEvents = True '<-- restore to original setting
End Sub
Edit 1: You can write your Sub in another way, that it will enter it only if a cell is changed inside the searched Range("C16:C100"). Only if the modified cell is inside that range, then check each cell if the Value <> "".
Private Sub Worksheet_Change(ByVal Target As Range)
Dim C As Range
Application.EnableEvents = False
If Not Intersect(Range("C16:C100"), Target) Is Nothing Then
For Each C In Intersect(Range("C16:C100"), Target)
If C.Value <> "" Then
C.Offset(, -2).Value = Date & " "
C.Offset(, -2).NumberFormat = "mm/dd/yy"
End If
Next C
End If
Application.EnableEvents = True
End Sub
I'd like to know how to detect if the user is deleting or inserting content into a range. If they are deleting a range say D14:D18. I'd like to then perform a macro that also deletes content in E14:E18. I just wouldn't want to delete E14:E18 if they are entering content into D14:D18.
I've tried:
If Selection.ClearContents Then
MsgBox Target.Offset(0, 3).Style
End If
But this get's me stuck in an infinite loop.
A bit more context:
I have a few hundred cells in D:D for entering quantities for services. Not everything in D:D should be touched. Only cells in D:D with .Style = "UnitInput". In E:E I have data validation that lets the user only enter contractor 1 or contractor 2 But, when content is entered in D:D I run a macro to assign the default contractor (housed in F:F) to E:E. So when the user enters quantities into D:D it correctly assigns the default contractor. And when they delete singular items from D:D I have it handling proper removal of contractors. It's only when they delete a range of items from D:D.
Full code:
Private Sub Worksheet_Change(ByVal Target As Range)
Application.ScreenUpdating = False
On Error GoTo ErrHandler:
If Selection.Rows.Count * Selection.Columns.Count = 1 Then
If Target.Offset(0, 3).Style = "Contractor" Then
If Target.Value < 1 Then
Target.Offset(0, 3).Value = ""
Else
Target.Offset(0, 3).Value = Target.Offset(0, 2).Value
End If
End If
If Target.Offset(0, 5).Style = "Markup" Then
If Target.Value = "" Then
Target.Offset(0, 5).Value = ""
ElseIf Target.Value <= Target.Offset(0, 14).Value Then
Target.Offset(0, 5).Value = "Redact 1"
ElseIf Target.Value >= Target.Offset(0, 15).Value Then
Target.Offset(0, 5).Value = "Redact 2"
Else
Target.Offset(0, 5).Value = "Redact 3"
End If
End If
Else
'!!!!!! this is where I need to handle multiple deletions. !!!!!!!
End If
Application.ScreenUpdating = True
ErrHandler:
Application.ScreenUpdating = True
Resume Next
End Sub
Based on your comments in chat, here is what I propose
UNTESTED
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rng As Range, aCell As Range
Dim lRow As Long
'~~> Error handling, Switching off events and Intersect
'~~> As described in
'~~> http://stackoverflow.com/questions/13860894/ms-excel-crashes-when-vba-code-runs
On Error GoTo Whoa
Application.EnableEvents = False
With ActiveSheet
'~~> Find Last Row since data is dynamic
'~~> For further reading see
' http://stackoverflow.com/questions/11169445/error-in-finding-last-used-cell-in-vba
If Application.WorksheetFunction.CountA(.Cells) <> 0 Then
lRow = .Cells.Find(What:="*", _
After:=.Range("A1"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
End If
If lRow > 12 Then
'~~> Set your range
Set rng = Range("D13:D" & lRow)
If Not Intersect(Target, rng) Is Nothing Then
For Each aCell In rng
If Len(Trim(aCell.Value)) = 0 Then
Select Case Target.Offset(0, 3).Style
Case "Contractor"
'~~> Do Your Stuff
Case "Markup"
'~~> Do Your Stuff
'
'~~> And so on
'
End Select
End If
Next aCell
End If
End If
End With
Letscontinue:
Application.EnableEvents = True
Exit Sub
Whoa:
MsgBox Err.Description
Resume Letscontinue
End Sub
Here is an idea -- you have to first select a region to clear its contents. Use selection change to record the number of non-blank cells and then worksheet change to see if it drops to zero. Something like:
Dim NumVals As Long
Private Sub Worksheet_Change(ByVal Target As Range)
Dim NewCount As Long
NewCount = Application.WorksheetFunction.CountA(Target)
If NewCount = 0 And NumVals > 0 Then MsgBox Target.Address & " was cleared"
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
NumVals = Application.WorksheetFunction.CountA(Target)
End Sub
I have this code is Sheet1 and it seems to capture when I highlight a group of cells (which contains at least one value) and then hit the delete key.
You can use the CommandBars Undo Control to determine if the user has actually deleted something.
Bear in mind this will fire if the user any or all of the contents of the Range D14:D18, but can be adjusted in many ways to suit your exact needs. After seeing your edit, this basically means you can adjust the ranges and need be and which cells in column E it affects as well. If you need more guidance on this, let me know.
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Me.Range("D14:D18")) Is Nothing Then
Dim sLastAction As String
sLastAction = Application.CommandBars("Standard").Controls("&Undo").List(1)
Debug.Print sLastAction
'manual delete 'right-click delete 'backspace delete
If sLastAction = "Clear" Or sLastAction = "Delete" Or Left(sLastAction, 9) = "Typing ''" Then
Application.EnableEvents = False
Me.Range("E14:E18").ClearContents
Application.EnableEvents = True
End If
End If
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
Dim ChangedRange As Range
Dim Area As Range
Dim Cell As Range
Set ChangedRange = Application.Intersect(Target, Range("D:D"))
If Not ChangedRange Is Nothing Then
Application.EnableEvents = False
For Each Area In ChangedRange.Areas
For Each Cell In Area
If IsEmpty(Cell) Then
Cell.Offset(0, 1).ClearContents
End If
Next
Next
Application.EnableEvents = True
End If
End Sub
I am creating an Excel spreadsheet. I have 2 separate functions that I need to combine but I am not sure how to smash them together. I know I can only have 1 change event. The first function will unprotect the sheet (column c is locked), auto populate column C when data is entered in to column A or erase C when A is erased and re-protect when complete. The second will return the cell focus to the next row, column A, when data is entered into A and B. Separately they work as needed.
Private Sub Worksheet_Change(ByVal Target As Range)
On Error Resume Next
Unprotect Password:="my password"
If Target.Column = 1 Then
Dim A As Range, B As Range, Inte As Range, r As Range
Set A = Range("A:A")
Set Inte = Intersect(A, Target)
If Target.Offset(0, 1 - Target.Column).Value = "" Then
Target.Offset(0, 3 - Target.Column).Clear
Exit Sub
End If
Application.EnableEvents = False
For Each r In Inte
r.Offset(0, 2).Value = Date & " " & Time
r.Offset(0, 2).NumberFormat = "m/d/yyyy h:mm am/pm"
Next r
Application.EnableEvents = True
End If
Protect Password:="my password"
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
On Error GoTo Whoa
Application.EnableEvents = False
If Not Target.Cells.CountLarge > 1 Then
If Not Intersect(Target, Columns(1)) Is Nothing Then
Target.Offset(, 1).Select
ElseIf Not Intersect(Target, Columns(2)) Is Nothing Then
Target.Offset(1, -1).Select
End If
End If
Letscontinue:
Application.EnableEvents = True
Exit Sub
Whoa:
MsgBox Err.Description
Resume Letscontinue
End Sub
How about this, seems to do what you want, as I understand the question.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rngIntersect As Range
Dim rngCell As Range
On Error GoTo TidyUp
Application.EnableEvents = False
If Target.Column = 1 Then
Set rngIntersect = Intersect(Range("A:A"), Target)
For Each rngCell In rngIntersect
If rngCell.Value = "" Then
rngCell.Offset(0, 2).Value = ""
Else
rngCell.Offset(0, 2).Value = Date & " " & Time
rngCell.Offset(0, 2).NumberFormat = "m/d/yyyy h:mm am/pm"
End If
Next rngCell
End If
If Target.Column < 3 And Target.Value <> "" Then ' lose the 'And Target.Value <> ""' as desired
Cells(Target.Row + Target.Rows.Count, 1).Select
End If
TidyUp:
Set rngIntersect = Nothing
Set rngCell = Nothing
Application.EnableEvents = True
End Sub
I'd also suggest using UserInterfaceOnly in your worksheet.Protect, then you don't have to unprotect the sheet for VBA to act on the sheet.
Implement it in two Sub-Procedures on a modul, then just call both of them in the Event-Procedure.
I am using the spreadsheet to do inventory- scanning parts in bin
I am trying to add a A2 Value next to the (Target.Row, 5)(row 6)
how do I make A2 or A9 or the next scan Value show up on Column F so if column E & F don't match I know the part is sitting on the wrong bin
I have tried .Value = Target.Worksheet.Cells(Target.Row, 1).Value but not working
Here is my complete code
Option Explicit
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
If Target.Cells.Count > 1 Or IsEmpty(Target) Or Target.Column <> 1 Then Exit Sub
If Not SheetExists("WarehouseInventory") Then Exit Sub
Dim Result As Variant
Set Result = Sheets("WarehouseInventory").Cells.Range("E:E").Find(Target)
If Result Is Nothing Then
Target.Worksheet.Cells(Target.Row, 2) = "Data New Bin"
Else
Target.Worksheet.Cells(Target.Row, 2) = Result.Worksheet.Cells(Result.Row, 4)
Target.Worksheet.Cells(Target.Row, 3) = Result.Worksheet.Cells(Result.Row, 5)
Target.Worksheet.Cells(Target.Row, 4) = Result.Worksheet.Cells(Result.Row, 6)
Target.Worksheet.Cells(Target.Row, 5) = Result.Worksheet.Cells(Result.Row, 7)
End If
End Sub
Public Function SheetExists(SheetName As String) As Boolean
Dim ws As Worksheet
SheetExists = False
For Each ws In ThisWorkbook.Worksheets
If ws.Name = SheetName Then SheetExists = True
Next ws
End Function
this an example how you can achieve required result
Private Sub Worksheet_Change(ByVal Target As Range)
Dim x&, y&
If Target.Column = 6 Then
y = 5: x = Target.Row
While Cells(x, y) <> "": x = x - 1: Wend
If Cells(Target.Row, 5) <> "" Then Cells(Target.Row, 6) = Cells(x, 1)
End If
End Sub
output
UPDATE
here your updated code, tested, works fine, insert it into "Cycle Count" sheet
Private Sub Worksheet_Change(ByVal Target As Range)
Dim x&, y&, Result As Range, ws As Worksheet
Application.EnableEvents = 0
If Target.Count > 1 Then
Application.EnableEvents = 1
Exit Sub
End If
If Not Intersect(Target, [A1:A99999]) Is Nothing Then
With ThisWorkbook
For Each ws In .Worksheets
If ws.Name = "WarehouseInventory" Then
Set ws = .Worksheets("WarehouseInventory")
Set Result = ws.[E:E].Find(Target.Value)
Exit For
End If
Next ws
If Result Is Nothing Then
Target.Offset(, 1).Value = "Data New Bin"
Application.EnableEvents = 1
Exit Sub
Else
Target.Offset(, 1).Value = ws.Range(Result.Address).Offset(, -1).Value
Target.Offset(, 2).Value = ws.Range(Result.Address).Value
Target.Offset(, 3).Value = ws.Range(Result.Address).Offset(, 1).Value
Target.Offset(, 4).Value = ws.Range(Result.Address).Offset(, 2).Value
y = 5: x = Target.Row
While .ActiveSheet.Cells(x, y) <> "": x = x - 1: Wend
Target.Offset(, 5).Value = .ActiveSheet.Cells(x, 1).Value
End If
End With
End If
Application.EnableEvents = 1
End Sub
sample file here http://www.filedropper.com/book1_5
What about Target.Worksheet.Cells(Target.Row, 6).Value = Target.Worksheet.Cells(Target.Row,1).Value ?
If you want only A2, in all rows substitute Target.Row with 2
My codes gives me a Object Required 424 error on this line:
lngRow = .Cells(.Rows.Count, 1).End(xlUp).Row
My full code:
Private Sub Worksheet_Change(ByVal Target As Range)
' If Target.Count > 1 Then Exit Sub
' If Target.Column > 2 Then Exit Sub
Application.EnableEvents = False
If Target.Column = 6 Then
If Target.Offset(0, 1).Value <> "" Then
MsgBox "You must only fill in one of the two columns"
Target.ClearContents
GoTo ExitSub
End If
End If
If Target.Column = 7 Then
If Target.Offset(0, -1).Value <> "" Then
MsgBox "You must only fill in one of the two columns"
Target.ClearContents
GoTo ExitSub
End If
End If
Dim arrData() As Variant
Dim i As Long
Dim lngRow As Long
Dim myNum As Variant
Dim ws As Worksheet
myNum = Target.Value
If Target.Column = 6 Then
With BogieInspectionPoints 'this is a sheet name
lngRow = .Cells(.Rows.Count, 1).End(xlUp).Row
arrData = .Range("a1:b" & lngRow)
End With
End If
If Target.Column = 7 Then
With WagonInspectionPoints 'this is a sheet name
lngRow = .Cells(.Rows.Count, 1).End(xlUp).Row
arrData = .Range("a1:b" & lngRow)
End With
End If
For i = 1 To lngRow
If myNum = arrData(i, 1) Then
Cells(Target.Row, 8).Value = arrData(i, 2)
Exit For
End If
Next
ExitSub:
Application.EnableEvents = True
End Sub
It looks like those sheet variables aren't set.
You will need to add this at the top.
Dim BogieInspectionPoints as Worksheet
Dim WagonInspectionPoints as Worksheet
Set BogieInspectionPoints = ActiveWorkbook.Sheets("BogieInspectionPoints")
Set WagonInspectionPoints = ActiveWorkbook.Sheets("WagonInspectionPoints")
I was assuming there was other code. When you add this line all the With statements should process correctly using the code you posted.
What you're doing with the With statements is shorthanding the object. Instead of writing
BogieInspectionPoints.Range("A1")
'More code
You can write
With BogieInspectionPoints
.Range("A1")
End With
It keeps you from having to write the full object name out.