Delete and shift cells up on a worksheet change - vba

Note: One of the users asked this question and after I answered he delete it. I am just reposting the question and answer as in my opinion its a good example of bad coding habits and highlights why one needs to use Option Explicit
I have a worksheet change event where if Column I on sheet "current" is altered, it will then cut/paste that current row into the "completed" sheet. Only issue is that I need the empty row to delete from the sheet. My current code is only causing it to clear the row, and not delete/shift it up. How could I go about deleting a row and shifting up, without effecting the on change event?
Private Sub Worksheet_Change(ByVal Target As Range)
Dim KeyCells As Range
Dim LastRowCompleted As Long
Dim RowToDelete As Long
RowToDelete = 0
LastRowCompleted = Sheets("completed").Cells(Sheets("completed").Rows.Count, "A").End(xlUp).Row
LastRowCompleted = LastRowCompleted + 1 'Next row after last row
Set KeyCells = Range("I:I")
Application.EnableEvents = False
If Not Application.Intersect(KeyCells, Range(Target.Address)) Is Nothing Then
'Cut and Paste Row
Target.EntireRow.Cut Sheets("completed").Range(LastRowCompleted & ":" & LastRowCompleted)
'Mark to delete row
RowToDelete = Target.EntireRow.Row
End If
Application.EnableEvents = True
Call DeleteRow(RowToDelete)
End Sub
Sub DeleteRow(Row As Long)
If RowsToDelete > 0 Then
Rows(Row).EntireRow.Delete Shift:=xlToUp
End If
End Sub

Always use Option Explicit
There is nothing called xlToUp correct enum value is xlUp
This is wrong
Sub DeleteRow(Row As Long)
If RowsToDelete > 0 Then
Rows(Row).EntireRow.Delete Shift:=xlToUp
End If
End Sub
There is no RowsToDelete variable so your condition always evaluates to false.
Correct code will be
Sub DeleteRow(RowsToDelete As Long)
If RowsToDelete > 0 Then
Rows(RowsToDelete).EntireRow.Delete Shift:=xlUp
End If
End Sub
Enable events after deleting the Row else you will get stuck in infinite loop.
Call DeleteRow(RowToDelete)
Application.EnableEvents = True
Always set CutCopyMode=False after cut or copy
This will work.
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim KeyCells As Range
Dim LastRowCompleted As Long
Dim RowToDelete As Long
RowToDelete = 0
LastRowCompleted = Sheets("completed").Cells(Sheets("completed").Rows.Count, "A").End(xlUp).Row
LastRowCompleted = LastRowCompleted + 1 'Next row after last row
Set KeyCells = Range("I:I")
Application.EnableEvents = False
If Not Application.Intersect(KeyCells, Range(Target.Address)) Is Nothing Then
'Cut and Paste Row
Target.EntireRow.Cut Sheets("completed").Range(LastRowCompleted & ":" & LastRowCompleted)
Application.CutCopyMode = False
'Mark to delete row
RowToDelete = Target.EntireRow.Row
End If
Call DeleteRow(RowToDelete)
Application.EnableEvents = True
End Sub
Sub DeleteRow(RowsToDelete As Long)
If RowsToDelete > 0 Then
Rows(RowsToDelete).EntireRow.Delete Shift:=xlUp
End If
End Sub

Related

Have more than one Worksheet_Change in a Worksheet

I am looking to limit my workbook users to 1000 characters over a range of cells (Example: A5:A30).
In other words limit the total characters in the range A5:A30 to 1000 characters.
When a user fills in a cell that sends the range over the 1000 character limit, it will call Application.undo which should just remove the last text that they added.
However since I have another Private Sub Worksheet_Change(ByVal Targe As Range) on the worksheet, it causes a bug.
Below is both Worksheet_Change subs. Both use the same cells.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim charCount As Long
If Not Intersect(Target, Range("E6,E11,E16")) Is Nothing Then
Dim arrValues As Variant
arrValues = Range("E6,E11,E16").Value2
Dim i As Long
Dim tempSplit As Variant
Dim j As Long
For i = LBound(arrValues) To UBound(arrValues)
tempSplit = Split(arrValues(i, 1), " ")
For j = LBound(tempSplit) To UBound(tempSplit)
charCount = charCount + Len(tempSplit(j))
Next j
Next i
End If
If charCount > 1000 Then
Application.Undo
MsgBox "Adding this exceeds the 1000 character limit"
End If
If Not Intersect(Target, Range("D6")) Is Nothing Then
If Target.Value2 = "Material" Then
'assumes the comment cell is one column to the right
Target.Offset(0, 1) = "**"
End If
End If
If Not Intersect(Target, Range("D7")) Is Nothing Then
If Target.Value2 = "Material" Then
'assumes the comment cell is one column to the right
Target.Offset(-1, 1) = "**"
End If
End If
If Not Intersect(Target, Range("D8")) Is Nothing Then
If Target.Value2 = "Material" Then
Target.Offset(-2, 1) = "**"
End If
End If
End Sub
Is there a way around this so I can have two Worksheet_Change on the same worksheet?
You cannot have two Worksheeet_Change events in one sheet. But, one is quite enough:
Private Sub Worksheet_Change(ByVal Target As Range)
Select Case True
Case Not Intersect(ActiveCell, Range("A5:A30")) Is Nothing
DoThingOne
Case Not Intersect(ActiveCell, Range("B5:B30")) Is Nothing
DoThingTwo
End Select
End Sub
Private Sub DoThingOne()
Debug.Print "THING ONE"
End Sub
Private Sub DoThingTwo()
Debug.Print "THING TWO"
End Sub
How about this revision using Vityata's idea?
Private Sub Worksheet_Change(ByVal Target As Range)
Select Case True
Case Not Intersect(Target, Range("E6,E11,E16")) Is Nothing
Dim charCount As Long
Dim arrValues As Variant
arrValues = Range("E6,E11,E16").Value2
Dim i As Long
Dim tempSplit As Variant
Dim j As Long
For i = LBound(arrValues) To UBound(arrValues)
tempSplit = Split(arrValues(i, 1), " ")
For j = LBound(tempSplit) To UBound(tempSplit)
charCount = charCount + Len(tempSplit(j))
Next j
Next i
If charCount > 1000 Then
With Application
.EnableEvents = False
.Undo
.EnableEvents = True
End With
MsgBox "Adding this exceeds the 1000 character limit"
End If
Case Not Intersect(Target, Range("D6")) Is Nothing
If Target.Value2 = "Material" Then
'assumes the comment cell is one column to the right
Target.Offset(0, 1) = "**"
End If
Case Not Intersect(Target, Range("D7")) Is Nothing
If Target.Value2 = "Material" Then
'assumes the comment cell is one column to the right
Target.Offset(-1, 1) = "**"
End If
Case Not Intersect(Target, Range("D8")) Is Nothing
If Target.Value2 = "Material" Then
Target.Offset(-2, 1) = "**"
End If
End Select
End Sub

Excel VBA to insert duplicate row below based on drop down menu

I would like to add to the following VBA code, so that when "Did not attend" is selected from dropdown menu a duplicate row is ALSO inserted below the current row within the current worksheet "Details".
Private Sub Worksheet_Change1(ByVal Target As Range)
'Determine if change was made to a single cell in Column E
If Target.Column = 5 And Target.Cells.Count = 1 Then
'Determine if Did not attend was chosen
If Target = "Did not attend" Then
'If Yes...
''Disable Events
Application.EnableEvents = False
''Insert a row below
ActiveCell.Offset(1).EntireRow.Insert
''Copy, Paste
Rows(Target.Row).EntireRow.Copy _
Destination:=Sheets("Non Attendance").Range("A" & nxtRw)
''Re-enable Events
Application.EnableEvents = True
End If
End If
End Sub
This code should do what you want. Please try it.
Private Sub Worksheet_Change(ByVal Target As Range)
' 22 Jan 2018
Dim Rng As Range
Set Rng = Range(Cells(2, "E"), Cells(Rows.Count, "E").End(xlUp))
Debug.Print Target.Address
' Determine if change was made in Column E, below row 1 and above last row
If Not Application.Intersect(Target, Rng) Is Nothing Then
With Target
On Error Resume Next
If .Cells.Count = 1 Then ' if change was in a single cell
'Determine if Did not attend was chosen
If StrComp(.Value, "Did not attend", vbTextCompare) = 0 Then
' If Yes...
Application.EnableEvents = False
.Offset(1).EntireRow.Insert ' Insert a row below
With Worksheets("Non Attendance")
Set Rng = .Cells(.Rows.Count, "A").End(xlUp).Offset(1)
End With
' Copy, Paste
Rows(.Row).EntireRow.Copy Destination:=Rng
Application.EnableEvents = True
End If
End If
End With
End If
End Sub

Excel VBA Large Table, Add Comments Vlookup, After Hitting Command Button

I have a large table and the information I'm wanting to add comments to falls within Range(D11:CY148). I have two tabs - "Finish Matrix" (main) and "list" (hidden - has 2 columns).
I have two issues.
First issue - Code works to a degree, after I type my values within a cell it automatically adds comments based off info in another sheet. The problem is there is too many cells to be manually typing into and if I copy and paste the code doesn't run. I created a CommandButton and wanted it to refresh the entire table with comments depending if the cells had the values that fall within "list". I tried to create a call out to Worksheet_Change but to no avail. (I'm a beginner so it'll help if you explain)
Second issue - I'm assuming it'll get fixed with whatever suggestion that works. Occasionally after typing into a cell I would get an error. Can't remember the error name but it is one of the common ones, atm the error isn't popping up but surely it'll come back since I didn't do anything different to the code.
Private Sub Worksheet_Change(ByVal Target As Range)
If Intersect(Target, Columns("A:CX")) Is Nothing Then _
If Intersect(Target, Columns("CY")) Is Nothing Then Exit Sub
Dim lRow As Integer
lRow = Sheets("list").Range("A1").End(xlDown).Row
If Target.Value = vbNullString Then Target.ClearComments
For Each cell In Sheets("list").Range("A1:A" & lRow)
If cell.Value = Target.Value Then
Target.AddComment
Target.Comment.Text Text:=cell.Offset(0, 1).Value
End If
Next cell
End Sub
Thanks for any and all help!
You are basically missing the For Each Cell in Target part...
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim wsMain As Worksheet, wsList As Worksheet
Dim cell As Range
Dim vCommentList As Variant
Dim i As Long, lLastRow As Long
Dim sValue As String
On Error GoTo ErrHandler
Application.ScreenUpdating = False
Application.EnableEvents = False
Set wsMain = Target.Parent
Set Target = Intersect(Target, wsMain.Range("D11:CY148"))
If Target Is Nothing Then Exit Sub
Set wsList = wsMain.Parent.Sheets("list")
lLastRow = LastRow(1, wsList)
' Read Comment List into Variant (for speed)
vCommentList = wsList.Range("A1:B" & lLastRow)
Target.ClearComments
' This...For each Cell in Target...is what you were missing.
For Each cell In Target
sValue = cell
For i = 1 To UBound(vCommentList)
If sValue = vCommentList(i, 1) Then
AddComment cell, CStr(vCommentList(i, 2))
Exit For
End If
Next
Next
ErrHandler:
If Err.Number <> 0 Then Debug.Print Err.Description
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub
Proper way to find last row ...
Public Function LastRow(Optional Col As Integer = 1, Optional Sheet As Excel.Worksheet) As Long
If Sheet Is Nothing Then Set Sheet = Application.ActiveSheet
LastRow = Sheet.Cells(Sheet.Rows.Count, Col).End(xlUp).Row
End Function
Add Comment Sub the allows appending is needed...
Public Sub AddComment(Target As Range, Text As String)
If Target.Count = 1 Then
If Target.Comment Is Nothing Then
Target.AddComment Text
Else
Target.Comment.Text Target.Comment.Text & vbLf & Text
End If
End If
End Sub
Untested, but this will take all the values in Range(D11:CY148) and add a comment based on a lookup from Sheet "list".
Sub testy()
Dim arr As Variant, element As Variant
Dim i As Long, j As Long, listItems As Long, rwLast As Long, clLast As Long
Dim comm As String
Dim rng As Range, cell As Range
listItems = Sheets("list").Range("A1").End(xlDown).Row
rwLast = Cells.SpecialCells(xlCellTypeLastCell).Row ' Adjust to fit your needs
clLast = Cells.SpecialCells(xlCellTypeLastCell).Column 'Idem
Set rng = Sheets("list").Range("A1:A" & listItems)
arr = Range("D11:CY148").Value
With Worksheets("Finish Matrix")
For i = 1 To rwLast - 10 'Adjust to make it more general, this is pretty rough
For j = 1 To clLast - 3 'Idem
If i = 3 Then
End If
comm = ""
For Each cell In rng
If arr(i, j) = cell.Value Then
comm = comm & Chr(13) & cell.Offset(0, 1).Value
End If
Next cell
If Not (comm = "") Then
.Cells(10, 3).Offset(i, j).ClearComments
.Cells(10, 3).Offset(i, j).AddComment
.Cells(10, 3).Offset(i, j).Comment.Text Text:=comm
End If
Next j
Next i
End With
End Sub

Check values in a range before continuing

So right now I have an excel workbook for a task tracker. When the column that contains the completed date is filled in, it will take that row and copy it onto another sheet ("Complete") then delete it off the current sheet ("Current"). What I would like it to do before this is executed is check the values of columns H through M for either a "C" or "U". If any of the Cells in that range do not contain either or, then I want it to exit out and display a message. I am not to familiar with Excel or VBA, but decent with C++.
Here is the code as of right now:
Private Sub Worksheet_Change(ByVal Target As Range)
Application.EnableEvents = False
Dim receivedDate As Range, nextOpen As Range, isect As Range
Set receivedDate = Sheet1.Range("G3:G166")
Set isect = Application.Intersect(Target, receivedDate)
If Not (isect Is Nothing) And IsDate(Target) = True Then
Set nextOpen = Sheet4.Range("A" & Rows.Count).End(xlUp).Offset(1, 0)
Target.EntireRow.Copy Destination:=nextOpen.EntireRow
Target.EntireRow.Delete
End If
Application.EnableEvents = True
End Sub
Here is snip of what I have going on...
snip of work
Any help would be greatly appreciated. Sorry I tried looking around some.
Edit - more robust, added error handler and multi-cell update handling
Private Sub Worksheet_Change(ByVal Target As Range)
Dim receivedDate As Range, nextOpen As Range, isect As Range
Dim rngHM As Range, c As Range, rngDel As Range
Set receivedDate = Sheet1.Range("G3:G166")
'are any of the changed cells in the range we're monitoring?
Set isect = Application.Intersect(Target, receivedDate)
On Error GoTo haveError 'error handler ensures events get re-enabled...
'### remember that Target can contain >1 cell...
For Each c In isect.Cells
If IsDate(c.Value) Then
With c.EntireRow
Set rngHM = .Cells(1, "H").Resize(1, 6)
'EDIT: all cells must be C or U
If (Application.CountIf(rngHM, "C") + _
Application.CountIf(rngHM, "U")) <> rngHM.Cells.Count Then
MsgBox "No C or U on row " & c.Row & " !"
Else
Set nextOpen = Sheet4.Range("A" & Rows.Count) _
.End(xlUp).Offset(1, 0)
.Copy Destination:=nextOpen.EntireRow
'deleting rows while looping gives odd results,
' so store them up until done...
If rngDel Is Nothing Then
Set rngDel = c
Else
Set rngDel = Application.Union(rngDel, c)
End If
End If
End With 'entirerow
End If 'is date
Next c
'delete any copied rows in a single operation
If Not rngDel Is Nothing Then
Application.EnableEvents = False
rngDel.EntireRow.Delete
Application.EnableEvents = True
End If
Exit Sub
haveError:
'if your code errors out then this makes sure event handling gets reset
Application.EnableEvents = True
End Sub

Creating a Timestamp VBA

Need Help with this Macro:
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Column = 2 Then
Application.EnableEvents = False
Cells(Target.Row, 3).Value = Date + Time
Application.EnableEvents = True
End If
End Sub
Sub DeleteCells()
For Each Cell In Range("B3:B25")
If IsBlank(Cell.Value) Then
Cell.Offset(0, 1).Clear
End If
Next
End Sub
The purpose of this macro is to create a timestamp. First macro works fine. If anything from row B is filled in, a timestamp will be created in row C. However, the delete cells function isn't working. I want it so that if someone deletes a cell in row B, the timestamp will also be deleted.
Try this:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rng As Range, c As Range
'anything in ColB?
Set rng = Application.Intersect(Me.Columns(2), Target)
If rng Is Nothing Then Exit Sub 'nothing to process...
Application.EnableEvents = False
'could be >1 cell, so loop over them...
For Each c In rng.Cells
'skip any cells with errors
If c.Row>=3 And Not IsError(c.Value) Then '<<edit
c.EntireRow.Cells(3).Value = _
IIf(Len(c.Value) > 0, Now, "")
End If
Next c
Application.EnableEvents = True
End Sub