Updating sheet automatically when a change is made - VBA Excel - excel

I struggling to work out the logic to this so any help would be appreciated!
I have a sheet with names and dates, on each row (in the example column D to F) it needs to find the greatest date and then add a date to a column (column C). I can get this to work on a single test row, but I need it to work when there is a change on any row.
B C D E F
Name Due Date Date 1 Date 2 Date 3
Dave 01-01-20 01-01-14 01-01-17
Sarah 01-01-21 01-02-11 01-02-15 01-02-18
The code I have so far is:
LastRow = wsCB.Cells(Rows.Count, "C").End(xlUp).Row
rowcount = 12
Max_date = Application.WorksheetFunction.Max(wsCB.Range(wsCB.Cells(rowcount, 5), wsCB.Cells(rowcount, 10)))
Max_date = CDate(Max_date)
DueDate = DateAdd("yyyy", 3, Max_date)
wsCB.Cells(12, 4) = DueDate
I have set it to call on a Worksheet_Change. I have tried various loops trying to use xlup but I'm not sure this is the right way to go about it as I need the value to be updated when the user has typed in a new date for someone. I can't quite work out how to scale this single line example to the whole sheet.
The data won't be massive, however there will be 5 sheets like this with up to a maximum of 70 names on each sheet.
I'm still quite new to VBA so any advice would be very helpful!

The following VBA code should achieve your desired results:
Private Sub Worksheet_Change(ByVal Target As Range)
Select Case Target.Column
Case 4, 5, 6 'if user entered data in columns D to F
Max_date = Application.WorksheetFunction.Max(Range(Cells(Target.Row, 4), Cells(Target.Row, 6)))
'get the max value in row from column D to F (4 to 6)
Max_date = CDate(Max_date)
DueDate = DateAdd("yyyy", 3, Max_date)
Cells(Target.Row, 3) = DueDate
End Select
End Sub

Try this.
You'll just need to adjust columns to fit your needs
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim MaxDate As Date, DueDate As Date
Dim CurRow As Long
Dim Ws As Worksheet
Set Ws = Target.Parent
CurRow = Target.Row
With Ws
MaxDate = CDate(Application.WorksheetFunction.Max(.Range(.Cells(CurRow, "D"),.Cells(CurRow, "F"))))
DueDate = DateAdd("yyyy", 3, MaxDate)
Application.EnableEvents = False
.Cells(CurRow, 3) = DueDate
Application.EnableEvents = True
End With
End Sub

My suggested code for your problem:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim xCellColumnD As Long
Dim xCellColumnE As Long
Dim xCellColumnF As Long
Dim xDueColumn As Long
Dim xRow As Long, xCol As Long
xCellColumnD = 4
xCellColumnE = 5
xCellColumnF = 6
xDueColumn = 3
xRow = Target.Row
xCol = Target.Column
If Target.Text <> "" Then
If xCol = xCellColumnD Or xCol = xCellColumnE Or xCol = xCellColumnF Then
Max_date = Application.WorksheetFunction.Max(Range(Cells(xRow, 4), Cells(xRow, 6)))
Max_date = CDate(Max_date)
DueDate = DateAdd("yyyy", 3, Max_date)
Cells(xRow, xDueColumn) = DueDate
End If
End If
End Sub

I suggest to use Intersect in combination with a loop over the Target range so you are a bit more save against pasting a whole range of values.
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim ws As Worksheet
Set ws = Target.Parent
If Not Intersect(Target, ws.Range("D:F")) Is Nothing Then
Dim MaxDate As Double
Dim DueDate As Variant
Dim iRow As Long
For iRow = Target.Row To Target.Row + Target.Rows.Count - 1
On Error Resume Next
MaxDate = Application.WorksheetFunction.Max(ws.Range(ws.Cells(iRow, "D"), ws.Cells(iRow, "F")))
If Err.Number <> 0 Then
DueDate = "#VALUE!"
ElseIf MaxDate = 0 Then
DueDate = vbNullString 'remove date if no dates
Else
DueDate = DateAdd("yyyy", 3, MaxDate)
End If
On Error GoTo 0
Application.EnableEvents = False 'prevents triggering change event again
ws.Cells(iRow, "C").Value = DueDate
Application.EnableEvents = True
Next iRow
End If
End Sub

Related

Cannot delete the cells with old data in them to make way for new data

In sheet 2 of my workbook, I have names of employees, the dates they came into work, the shifts they worked, and absenteeism. In sheet 1 of my code, I have a lookup sheet where I intend for the employee's name to be typed into a cell and to show all the dates and this person worked, along with the shift and absenteeism into the lookup sheet. I've tried a vriaty of things, but this is my current code:
Private Sub worksheet_change(ByVal Target As Range)
Dim Lookup As Worksheet
Dim Data As Worksheet
Dim LastRow As Long
Dim V As Range
Set Lookup = ThisWorkbook.Worksheets("Lookup")
Set Data = ThisWorkbook.Worksheets("Data")
Set V = Lookup.Range("A1:A2")
LastRow = Data.Cells(Rows.Count, "A").End(xlUp).Row
LookupCounter = 2
For i = 2 To LastRow
If Intersect(V, Target) Is Nothing Then
Lookup.Range("B2:C2000").Delete
ElseIf Lookup.Range("A2") = Data.Cells(i, 2) Then
Lookup.Cells(LookupCounter, 2).Value = Data.Cells(i, 1)
Lookup.Range("B2:B2000").NumberFormat = "mm/dd/yyyy"
Lookup.Cells(LookupCounter, 3).Value = Data.Cells(i, 9)
LookupCounter = LookupCounter + 1
End If
Next i
End Sub
My intention is for when a new name is typed, this clears the info from the columns of the lookup page, and inputs the new data for the new name. The second part of my code where I match the names to the dates works, but I am struggling with the clearing function. What can I do to fix it?
Option Explicit
Private Sub worksheet_change(ByVal Target As Range)
Dim Lookup As Worksheet, Data As Worksheet
Dim LastRow As Long, LookupCounter As Long, i As Long
With ThisWorkbook
Set Lookup = .Worksheets("Lookup")
Set Data = .Worksheets("Data")
End With
If Intersect(Lookup.Range("A1:A2"), Target) Is Nothing Then
Exit Sub
Else
' clear sheet
Lookup.Range("B2:C2000").Delete
LastRow = Data.Cells(Rows.Count, "A").End(xlUp).Row
LookupCounter = 2
' get data
For i = 2 To LastRow
If Data.Cells(i, 2) = Lookup.Range("A2") Then
Lookup.Cells(LookupCounter, 2).Value = Data.Cells(i, 1)
Lookup.Cells(LookupCounter, 3).Value = Data.Cells(i, 9)
LookupCounter = LookupCounter + 1
End If
Next
Lookup.Range("B2:B2000").NumberFormat = "mm/dd/yyyy"
End If
End Sub

How to insert current timestamp when cell value changes based on cell content

I have a drop-down list in a column B and I want to capture timestamps of changes made to that column based on different options available in drop-down and extract that timestamp to different columns
User can choose from the list and i have a macro that inputs timestamp of any change made.
Private Sub Worksheet_Change(ByVal Target As Range)
'Updated by Extendoffice 20180830
Dim xCellColumn As Integer
Dim xTimeColumn As Integer
Dim xRow, xCol As Integer
Dim xDPRg, xRg As Range
xCellColumn = 3
xTimeColumn =
xRow = Target.Row
xCol = Target.Column
If Target.Text <> "" Then
If xCol = xCellColumn Then
Cells(xRow, xTimeColumn) = Now()
Else
On Error Resume Next
Set xDPRg = Target.Dependents
For Each xRg In xDPRg
If xRg.Column = xCellColumn Then
Cells(xRg.Row, xTimeColumn) = Now()
End If
Next
End If
End If
End Sub
I am looking for a way to modify it, so that once user chooses "In-progress" timestamp is saved in column D, later that cell can be changed into "Closed" and timestamp of that should be presented in column E (respective row) but the value of Column D will stay the same.
I wanna trace timestamp of status changes.
You can use a switch for each of your outputs to determine the column (c), based on your change event, such that:
Private Sub Worksheet_Change(ByVal Target As Range)
If Intersect(Target, Columns(2)) Is Nothing Then Exit Sub
dim r as long, c as long
r = target.row
select case lcase(target.value)
case "in-progess"
c = 4
case "closed"
c = 5
case else
c = 0
end select
if c > 0 then cells(r,c).value = now()
end sub
untested code

If cell is updated, write cell value and timestamp in next blank row

I'm trying to update a column with timestamp and another column with updates (which got from a specific cell).
Desired behaviour:
A2 has xy written to it. That change triggers a macro, which puts timestamp in C column at row 2 and the update in D column row 2.
If new update is made in A2: If C2 is not empty, jump to C3 and put timestamp and put update on D3 and so on.
Unfortunately, it puts the first update timestamp and the update to the columns, but if I update again, it doesn't jump and put update there.
Error message and Excel macro
Excel sheet which I try to update.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim xCellColumn As Integer
Dim xCellRow As Integer
Dim xTimeColumn As Integer
Dim xTimeRow As Integer
Dim xUpdateColumn As Integer
Dim xUpdateRow As Integer
Dim xRow, xCol As Integer
xCellColumn = 2
xCellRow = 10
xTimeColumn = 6
xTimeRow = 2
xUpdateColumn = 7
xUpdateRow = 2
i = 2
xCol = Target.Column
xRow = Target.Row
If Target.Text <> "" Then
If xCol = xCellColumn Then
If xRow = xCellRow Then
Do While Range("Munka1").Cells(i, xTimeColumn).Value <> ""
i = i + 1
Loop
Cells(i, xTimeColumn) = Now()
Cells(i, xUpdateColumn) = Target.Value
End If
End If
End If
End Sub
Please try this:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim xCellColumn As Long
Dim xCellRow As Long
Dim xTimeColumn As Long
Dim xUpdateColumn As Long
Dim SupervisedArea As Range
Dim i As Integer
xCellColumn = 2
xCellRow = 10
xTimeColumn = 6
xUpdateColumn = 7
If Target.Text <> "" Then
' If any changed value in the whole column should generate a new data pair:
'Set SupervisedArea = Intersect(Target, Me.Columns(xCellColumn))
' If only one cell should be supervised:
Set SupervisedArea = Intersect(Target, Me.Cells(xCellRow, xCellColumn))
If Not SupervisedArea Is Nothing Then
i = 2
Do While Me.Cells(i, xTimeColumn).Value <> ""
i = i + 1
Loop
Application.EnableEvents = False
Me.Cells(i, xTimeColumn) = Now()
Me.Cells(i, xUpdateColumn) = Target.Value
Application.EnableEvents = True
End If
End If
End Sub

How do you make VBA run on multiple cells?

I am very new to VBA but pretty good at formulas. I am working on a time stamp issue. I have the code written so that if I choose from a validation list in E3 it will give me a time stamp in F3. I want this to be true of all cells in the E column starting with E3. I will have between 500 and 15000 records (rows). The code I am using is pasted below. Thanks in advance for any suggestions.
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Column = 5 And Target.Row = 3 Then
If Target.Value = "" Then
Cells(3, 6).Value = ""
Else
Cells(3, 6).Value = Format(Now, "mm/dd/yyyy HH:mm:ss")
End If
End If
End Sub
How's this?
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Column = 5 And Target.Row >= 3 Then
i = Target.Row
If Target.Value = "" Then
Cells(i, 6).Value = ""
Else
Cells(i, 6).Value = Format(Now, "mm/dd/yyyy HH:mm:ss")
End If
End If
End Sub
The fastest way to do this is to select the entire range a set the value once using an array. This is done with the .Value property of a Range when it contains multiple cells.
Private Sub SetDate(ByVal Target As Range, Optional bybal RowCount as Long = 0)
Dim i as Long
' Check if row count needs to be found
If RowCount = 0 Then
'Count non-empty rows from target down
RowCount = Target.Worksheet.Range(Target, Target.End(xlDown).Rows.Count
End If
' Target entire range of cells that are going to be affected
Set Target = Target.Resize(RowCount, 6)
Dim vals() as Variant
' Read values from worksheet
vals = Target.Values
' Make changes in memory here
For i=1 to RowCount
if IsEmpty(vals(i,1)) Then
vals(i, 6) = vbNullString
Else
vals(i, 6) = Format(Now, "mm/dd/yyyy HH:mm:ss")
End If
Next i
' Write values into worksheet
Target.Value = vals
End Sub

Find duplicates in a column and add their corresponding values from another column

I have column A with staff ids and hours worked in column K.
I would like if a staff id appears more than once to add hours worked and put the result in another column corresponding to the first instance of that staff id and the duplicates being 0.
This is for a monthly report and there may be over 2k records at any point.
As everyone else said, a Pivot Table really is the best way. If you're unsure how to use a PivotTable or what it's good for, refer to this SO post where I explain in detail.
Anyway, I put together the below VBA function to help get you started. It's by no means the most efficient approach; it also makes the following assumptions:
Sheet 1 has all the data
A has Staff Id
B has Hours
C is reserved for Total Hours
D will be available for processing status output
This of course can all be changed very easily by altering the code a bit. Review the code, it's commented for you to understand.
The reason a Status column must exist is to avoid processing a Staff Id that was already processed. You could very alter the code to avoid the need for this column, but this is the way I went about things.
CODE
Public Sub HoursForEmployeeById()
Dim currentStaffId As String
Dim totalHours As Double
Dim totalStaffRows As Integer
Dim currentStaffRow As Integer
Dim totalSearchRows As Integer
Dim currentSearchRow As Integer
Dim staffColumn As Integer
Dim hoursColumn As Integer
Dim totalHoursColumn As Integer
Dim statusColumn As Integer
'change these to appropriate columns
staffColumn = 1
hoursColumn = 2
totalHoursColumn = 3
statusColumn = 4
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
totalStaffRows = Sheet1.Cells(Rows.Count, staffColumn).End(xlUp).Row
For currentStaffRow = 2 To totalStaffRows
currentStaffId = Cells(currentStaffRow, staffColumn).Value
'if the current staff Id was not already processed (duplicate record)
If Not StrComp("Duplicate", Cells(currentStaffRow, statusColumn).Value, vbTextCompare) = 0 Then
'get this rows total hours
totalHours = CDbl(Cells(currentStaffRow, hoursColumn).Value)
'search all subsequent rows for duplicates
totalSearchRows = totalStaffRows - currentStaffRow + 1
For currentSearchRow = currentStaffRow + 1 To totalSearchRows
If StrComp(currentStaffId, Cells(currentSearchRow, staffColumn), vbTextCompare) = 0 Then
'duplicate found: log the hours worked, set them to 0, then mark as Duplicate
totalHours = totalHours + CDbl(Cells(currentSearchRow, hoursColumn).Value)
Cells(currentSearchRow, hoursColumn).Value = 0
Cells(currentSearchRow, statusColumn).Value = "Duplicate"
End If
Next
'output total hours worked and mark as Processed
Cells(currentStaffRow, totalHoursColumn).Value = totalHours
Cells(currentStaffRow, statusColumn).Value = "Processed"
totalHours = 0 'reset total hours worked
End If
Next
Application.ScreenUpdating = False
Application.Calculation = xlCalculationAutomatic
End Sub
BEFORE
AFTER
Here is the solution for the data table located in range A1:B10 with headers and results written to column C.
Sub Solution()
Range("c2:c10").Clear
Dim i
For i = 2 To 10
If WorksheetFunction.SumIf(Range("A1:a10"), Cells(i, 1), Range("C1:C10")) = 0 Then
Cells(i, "c") = WorksheetFunction.SumIf( _
Range("A1:a10"), Cells(i, 1), Range("B1:B10"))
Else
Cells(i, "c") = 0
End If
Next i
End Sub
Try below code :
Sub sample()
Dim lastRow As Integer, num As Integer, i As Integer
lastRow = Range("A65000").End(xlUp).Row
For i = 2 To lastRow
num = WorksheetFunction.Match(Cells(i, 1), Range("A1:A" & lastRow), 0)
If i = num Then
Cells(i, 3) = WorksheetFunction.SumIf(Range("A1:A" & lastRow), Cells(i, 1), Range("B1:B" & lastRow))
Else
Cells(i, 1).Interior.Color = vbYellow
End If
Next
End Sub
BEFORE
AFTER
Below code identifies duplicate value in a column and highlight with red. Hope this might be of some help.
iLastRow = Cells(chosenExcelSheet.Rows.Count, 1).End(xlUp).Row 'Determine the last row to look at
Set rangeLocation = Range("A1:A" & iLastRow)
'Checking if duplicate values exists in same column
For Each myCell In rangeLocation
If WorksheetFunction.CountIf(rangeLocation, myCell.Value) > 1 Then
myCell.Interior.ColorIndex = 3'Highlight with red Color
Else
myCell.Interior.ColorIndex = 2'Retain white Color
End If
Next
Sub SelectColoredCells()
Dim rCell As Range
Dim lColor As Long
Dim rColored As Range
'Select the color by name (8 possible)
'vbBlack, vbBlue, vbGreen, vbCyan,
'vbRed, vbMagenta, vbYellow, vbWhite
lColor = RGB(156, 0, 6)
'If you prefer, you can use the RGB function
'to specify a color
'Default was lColor = vbBlue
'lColor = RGB(0, 0, 255)
Set rColored = Nothing
For Each rCell In Selection
If rCell.Interior.Color = lColor Then
If rColored Is Nothing Then
Set rColored = rCell
Else
Set rColored = Union(rColored, rCell)
End If
End If
Next
If rColored Is Nothing Then
MsgBox "No cells match the color"
Else
rColored.Select
MsgBox "Selected cells match the color:" & _
vbCrLf & rColored.Address
End If
Set rCell = Nothing
Set rColored = Nothing
End Sub
this highlights the duplicates

Resources