I'm trying to put together an audit tracker so I can tell when people have changed a shared file. I think I'm close but just can't get it over the line!
The VBA should alert to any changes in sheet "Client Data" by inputting user details and the details of changes into sheet "User Log" in three scenarios:
when someone overwrites existing data
when someone deletes a row
when someone adds a row
I've worked out how to do 1 or 2&3, but can't join them together.
It's taken from a couple of different sources which might explain any inconsistencies (plus I'm still learning).
My current code below:
Dim PreviousValue
Private Sub worksheet_selectionchange(ByVal target As Range)
'reset previous val
PreviousValue = target.Value
End Sub
Private Sub Worksheet_Change(ByVal target As Range)
Static lngRow As Long
Dim rng1 As Range
Dim i As Long
Dim ws As Worksheet
Set ws = Sheets("User Log")
Set rng1 = ThisWorkbook.Names("RowMarker").RefersToRange
If lngRow = 0 Then
lngRow = rng1.Row
Exit Sub
End If
i = ws.Range("A" & Rows.Count).End(xlUp).Row + 1
'track a row deletion
If rng1.Row < lngRow Then
With ws
.Range("A" & i).Value = Application.UserName
.Range("B" & i).Value = ActiveSheet.Name
.Range("E" & i).Value = "Row Removed"
.Range("F" & i).Value = Format(Now(), "dd/mm/yyyy, hh:mm:ss")
End With
Set rng1 = ThisWorkbook.Names("RowMarker").RefersToRange
End If
'track a row addition
If rng1.Row > lngRow Then
With ws
.Range("A" & i).Value = Application.UserName
.Range("B" & i).Value = ActiveSheet.Name
.Range("E" & i).Value = "Row Added"
.Range("F" & i).Value = Format(Now(), "dd/mm/yyyy, hh:mm:ss")
End With
Set rng1 = ThisWorkbook.Names("RowMarker").RefersToRange
End If
'track an overwrite
If rng1.Row = lngRow Then
With ws
.Range("A" & i).Value = Application.UserName
.Range("B" & i).Value = ActiveSheet.Name
.Range("C" & i).Value = target.Address
.Range("D" & i).Value = PreviousValue
.Range("E" & i).Value = target.Value
.Range("F" & i).Value = Format(Now(), "dd/mm/yyyy, hh:mm:ss")
End With
End If
End Sub
This starts to work when I overwrite existing data, but as soon as I add a row, it does record that but then gets stuck and any other changes appear as "row added". It (now) doesn't to be tracking row deletions at all.
Any help very much appreciated!!
Related
I have a data entry form that let's users enter the data into specific cells. What i want is a way to track changes to the cell values. When the data entered initially through the entry form, i don't want that information to be tracked. However, if the user tries to change/edit the data that was entered then i want to add a comment to show the initial value and the amended one as well.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim singlecell As Range
If Target.Cells.CountLarge > 1000 Then Exit Sub
For Each singlecell In Target
If singlecell.Comment Is Nothing Then
singlecell.AddComment Now & " - " & singlecell.Value & " - " & Environ("UserName")
Else
singlecell.Comment.Text _
vbNewLine & Now & " - " & singlecell.Value & " - " & Environ("UserName") _
, Len(singlecell.Comment.Text) + 1 _
, False
End If
singlecell.Comment.Shape.TextFrame.AutoSize = True
Next singlecell
End Sub
The code i tried adds a comment when the information from the entry form is submitted. However I don't need the comment to show just yet, I only want it when the user changes the initial cell value.
you can use a helper array to temporary store all of current cell comments and get the sensitive text out of the last recorded comment to compare with current cell content
Private Sub Worksheet_Change(ByVal Target As Range)
Dim singleCell As Range
Dim commentsArray As Variant 'array to hold all singleCell comments
Dim oldText As String ' string to hold last comment sensitive content
If Target.Cells.CountLarge > 1000 Then Exit Sub
For Each singleCell In Target
If singleCell.Comment Is Nothing Then
singleCell.AddComment Now & " - " & singleCell.Value & " - " & Environ("UserName")
Else
commentsArray = Split(singleCell.Comment.Text, vbNewLine) ' fill the array with current singleCell comments
oldText = CStr(Split(commentsArray(UBound(commentsArray)), " - ")(1)) ' extract last recorded comment sensitive text
'update comment if current cell value differs from last recorded comment sensitive text
If oldText <> CStr(singleCell.Value2) Then _
singleCell.Comment.Text _
vbNewLine & Now & " - " & singleCell.Value & " - " & Environ("UserName") _
, Len(singleCell.Comment.Text) + 1 _
, False
End If
singleCell.Comment.Shape.TextFrame.AutoSize = True
Next
End Sub
Copy and create the same table in same sheet, have it hidden ,
Sub CopyCurrentTable()
Application.ScreenUpdating = False
With shtMapping
.Range("E4:G1000").ClearContents 'which value to which value you are copying
.Range("B4:D" & GetLastRow(shtMapping, "B", 4)).Copy ' starting postion
.Range("E4").PasteSpecial xlPasteAll
Application.CutCopyMode = False
End With
End Sub
Sub LogAuditTrail()
Dim colOld As Collection
Dim colNew As Collection
Dim objNew As ClsMapping
Dim objOld As ClsMapping
Set colOld = getMappingData("E")
Set colNew = getMappingData("B")
Dim sTS As String
sTS = Format(Now, "dd-mmm-yyy hh:mm:ss")
For Each objNew In colNew
'Detect Items Changed
If ItemIsInCollection(colOld, objNew.getKey) Then
Set objOld = colOld(objNew.getKey)
If objNew.isDifferent(objOld) Then
Call PlotToAudit(objNew, objOld, sTS, "Change")
End If
Else
'Detect Items Added
Set objOld = New ClsMapping
Call PlotToAudit(objNew, objOld, sTS, "New")
End If
Next objNew
'Detect Items removed
For Each objOld In colOld
If Not ItemIsInCollection(colNew, objOld.getKey) Then
Set objNew = New ClsMapping
Call PlotToAudit(objNew, objOld, sTS, "Removed")
End If
Next objOld
End Sub
Sub PlotToAudit(obj1 As ClsMapping, obj2 As ClsMapping, sTS As String, sType As String)
Dim lRow As Long
lRow = shtAudit.Range("B1048576").End(xlUp).Row
If lRow = 3 Then
lRow = 5
ElseIf lRow = 1048576 Then
MsgBox "Audit sheet is full. Contact Support." & vbNewLine & "No audit trail will be saved", vbCritical, "ERROR"
Exit Sub
Else
lRow = lRow + 1
End If
With shtAudit
.Unprotect g_sPassword
.Range("B" & lRow).value = Application.UserName & "(" & Environ("USERNAME") & ")"
.Range("C" & lRow).value = sTS
.Range("D" & lRow).value = sType
Select Case sType
Case "Removed"
.Range("E" & lRow).value = ""
.Range("F" & lRow).value = ""
.Range("G" & lRow).value = ""
.Range("H" & lRow).value = obj2.FundCode
.Range("I" & lRow).value = obj2.Subs
.Range("J" & lRow).value = obj2.Reds
Case "New"
.Range("E" & lRow).value = obj1.FundCode
.Range("F" & lRow).value = obj1.Subs
.Range("G" & lRow).value = obj1.Reds
.Range("H" & lRow).value = ""
.Range("I" & lRow).value = ""
.Range("J" & lRow).value = ""
Case "Change"
.Range("E" & lRow).value = obj1.FundCode
.Range("F" & lRow).value = obj1.Subs
.Range("G" & lRow).value = obj1.Reds
.Range("H" & lRow).value = obj2.FundCode
.Range("I" & lRow).value = obj2.Subs
.Range("J" & lRow).value = obj2.Reds
End Select
With .Range("B" & lRow & ":J" & lRow)
.Interior.Color = vbWhite
.Borders.LineStyle = xlContinuou
End With
.Protect g_sPassword
End With
End Sub
I'm writing a code that calculate number automatically every time you edit a sheet. But somehow the code I wrote is not functioning properly that it gives a run-time error. I checked the cells and range but they are all valid and correct. All of the inputs and variables involved are simple integers (no more than 3 digits).
I just got a work assignment to automate some excel sheets at work and I just learned vba from ground up recently.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim A As Integer
Dim i As Byte
i = 5
For i = 5 To 12
If Worksheets("Sheet1").Range("D" & i).Value = "" Or Worksheets("Sheet1").Range("D" & i).Value = 0 Then
A = Worksheets("Sheet1").Range("E" & i).Value - Worksheets("Sheet1").Range("C" & i).Value
Worksheets("Sheet1").Range("F" & i).Value = A
Else
Worksheets("Sheet1").Range("F" & i).Value = Worksheets("Sheet1").Range("D" & i).Value * Worksheets("Sheet1").Range("B" & i).Value _
+ Worksheets("Sheet1").Range("E" & i).Value - Worksheets("Sheet1").Range("C" & i).Value
End If
Next i
End Sub
It gives a run-time error
Give this a shot and let me know what error you get:
Private Sub Worksheet_Change(ByVal Target As Range)
'Only run if something changes in column D or E
If Target.Column = 4 Or Target.Column = 5 Then
'Turn off any events so that we don't encounter recursion
Application.EnableEvents = False
'This will help readability a bit
Dim sht As Worksheet
Set sht = ThisWorkbook.Worksheets("Sheet1")
Dim A As Integer
Dim i As Long
'This needs to be removed - it's irrelevant as i is used as an iterable on the next line
'i = 5
For i = 5 To 12
If sht.Range("D" & i).Value = "" Or sht.Range("D" & i).Value = 0 Then
'What's the point of using a variable here?
A = sht.Range("E" & i).Value - sht.Range("C" & i).Value
sht.Range("F" & i).Value = A
Else
'Order of operations - is that important here?
'Are we certain these fields are numeric?
sht.Range("F" & i).Value = sht.Range("D" & i).Value * sht.Range("B" & i).Value _
+ sht.Range("E" & i).Value - sht.Range("C" & i).Value
End If
Next i
'Turn it back on once we're done
Application.EnableEvents = True
End If
End Sub
I have a workbook like so:
Column A U
Supplier A 10
Supplier B 1
Supplier C 5
Supplier D 9
I am trying to highlight the entire row in red, only for the top 10 numbers in column B.
Here is my conditional formatting rule:
For some reaason the rows are only changing font colour, and the row is not highlighted. I reckon this has something to do with me turning off calculations?
My vba code includes:
Option Explicit
Sub code()
MsgBox "This will take upto 3 minutes."
Application.ScreenUpdating = False
Dim WB As Workbook
Dim i As Long
Dim j As Long
Dim Lastrow As Long
On Error Resume Next
Set WB = Workbooks("L.O. Lines Delivery Tracker.xlsm")
On Error GoTo 0
If WB Is Nothing Then 'open workbook if not open
Set WB = Workbooks.Open("G:\WH DISPO\(3) PROMOTIONS\(18) L.O. Delivery Tracking\L.O. Lines Delivery Tracker.xlsm")
End If
' ======= Edit #2 , also for DEBUG ======
With WB.Worksheets(1)
Lastrow = .Cells(.Rows.Count, "G").End(xlUp).Row
j = 2
For i = 7 To Lastrow
' === For DEBUG ONLY ===
Debug.Print CInt(ThisWorkbook.Worksheets(1).Range("F9").value)
Debug.Print Month(.Range("G" & i).value)
Debug.Print CInt(ThisWorkbook.Worksheets(1).Range("F10").value)
Debug.Print Year(.Range("G" & i).value)
Debug.Print ThisWorkbook.Worksheets(1).Range("B6").value
Debug.Print .Range("M" & i).value
If CInt(ThisWorkbook.Worksheets(1).Range("F9").value) = Month(.Range("G" & i).value) Then ' check if Month equals the value in "A1"
If CInt(ThisWorkbook.Worksheets(1).Range("F10").value) = Year(.Range("G" & i).value) Then ' check if Year equals the value in "A2"
If ThisWorkbook.Worksheets(1).Range("B6").value = .Range("M" & i).value Then
ThisWorkbook.Worksheets(2).Range("A" & j).value = .Range("G" & i).value
ThisWorkbook.Worksheets(2).Range("B" & j).Formula = "=MONTH(B" & j & ")"
ThisWorkbook.Worksheets(2).Range("C" & j).value = .Range("L" & i).value
ThisWorkbook.Worksheets(2).Range("D" & j).value = .Range("D" & i).value
ThisWorkbook.Worksheets(2).Range("E" & j).value = .Range("E" & i).value
ThisWorkbook.Worksheets(2).Range("F" & j).value = .Range("F" & i).value
ThisWorkbook.Worksheets(2).Range("g" & j).value = .Range("p" & i).value
ThisWorkbook.Worksheets(2).Range("H" & j).value = .Range("H" & i).value
ThisWorkbook.Worksheets(2).Range("I" & j).value = .Range("I" & i).value
ThisWorkbook.Worksheets(2).Range("J" & j).value = .Range("J" & i).value
ThisWorkbook.Worksheets(2).Range("k" & j).value = .Range("Q" & i).value
ThisWorkbook.Worksheets(2).Range("L" & j).value = .Range("m" & i).value
j = j + 1
End If
End If
End If
Next i
End With
Worksheets(1).UsedRange.Columns("B:AA").Calculate
On Error GoTo Message
With ThisWorkbook.Worksheets(1) '<--| change "mysheet" to your actual sheet name
Intersect(.Range(Rows(14), .UsedRange.Rows(.UsedRange.Rows.Count)), .Range("G:G")).WrapText = True
Intersect(.Range(Rows(14), .UsedRange.Rows(.UsedRange.Rows.Count)), .Range("G:G")).EntireRow.AutoFit
End With
'End
Application.ScreenUpdating = True
Exit Sub
Message:
On Error Resume Next
Exit Sub
End Sub
And
Private Sub Worksheet_Change(ByVal Target As Range)
Application.Calculation = xlManual
Application.CalculateBeforeSave = False
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Application.Calculation = xlManual
Application.CalculateBeforeSave = False
End Sub
And
Private Sub Workbook_Open()
Application.Calculation = xlManual
Application.CalculateBeforeSave = False
End Sub
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Application.Calculation = xlManual
Application.CalculateBeforeSave = False
End Sub
Please can someone show me where i am going wrong?
Please try:
Sub CF()
Cells.Select
Selection.FormatConditions.Add Type:=xlExpression, Formula1:= _
"=AND($B1>=LARGE($B:$B,10),ROW()<>1)"
Selection.FormatConditions(Selection.FormatConditions.Count).SetFirstPriority
With Selection.FormatConditions(1)
.Interior.Color = 255
.StopIfTrue = False
End With
End Sub
I am able to run the validation and change event trigger to work on one cell(reference here is M6). When the user select "Valid" or "Not Valid" from the dropdown list it should populate name of the user and date in adjacent columns(N6,O6), this is working fine if I am selecting the option from drop down or copying the value one cell at a time.
Somehow the macro does not work when I copy the value in multiple cells(M8:M10) at a time, nor it is working when using the autofill option to populate records in the cells of that column. Also tried to insert a non-valid data "adsadasdad
" in Cell M8, the validation worked, but when inserting non-valid data in multiple cells, validation is not working.
Please find the macro code
Private Sub Worksheet_Change(ByVal Target As Range)
Dim LastRow As Long
Set MainWB = ThisWorkbook
LastRow = MainWB.Worksheets("LDVC_data").Cells(MainWB.Worksheets("LDVC_data").Rows.Count, "A").End(xlUp).Row
For i = 2 To LastRow
If Target.Address = Range("M" & i).Address Then
If Range("M" & i) = "Valid" Or Range("M" & i) = "Not Valid" Then
ActiveSheet.Range("N" & i).Value = (Environ$("Username"))
ActiveSheet.Range("O" & i).Value = Now
ElseIf (Range("M" & i) = "[enter image description here][1]") Then
ActiveSheet.Range("N" & i).Clear
ActiveSheet.Range("O" & i).Clear
Else[enter image description here][1]
MsgBox ("Kindly enter valid value")
Range("M" & i) = ""
End If
End If
Next i
End Sub
Managed to capture the change and perform data validation in multiple cells.
Did following changes to the code.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim LastRow As Long
Set MainWB = ThisWorkbook
Dim myRange As Range
Set myRange = Target
LastRow = MainWB.Worksheets("LDVC_data").Cells(MainWB.Worksheets("LDVC_data").Rows.Count, "A").End(xlUp).Row
For Each targetCell In Target
For i = 2 To LastRow
If targetCell.Address = Range("M" & i).Address Then
If Range("M" & i) = "Valid" Or Range("M" & i) = "Not Valid" Then
ActiveSheet.Range("N" & i).Value = (Environ$("Username"))
ActiveSheet.Range("O" & i).Value = Now
ElseIf (Range("M" & i) = "") Then
ActiveSheet.Range("N" & i).Clear
ActiveSheet.Range("O" & i).Clear
Else
MsgBox ("Kindly enter valid value")
Range("M" & i) = ""
End If
End If
Next i
Next targetCell
End Sub
I have an excel sheet which need do format the data
I need to format this data like this in different sheet
Note - This a small sample I created for your understanding
my test macro is below. If you want to use it, you just need to rename your sheets - "DataSheet" for the one with the data and "ResultSheet" where the result will be stored.
Sub Reformat()
Dim letter As String
Dim iRow As Integer
Dim rng As Excel.Range
Sheets("ResultSheet").Range("A1:A" & Range("A1").End(xlDown).Row).Value = Range("A1:A" & Range("A1").End(xlDown).Row).Value
Sheets("ResultSheet").Select
Range("A1:A" & Range("A1").End(xlDown).Row).RemoveDuplicates Columns:=1, Header:=xlNo
Set rng = Range("A1:A" & Range("A1").End(xlDown).Row)
For i = 1 To Sheets("DataSheet").Range("A1").End(xlDown).Row
letter = Sheets("DataSheet").Range("A" & i).Value
iRow = WorksheetFunction.Match(letter, rng)
If Range("B" & iRow).Value = "" Then
Range("B" & iRow).Value = Sheets("DataSheet").Range("B" & i).Value
Else
Range("A" & iRow).End(xlToRight).Offset(0, 1).Value = Sheets("DataSheet").Range("B" & i).Value
End If
Next i
End Sub