Counting the number of times a value has changed within a cell - vba

I'm evaluating a column of cells, for example A:A, and every time a value within a cell changes (not including the initial value), I want to be able to log the change to the appropriate cell of another column, say B:B.
The following is a pair of before and after screenshots demonstrating what is required:
A2has been updated once, so B2 should show a count of 1 and A6 has been updated twice, so B6 should show 2.
A similar solution can be found here, however this only applies to one cell:
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Address = "$A$1" Then [A2].Value = 1
End Sub
Cell values aren't connected to different sheets and can be hard coded.

None of the solutions in your link are both particularly good and comprehensive enough to modify, in my opinion, so I will be posting a better one.
Also your requirements are also not quite specific enough, so I've made up a few extra ones.
Assumptions:
All the A:A cell values start off empty. (Can be trivially modified to allow non-empty initial values.)
An edit of a cell that results in the same value is still considered a "change". This would normally also include where the cell was initially empty, but my code specifically excludes this edge case.
The "initial" value is the value after the first "change" to the cell. (Can be trivially modified to allow the initial value to be "empty", if that is the actual requirement.)
This is the relatively simple code:
'============================================================================================
' Module : <The appropriate sheet module>
' Version : 1.0
' Part : 1 of 1
' References : N/A
' Source : https://stackoverflow.com/a/47405528/1961728
'============================================================================================
Option Explicit
Private Sub Worksheet_Change _
( _
ByVal Target As Range _
)
Const s_CheckColumn As String = "A:A"
Const s_CountColumn As String = "B:B"
If Intersect(Target, Range(s_CheckColumn)) Is Nothing Then Exit Sub
Dim rngCell As Range
For Each rngCell In Intersect(Target, Range(s_CheckColumn))
With Range(s_CountColumn).Cells(rngCell.Row)
.Value2 = IIf(.Value2 <> vbNullString, .Value2 + 1, IIf(rngCell.Value2 <> vbNullString, 0, vbNullString))
End With
Next rngCell
End Sub
This solution correctly allows for multiple cells to be edited simultaneous, as happens when you do a multi-cell delete/paste or a fill.
Notes:
The initial "change" results in a count of 0. If you wish to hide these zeroes, set a custom number format of #;; for the count column.
The code can be modified to stop a "same value" edit from counting as a "change". One of the simplest ways of doing this is to use an extra column to store the old values.
Caveats:
Undo will no longer work correctly. This can be rectified, but it is not trivial to do so.
Pressing an ESC after starting an edit is required to cancel the edit and not trigger an update.

Put this in your worksheet code after you've entered the initial values, or add code to check if this is the initial value or not.
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Range("A:A")) Is Nothing Then
Range("B" & Target.Row) = Range("B" & Target.Row) + 1
End If
End Sub

Related

Insert variable string into formula

Been working on this for a while, and can't seem to figure it out. I feel like I'm almost there, thanks to different posts in here. Here is what I am trying to do:
I have a dependent list that I would like to change to it's first value based on the selection of the first list. I found a code here, that I modified to work for my sheet, but I had to change all the names in the first list to conform to the Define Name rules(ie. no spaces). This works, and I can leave it at that, but I would prefer to have the spaces in my list. Whenever I use the formulas I was using to change the name shown in the list, the VBA script no longer works. I feel that it is because it is not seeing a change in the target cell, although the value is changing, the formula in the cell is not. Is that an accurate statement? If so, what can I change to make it work the way I want it to? Right now, the dependent list is running off =INDIRECT("$B$5) but I would like it to run off =INDIRECT('Store Data'!$A$23) which contains a VLOOKUP formula.
Here is the VBA script I am using now:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rng(1) As Range, rng1 As Range
Set rng(0) = Range("B5") 'your primary selection
Set rng(1) = Range("G5") 'your secondary selection range
Application.EnableEvents = False
If Not Intersect(Target, rng(0)) Is Nothing Then 'if you have changed your primary selection
For Each rng1 In rng(1) 'each cell in your secondary selection
i = i + 1
rng1 = Worksheets("SD Names").Range("" & rng(0).Value2)(i, 1) 'gets changed to the nth value in the indirect reference of the primary selection ("TeamA" 's second row is "MemberA2" for example)
Next
End If
Application.EnableEvents = True
End Sub
Hope this is enough detail, thank you for any help!

Is there a way to capture the content of the changed cells in Excel using VBA?

There is a table in a worksheet. I want to use VBA to capture any changes made to that table. Is this possible?
For example, C4 = 14 at the beginning. If user changes to C4 = 18, I want to know which cell is changed (C4 in this case) and what has it been changed to (18 in this case).
For advanced feature, if user adds a row, changing C4 to C5 (add a row above row 4), and add C4 = 11 (new C4). Could this be captured? How about deleting original row 4? Could the deleted content be captured?
Any help is appreciated.
Thanks
Unfortunately the answer's no, not really. By the time the worksheet change event is triggered, the target's value is the new value and the old value is lost.
I did once read of someone maintaining a hidden workbook that kind of mirrored every action that was done to the target workbook and could hence be used to track the changes. But obviously this is a lot of effort.
An alternative might be to try something using Application.Undo. So, from the worksheet change event, record what the value is, apply Application.Undo, record that value, then replace with the first value. It's really messy though.
Say our table is A1 through D4. Enter the following code into the worksheet code area:
Public OldValue As Variant
Private Sub Worksheet_Change(ByVal Target As Range)
If Intersect(Range("A1:D4"), Target) Is Nothing Then
Else
If Target.Count = 1 Then
MsgBox "Previous Value: " & OldValue & vbCrLf & "New value: " & Target.Value
End If
End If
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Intersect(Range("A1:D4"), Target) Is Nothing Then
Else
If Target.Count = 1 Then
OldValue = Target.Value
End If
End If
End Sub
This will work for simple single cell click and type edits. SelectionChange detects and records the old value. Change displays both the old value and the new value.
This will not work for multi-cell copy/pastes.

copy row to next free row on another spreadsheet on change

First off, I'm a noob when it comes to Macros and VBA, so please forgive me if I don't make sense.
I've got an Excel spreadsheet which is basically a list of users and their mobile phone numbers and some other bits (columns A-K are currently used) and it's ordered by rows.
What I need is a way of copying the whole row if I change a cell. So if I change the username, it copies the whole row of that user to the next blank row on a second sheet.
The purpose of this is to keep an audit trail allowing us to see who's previously used a number etc.
I found this: Copy row to another sheet in excel using VBA which is working as intended, but I can't for the life of me get it to a, copy the cells to the next free row, or b, not overwrite the existing entry.
This is the code I'm using:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim a As Range, rw As Range
For Each a In Selection.Areas
For Each rw In a.Rows
If rw.Row >= 2 Then
rw.EntireRow.Copy Sheet2.Cells(2 + (rw.Row - 2) * 3, 1)
End If
Next rw
Next a
End Sub
I'd really appreciate it if someone could help me customise it.
I'm using Excel 2010 on Win7.
Many thank in advance.
Typically the Intersect method is used to determine if the cell or cells receiving a change involve one or more columns that you are concerned with. You can add additional parameters; in this case, I've .Offset the Worksheet.UsedRange property down one row to make sure that row 1 is not involved.
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Columns(1), Me.UsedRange.Offset(1, 0)) Is Nothing Then
On Error GoTo bm_Safe_Exit
Application.EnableEvents = False 'not really necessary in this case but never a bad idea within a Worksheet_Change
Dim a As Range
For Each a In Intersect(Target, Columns(1), Me.UsedRange.Offset(1, 0))
If CBool(Len(a.Value2)) Then _
a.EntireRow.Copy _
Destination:=Sheet2.Cells(Rows.Count, 1).End(xlUp).Offset(1, 0) 'not really sure this is the correct destination
Next a
End If
bm_Safe_Exit:
Application.EnableEvents = True
End Sub
I've included a call to disable event handling for the duration of the Worksheet_Change event macro. While this is a critical step when the Worksheet_Change modifies values, it is not really important to incorporate here. However, it does not harm and is already in place in case you want to augment the Worksheet_Change to include something like a timestamp that would change the values on the worksheet.

Worksheet_Change determine value not content

I am trying to detect if there are changes in a cell value, not particularly the cell contents. I have found multiple solutions to find out if a cell contents has changed, but it does not work when a cell is equal to another cell.
For example, I have cell A1 set to equal B1 and then B1 has a formula that calls in multiple other cells, so I am not able to go back to the beginning and determine whether the cell has changed from that. It needs to come directly from A1.
This is one of the examples I found on this site, but does not determine if the value of A1 has changed, just whether the contents has changed.
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Column = 1 Then
Cells(Target.Row, 3).Value = Date
End If
End Sub
The function application.volatile TRUE at the top of your sub will make your sub calculate each time any value in Excel changes. So then you need a global variable which stores the last-known value of your specified range, and any time the sub runs, start with an
If new_cell_value <> stored_global_variable then...
and close with
stored_global_variable = new_cell value'
End If
See here for further info [h/t to vzczc for the original answer and method]: Refresh Excel VBA Function Results

Run VBA code in Excel based on a cell value change by a function

Kindly I need help after trying for hours myself.
I have this code to run a code base on a cell value change, but works only if I type myself the change.
I need to be automatic with the function I have inside the cell, but I can get it right.
This is the code I have now that works well manually:
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Address = "$A$1" Then
Application.EnableEvents = False
Range("D10").Select
Selection.ClearContents
Application.EnableEvents = True
End If
End Sub
Anything that I should add to make it automatic with the formula inside?
This is a tricky one since there is no built in worksheet event that triggers when a particular cell has a value change due to a formula. There is, however, a Worksheet.Calculate event that could be used.
First we make a global string variable. This is a variable that is declared outside of a subroutine or function. It will hold it's value between executions of subroutines and functions. So we will declare it, and then set it to the value of A1 when A1 is changed.
Dim lastValue As String
Private Sub Worksheet_Calculate()
If Range("A1").Value <> lastValue Then
lastValue = Range("A1").Value
Range("D10").ClearContents
End If
End Sub
lastValue here is our Global that will hold the last known value of A1. When worksheet.calculate is triggered it will compare that value against A1 to see if it's changed, if it is then it executes your code to clear the contents of D1
Instead of a Global you could also just stick that last known value of A1 into a Cell somewhere too. Then do you comparison to that. The advantage of using a cell over a global variable, is that the cell value will hold it's value even if you close and reopen the sheet. So, depending on your needs, it may be a better option:
Private Sub Worksheet_Calculate()
lastValue = Range("Z10").value
If Range("A1").Value <> lastValue Then
Range("Z10").value = Range("A1").Value
Range("D10").ClearContents
End If
End Sub
Where Range Z10 is the spot that holds the last known value.
Lastly I reduced your code to clear contents of D10 to a single line. Anytime you see Something.Select and then in the next line see Selection.DoSomething you can just get rid of that Select/Selection bit. Selecting a cell is generally something that only a human needs to do to interact with the sheet. VBA can do whatever it likes to cells without have to Select them.