excelvba

Excel VBA- Delete Multiple Cells and Change Format in a Table for Tracking Changes and Logging in a Table


The following code works to track changes and subsequently list each change in a table.

However, for some reason, I keep getting an error 1004 Method 'Undo' of object '_Application' failed if I select multiple cells and hit delete.

NOTE: This is an extension of Excel VBA- Delete Multiple Cells in a Table for Tracking Changes

Private Sub Worksheet_Change(ByVal Target As Range)

    Application.ScreenUpdating = False
        
    Dim wsLog As Worksheet
    Dim oList1, oList2, chgTbl As ListObject
    Dim rng As Range, c As Range, sCol As String
    Dim sRowDate, sRowVend, sRowDesc As String
    Dim oldValue, sTo As String, sFrom As String
    Dim r As Long
    
    On Error GoTo myerror
    Application.EnableEvents = False
   
    Set oList1 = Me.ListObjects(1)
    Set oList2 = Me.ListObjects(2)
    Set rng = Union(oList2.DataBodyRange.Columns("A:C"), _
                    oList2.DataBodyRange.Columns("E:AR"))
   
    Set wsLog = ThisWorkbook.Sheets("Change Log")
    Set chgTbl = wsLog.ListObjects(1)
    
    With chgTbl.Range
        Dim rStart As Long, rEnd As Long
        r = chgTbl.ListRows.Count
        
        rStart = r + 1
        For Each c In Target
            If Intersect(c, rng) Is Nothing Then
                ' do nothing
                
            Else
                ' column header
                If c.Column - oList2.DataBodyRange.Column + 1 <= 3 Then
                    sCol = Intersect(c.EntireColumn, oList2.HeaderRowRange).Value
                    
                Else
                    sCol = Intersect(c.EntireColumn, oList1.DataBodyRange.Rows(1)).Value
                End If
                
                
                sRowDate = Intersect(c.EntireRow, oList2.ListColumns(1).DataBodyRange).Value
                sRowVend = Intersect(c.EntireRow, oList2.ListColumns(2).DataBodyRange).Value
                sRowDesc = Intersect(c.EntireRow, oList2.ListColumns(3).DataBodyRange).Value
               
                Application.Undo
                oldValue = c
                Application.Undo
                
                
                ' so that empty values are easier to read versus just ****
                If c.Value = "" Then
                    sTo = "EMPTY"
                    sFrom = oldValue
                    
                ElseIf oldValue = 0 Then
                   sTo = c.Value
                   sFrom = "EMPTY"
                   
                ElseIf oldValue <> 0 Then
                   sTo = c.Value
                   sFrom = oldValue
                End If
                

                ' prevent recording when deleting empty cell
                If sTo = "EMPTY" And sFrom = "" Then
                    'do nothing
                    
                Else
                    'log it
                    Dim NewRow As ListRow
                    Set NewRow = chgTbl.ListRows.Add
                    r = 1

                    With NewRow.Range
                        .Cells(r, 1) = Environ("username") ' user
                        .Cells(r, 2) = Format(Now(), "DD MMMM") 'date of Change
                        .Cells(r, 3) = Me.Name ' month
                        .Cells(r, 4) = sRowDate ' expense date
                        .Cells(r, 5) = sRowVend ' vendor
                        .Cells(r, 6) = sRowDesc ' description
                        .Cells(r, 7) = sCol ' account (column name)
                        .Cells(r, 8) = sTo ' new value
                        .Cells(r, 9) = sFrom ' old value
                        .Cells(r, 10) = sCol & " was changed to **" & sTo & "** from **" _
                            & sFrom & "** by " & Environ("username") & " on" & " " & _
                            Format(Now(), "DD MMMM YYYY @ H:MM:ss")
                        .Cells(r, 11) = 2
                    End With
                End If
            End If
            
        Next
        rEnd = r
        
        ' change format
        For r = rStart To rEnd
            If .Cells(r, 11) = 2 Then
                .Cells(r, 11).Locked = False
            Else
                
            End If
            .Cells(r, 11).ClearContents
        Next
        
        wsLog.Columns("B:J").AutoFit
    End With
    
myerror:
    Application.EnableEvents = True
    If Err.Number Then MsgBox Err.Number & " " & Err.Description

End Sub

Furthermore, the changing format is not working. It pastes value of two (2) into .Cells(r, 11) but nothing more.

        ' change format
        For r = rStart To rEnd
            If .Cells(r, 11) = 2 Then
                .Cells(r, 11).Locked = False
            Else
                
            End If
            .Cells(r, 11).ClearContents
        Next

Solution

  • Made revisions based on comments.

    Option Explicit
    
    Private Sub Worksheet_Change(ByVal Target As Range)
    
        Application.ScreenUpdating = False
            
        Dim wsLog As Worksheet
        Dim oList1, oList2, chgTbl As ListObject
        Dim rng As Range, c As Range, sCol As String
        Dim sRowDate, sRowVend, sRowDesc As String
        Dim oldValue, sTo As String, sFrom As String
        Dim r As Long
        
        Dim dict As Object, k
        Set dict = CreateObject("Scripting.Dictionary")
        
        On Error GoTo myerror
        Application.EnableEvents = False
       
        Set oList1 = Me.ListObjects(1)
        Set oList2 = Me.ListObjects(2)
        Set rng = Union(oList2.DataBodyRange.Columns("A:C"), _
                        oList2.DataBodyRange.Columns("E:AR"))
       
        Set wsLog = ThisWorkbook.Sheets("Change Log")
        Set chgTbl = wsLog.ListObjects(1)
        
        With chgTbl.Range       
            ' store changes
            For Each c In Target
                If Intersect(c, rng) Is Nothing Then
                    ' do nothing
                Else
                    Application.Undo
                    dict.Add c.Address, c.Value
                    Application.Undo
                End If
            Next
            
            ' process changes
            For Each k In dict
                oldValue = dict(k)
                Set c = Me.Range(k)
                
                ' so that empty values are easier to read versus just ****
                If c.Value = "" Then
                    sTo = "EMPTY"
                    sFrom = oldValue
                    
                ElseIf oldValue = 0 Then
                   sTo = c.Value
                   sFrom = "EMPTY"
                   
                ElseIf oldValue <> 0 Then
                   sTo = c.Value
                   sFrom = oldValue
                End If
    
                ' prevent recording when deleting empty cell
                If sTo = "EMPTY" And sFrom = "" Then
                    'do nothing
                Else
                    ' column header
                    If c.Column - oList2.DataBodyRange.Column + 1 <= 3 Then
                        sCol = Intersect(c.EntireColumn, oList2.HeaderRowRange).Value
                    Else
                        sCol = Intersect(c.EntireColumn, oList1.DataBodyRange.Rows(1)).Value
                    End If
                    
                    sRowDate = Intersect(c.EntireRow, oList2.ListColumns(1).DataBodyRange).Value
                    sRowVend = Intersect(c.EntireRow, oList2.ListColumns(2).DataBodyRange).Value
                    sRowDesc = Intersect(c.EntireRow, oList2.ListColumns(3).DataBodyRange).Value
                
                    'log it
                    Dim NewRow As ListRow
                    Set NewRow = chgTbl.ListRows.Add
    
                    With NewRow.Range
                        .Cells(1, 1) = Environ("username") ' user
                        .Cells(1, 2) = Format(Now(), "DD MMMM") 'date of Change
                        .Cells(1, 3) = Me.Name ' month
                        .Cells(1, 4) = sRowDate ' expense date
                        .Cells(1, 5) = sRowVend ' vendor
                        .Cells(1, 6) = sRowDesc ' description
                        .Cells(1, 7) = sCol ' account (column name)
                        .Cells(1, 8) = sTo ' new value
                        .Cells(1, 9) = sFrom ' old value
                        .Cells(1, 10) = sCol & " was changed to **" & sTo & "** from **" _
                            & sFrom & "** by " & Environ("username") & " on" & " " & _
                            Format(Now(), "DD MMMM YYYY @ H:MM:ss")
                        .Cells(r, 11).Locked = False
                    End With
                End If
            Next
    
            wsLog.Columns("B:J").AutoFit
        End With
        
    myerror:
        Application.EnableEvents = True
        If Err.Number Then MsgBox Err.Number & " " & Err.Description
    
    End Sub