Display old and new values for a cell - vba

I have a worksheet that logs changes that uses have made to cells. It goes as follows
Public OldVal As String
Public NewVal As String
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Target.Cells.Count > 1 Or IsEmpty(Target) Then Exit Sub
OldVal = Target.Value
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
Dim LDate As String
If Target.Cells.Count > 1 Then Exit Sub
NewVal = Target.Value
Sheets("corrections").Cells(Rows.Count, "A").End(xlUp)(2).Value = Now & "_Sheet " & ActiveSheet.Name & " Cell " & Target.Address(0, 0) & " was changed from '" & OldVal & "' to '" & NewVal & "'"
OldVal = ""
NewVal = ""
End Sub
The problem im having is that for some reason it will never display the previous value. it will output it only as Sheet FA Cell B5 was changed from '' to '12' even if say for example 10 was in the cell prviously.
I also was curious to know is there a way that you can have it so that this code is not running at all times. Id prefer to have a button you click and at that point it will initiate and start logging changes.
Thanks

I got your posted code to work with a very small change:
Public OldVal As String
Public NewVal As String
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Target.Cells.Count > 1 Or IsEmpty(Target) Then Exit Sub
OldVal = Target.Value
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
Dim LDate As String
If Target.Cells.Count > 1 Then Exit Sub
NewVal = Target.Value
Application.EnableEvents = False
Sheets("corrections").Cells(Rows.Count, "A").End(xlUp)(2).Value = Now & "_Sheet " & ActiveSheet.Name & " Cell " & Target.Address(0, 0) & " was changed from '" & OldVal & "' to '" & NewVal & "'"
Application.EnableEvents = True
OldVal = ""
NewVal = ""
End Sub
For your second question, start with:
Application.EnableEvents = False
Hook your button onto a macro like this:
Sub StartLogging()
Application.EnableEvents = True
End Sub

Your code is working fine for me. As for the enable/disable macro, you just need to insert this line before (/above) each IF (in both of your macros). Optional check for a more appropriate cell to store the Yes/No option (rather than "X1"):
If Sheets("corrections").Range("X1") <> "Yes" Then Exit Sub
' where you can change X1 for a more appropriate cell
To create the buttons just add the shapes/objects and assign the macros below:
Sub Enable_Logs()
Sheets("corrections").Range("X1").Value = "Yes"
End Sub
Sub Disable_Logs()
Sheets("corrections").Range("X1").Value = "No"
End Sub
Note! To add buttons with macro assigned to them: press Alt + N, +SH and select a shape. Then, right click on the shape > Assign Macro (and select the corresponding macros). Note! for the 1st time you run the macro, you should manually set the "Yes" value in cell X1.

Thr problem why my OldVal was not showing up was that it was being held in array. So when I told it to look at OldVal(1, 1) it works just as it should. Thanks for the help. The final working code is:
Public OldVal As String
Public NewVal As String
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Sheets("corrections").Range("G1") <> "Yes" Then Exit Sub
OldVal = Target.Value2
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
If Sheets("corrections").Range("G1") <> "Yes" Then Exit Sub
If Target.Cells.CountLarge > 1 Then Exit Sub
NewVal = Target.Value
Sheets("corrections").Cells(Rows.Count, "A").End(xlUp)(2).Value = Now & "_Sheet " & ActiveSheet.Name & " Cell " & Target.Address(0, 0) & " was changed from '" & OldVal(1, 1) & "' to '" & NewVal & "'"
OldVal = ""
NewVal = ""
End Sub

Related

VBA Macro triggering too often

My worksheet is set up with data validation dropdowns and I am wanting a macro to ONLY trigger when the value of the cell is changed from another value in the dropdown, not from the default "empty" value.
Here is what I am trying to use:
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Column = 5 Then
If IsEmpty(Target.Value) = True Then
MsgBox "Test1"
Else
MsgBox "Test2"
End If
End If
exitHandler:
Application.EnableEvents = True
Exit Sub
End Sub
My problem is that this "IsEmpty" command is reading the cell AFTER the selection not before. I want it to read what the cells value was BEFORE the selection not after.
How can I do this?
Example approach:
Const COL_CHECK As Long = 5
Private oldVal
Private Sub Worksheet_Change(ByVal Target As Range)
Dim c As Range
Set c = Target.Cells(1) '<< in case multiple cells are changed...
If c.Column = COL_CHECK Then
If oldVal <> "" Then
Debug.Print "changed from non-blank"
Else
Debug.Print "changed from blank"
End If
End If
exitHandler:
Application.EnableEvents = True
Exit Sub
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim c As Range
Set c = Target.Cells(1)
oldVal = IIf(c.Column = COL_CHECK, c.Value, "")
Debug.Print "oldVal=" & oldVal
End Sub
Another approach:
This will need one cell per validation-dropdown:
Function ValChange(Cell2Follow As Range) As String
ValChange = ""
If Len(Application.Caller.Text) = 0 Then Exit Function
If Application.Caller.Text = Cell2Follow.Text Then Exit Function
MsgBox "value of the cell is changed from another value in the dropdown" & vbLf & "not from the default 'empty' value"
End Function
in a different cell, assumed the dropdown is in E6:
=E6&ValChange(E6)
application.caller.text will be the old value
(calculation must be automatic)

Private Sub Worksheet_SelectionChange/Worksheet_Change

Good day, I need to create 2 private macros in one workbook - one which stores content of cell after clicking on it and second one which will store the new value of cell and send and email with body of old text in cell and new text in cell.
Truth to be told, I'm not sure if this is the right way to do it (or if it's even possible) but I don't work with private macros often so I will appreciate any help. Thanks a lot!
That is what i got so far:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Target.Cells.Count > 1 Then Exit Sub
OldCellValue = ActiveCell.text
old_value = OldCellValue
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
Dim Area As Range
Dim OutlApp As Object
Dim IsCreated As Boolean
Dim cell As String
Dim old_value As String
Dim new_value As String
Set Area = Range("A1:E20")
If Target.Cells.Count > 1 Then Exit Sub
If Not Intersect(Target, Area) Is Nothing Then
cell = ActiveCell.Address
new_value = ActiveCell.text
On Error Resume Next
Set OutlApp = GetObject(, "Outlook.Application")
If Err Then
Set OutlApp = CreateObject("Outlook.Application")
IsCreated = True
End If
OutlApp.Visible = True
On Error GoTo 0
With OutlApp.CreateItem(0)
.Subject = "Change in table"
.to = "someones email"
.HTMLBody = "Change in cell " & "<B>" & cell & "</B><br>" _
& "Old value: " & old_value & "New value: " & new_value
On Error Resume Next
.Send
Application.Visible = True
On Error GoTo 0
End With
If IsCreated Then OutlApp.Quit
Set OutlApp = Nothing
End With
End If
End Sub
Not re-writing all your code, but in essence you have to do this to store the value when the cell is selected and then after it is changed. You don't need the newcellvalue variable as Target captures that.
Dim OldCellValue
Private Sub Worksheet_Change(ByVal Target As Range)
Dim newcellvalue
newcellvalue = Target.Value
MsgBox "Old " & OldCellValue & ", New " & newcellvalue
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
OldCellValue = Target.Value
End Sub
I think you're on the right track. I would use a global variable to track the current/old value so you can compare it in the Worksheet_Change event.
Something like this:
Private old_value As String
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Target.Cells.Count > 1 Then Exit Sub
old_value = Target.Text
'Debug to check the old_value
'Debug.Print "old_value = " + old_value
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
Dim Area As Range
Dim OutlApp As Object
Dim IsCreated As Boolean
Dim cell As String
Dim new_value As String
Set Area = Range("A1:E20")
If Target.Cells.Count > 1 Then Exit Sub
If Not Intersect(Target, Area) Is Nothing Then
new_value = Target.Text
'Debug to compare values
'Debug.Print "new_value = " + new_value
'Debug.Print "old_value = " + old_value
If new_value <> old_value Then
'Debug to compare
'Debug.Print "new_value and old_value are different"
End If
End If
End Sub
Keep in mind that the Worksheet_SelectionChange event is going to fire every time you navigate between cells. So, if you change the value of a cell and press the Enter key, the value of old_value is going to change because you are resetting the value in the Worksheet_SelectionChange event. You need to perform the comparison and send the email before the selection changes.
Also, you'll probably want to use .Value for the cell instead of .Text. See this post for the differences: What is the difference between .text, .value, and .value2?

Insert code in Excel workbook in "ThisWorkbook" using VBA

I need help with inserting a sizeable code into "ThisWorkbook" module in Excel using VBA.
Using the code below I'm able to insert the code into "ThisWorkbook" module, but this method (as I've learned recently) has limitations of 24 lines due to line beak (& _).
Sub AddCode()
Dim VBP As Object
Dim newmod As Object
Set VBP = ActiveWorkbook.VBProject
Set newmod = VBP.VBComponents.Add(1)
Dim StartLine As Long
Dim cLines As Long
With ActiveWorkbook.VBProject.VBComponents("ThisWorkbook").CodeModule
cLines = .CountOfLines + 1
.InsertLines cLines, _
"Private Sub Workbook_Open()" & Chr(13) & _
" Application.Calculation = xlManual" & Chr(13) & _
" Application.CalculateBeforeSave = False" & Chr(13) & _
" Application.DisplayFormulaBar = False" & Chr(13) & _
"Call Module1.ProtectAll" & Chr(13) & _
"End Sub"
End With
End Sub
The code I want to inject in addition to the code above is below (code found on another site). This allows me to track changes on the workbook that I share with others. I do not want to use Excel's built-in "Track Changes" feature.
Dim vOldVal
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Dim bBold As Boolean
If Target.Cells.Count > 1 Then Exit Sub
On Error Resume Next
With Application
.ScreenUpdating = False
.EnableEvents = False
End With
If IsEmpty(vOldVal) Then vOldVal = "Empty Cell"
bBold = Target.HasFormula
With Sheet1
.Unprotect Password:="Passcode"
If .Range("A1") = vbNullString Then
.Range("A1:E1") = Array("CELL CHANGED", "OLD VALUE", _
"NEW VALUE", "TIME OF CHANGE", "DATE OF CHANGE")
End If
With .Cells(.Rows.Count, 1).End(xlUp)(2, 1)
.Value = Target.Address
.Offset(0, 1) = vOldVal
With .Offset(0, 2)
If bBold = True Then
.ClearComments
.AddComment.Text Text:= _
"Note:" & Chr(10) & "" & Chr(10) & _
"Bold values are the results of formulas"
End If
.Value = Target
.Font.Bold = bBold
End With
.Offset(0, 3) = Time
.Offset(0, 4) = Date
End With
.Cells.Columns.AutoFit
.Protect Password:="Passcode"
End With
vOldVal = vbNullString
With Application
.ScreenUpdating = True
.EnableEvents = True
End With
End Sub
Private Sub Workbook_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
vOldVal = Target
End Sub
How can I achieve this? What is the best and the most efficient way to do this?
I've tried splitting the code in chunks of 20 lines and create 3 "AddCode" sub-routines, but I get an error at "bBold = Target.HasFormula". I have searched the web for alternatives, but nothing seems to be working.
Thanks in advance.
This is an abbreviated version of what I'm doing to create on load code. I create the onload event, then add a new module.
Sub AddOnload()
''Create on load sub
With ActiveWorkbook.VBProject.VBComponents("ThisWorkbook").CodeModule
.InsertLines 1, "Private Sub Workbook_Open()"
.InsertLines 2, " call CallMe"
.InsertLines 3, "End Sub"
End With
Call CreateCode
End Sub
''Add new module with code
Sub CreateCode()
Dim vbp As VBProject
Dim vbc As VBComponent
Dim strCode
Set vbp = Application.VBE.ActiveVBProject
Set vbc = vbp.VBComponents.Add(vbext_ct_StdModule)
vbc.Name = "tracker"
strCode = "Sub CallMe()" & vbCrLf & "End Sub"
vbc.CodeModule.AddFromString strCode
End Sub

VBA - Change color of modified text

I have this code that changes the color of the text in a cell if it is modified. However I was looking into something that only changes the color of modified text inside the cell. For example I have in cell A1 = "This cell" and when I change it to "This cell - this is new text" I would like just to change the color of "- this is new text"
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Range("A1:A100")) Is Nothing Then
If Target.Font.ColorIndex = 3 Then
Target.Font.ColorIndex = 5
Else
Target.Font.ColorIndex = 3
End If
End If
End Sub
Thanks
Here's what I put together:
Dim oldString$, newString$
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Range("A1:A100")) Is Nothing Then
newString = Target.Value
If Target.Font.ColorIndex = 3 Then
Target.Font.ColorIndex = 5
Else
Target.Font.ColorIndex = 3
End If
End If
Debug.Print "New text: " & newString
color_New_Text oldString, newString, Target
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Excel.Range)
If Not Intersect(Target, Range("A1:A100")) Is Nothing Then
oldString$ = Target.Value
Debug.Print "Original text: " & oldString$
End If
End Sub
Sub color_New_Text(ByVal oldString As String, ByVal newString As String, ByVal theCell As Range)
Dim oldLen&, newLen&, i&, k&
oldLen = Len(oldString)
newLen = Len(newString)
Debug.Print newString & ", " & oldString
For i = 1 To newLen
If Mid(newString, i, 1) <> Mid(oldString, i, 1) Then
Debug.Print "different"
Debug.Print theCell.Characters(i, 1).Text
If theCell.Characters(i, 1).Font.ColorIndex = 3 Then
theCell.Characters(i, 1).Font.ColorIndex = 5
Else
theCell.Characters(i, 1).Font.ColorIndex = 3
End If
End If
Next i
End Sub
It's two global variables, a Worksheet_SelectionChangeand Worksheet_Changeto get the strings.
It is laborious:
detect that a cell has changed in the range of interest
use UnDo to get the original contents
use ReDo to get the new contents
compare them to get the changed characters
use the Characters property of the cell to format the new characters
I would use UnDo to avoid keeping a static copy of each of the 100 cells.
using the tip from Gary's Student, I retain the old value of cell and compare with the new value. Then use the lenght to get the 'difference' and color the 'characters'. Here's the modification:
Option Explicit
Public oldValue As Variant
Public Sub Worksheet_SelectionChange(ByVal Target As Range)
oldValue = Target.Value
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
Dim oldColor
If Not Intersect(Target, Range("A1:A100")) Is Nothing Then
If Target.Value <> oldValue Then
oldColor = Target.Font.ColorIndex
Target.Characters(Len(oldValue) + 1, Len(Target) - Len(oldValue)).Font.ColorIndex = IIf(oldColor = 3, 5, 3)
End If
End If
End Sub
P.S. Sorry my english
This will change the font, but it's not perfect. Seems if you have different font colours in the same cell then Target.Font.ColorIndex returns NULL so it only works on the first change.
Option Explicit
Dim sOldValue As String
Private Sub Worksheet_Change(ByVal Target As Range)
Dim sNewValue As String
Dim sDifference As String
Dim lStart As Long
Dim lLength As Long
Dim lColorIndex As Long
On Error GoTo ERROR_HANDLER
If Not Intersect(Target, Range("A1:A100")) Is Nothing Then
sNewValue = Target.Value
sDifference = Replace(sNewValue, sOldValue, "")
lStart = InStr(sNewValue, sDifference)
lLength = Len(sDifference)
If Target.Font.ColorIndex = 3 Then
lColorIndex = 5
Else
lColorIndex = 3
End If
Target.Characters(Start:=lStart, Length:=lLength).Font.ColorIndex = lColorIndex
End If
On Error GoTo 0
Exit Sub
ERROR_HANDLER:
Select Case Err.Number
'I haven't added error handling - trap any errors here.
Case Else
MsgBox "Error " & Err.Number & vbCr & _
" (" & Err.Description & ") in procedure Sheet1.Worksheet_Change."
End Select
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Not Intersect(Target, Range("A1:A100")) Is Nothing Then
sOldValue = Target.Value
End If
End Sub
Edit: It will only work with a continuous string. Maybe can change to look at each character in sOldValue and sNewValue and change colour as required.
Try with below
Private Sub Worksheet_Change(ByVal Target As Range)
Dim newvalue As String
Dim olvalue As String
Dim content
Application.EnableEvents = False
If Not Intersect(Target, Range("A1:A100")) Is Nothing Then
If Target.Font.ColorIndex <> -4105 Or IsNull(Target.Font.ColorIndex) = True Then
newvalue = Target.Value
Application.Undo
oldvalue = Target.Value
Content = InStr(newvalue, Replace(newvalue, oldvalue, ""))
Target.Value = newvalue
With Target.Characters(Start:=Content, Length:=Len(newvalue)).Font
.Color = 5
End With
Else
Target.Font.ColorIndex = 3
End If
End If
Application.EnableEvents = True
End Sub

saving global variable from private sheet

I am trying to send out an email with updates when the the sheet are saved. To do this I am tracking changes and then trying to save these changes as a global string:
Public outString As String
Public Sub Worksheet_Change(ByVal Target As Range)
Dim colN, rowN As Integer
Dim changeHeading As String
Dim drawingNumber, partNumber As Integer
'Do nothing if more than one cell is changed or content deleted
If Target.Cells.Count > 1 Or IsEmpty(Target) Then Exit Sub
'Stop any possible runtime errors and halting code
On Error Resume Next
Application.EnableEvents = False
colN = Target.Column
rowN = Target.Row
changeHeading = ThisWorkbook.Sheets("List").Cells(1, colN).Value 'Header of the changed cell
partNumber = ThisWorkbook.Sheets("List").Cells(rowN, 2).Value 'Partnumber changed
drawingNumber = ThisWorkbook.Sheets("List").Cells(rowN, 4).Value 'Drawingnumber changed
outString = outString & vbNewLine _
& "PartNumber: " & partNumber & " DrawingNumber: " & drawingNumber _
& " " & changeHeading & ": " & Target & vbNewLine
'Turn events back on
Application.EnableEvents = True
'Allow run time errors again
On Error GoTo 0
End Sub
So this piece of code works nice except if I alter several column on the same row then each change will be presented on a new line instead of the same line, Maybe i have to use a dictionary with partnumber as key to avoid this.
Then in thisworkbook sheet i have the following code
Public outString
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
Call track
Call missingDrawings
Call updateText
End Sub
However now the outString variable is , so what did I do wrong when declaring the global variable outString?
You seem to have two variables called outString one in the worksheet and one in the workbook. You should only have one. If you leave the one in thisWorkbook (adding As String would be a good idea), then you can access it from the sheet by using ThisWorkbook.outString.