Worksheet_SelectionChange - Timestamp in different column - vba

I am using the code below to time stamp my employees priority list when they change a task to Completed. The code works fine but has to be replicated for each cell that I want to track the changes in.
Ideally, I would like the code to have the exact same functionality but compressed so that I can have it look at a large range, M5:M2500, and if cell M250 is changed to Completed have it look through Y5:Y500 and paste the time stamp in cell Y250.
Hopefully this make sense and thanks for any suggestions!
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Address = "$M$5" Then
Call Complete5
End If
If Target.Address = "$M$6" Then
Call Complete6
End If
End Sub
Sub Complete5()
ActiveSheet.Unprotect Password:="unlock"
If InStr(1, Range("$M$5"), "Completed") > 0 Then
Range("$Y$5").Select
ActiveCell.FormulaR1C1 = "=NOW()"
ActiveCell.Select
Selection.Copy
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
Application.CutCopyMode = False
Range("$M$5").Select
Else
Range("$Y$5").Select
ActiveCell.ClearContents
Range("$M$5").Select
End If
End Sub
Sub Complete6()
ActiveSheet.Unprotect Password:="unlock"
If InStr(1, Range("$M$6"), "Completed") > 0 Then
Range("$Y$6").Select
ActiveCell.FormulaR1C1 = "=NOW()"
ActiveCell.Select
Selection.Copy
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
Application.CutCopyMode = False
Range("$M$6").Select
Else
Range("$Y$6").Select
ActiveCell.ClearContents
Range("$M$6").Select
End If
End Sub

You can do this very cleanly right within the Worksheet_Change event itself. This code will evaluate the row in M that was changed and modify the corresponding row in Y accordingly and will also work if a user marks several rows complete at the same time (Ctrl + Enter). Warning, it will not fire when a user pastes a value into the cell.
Also, pay close attention to how I removed all the .Select and .Activate statements and worked directly with the objects themselves.
Private Sub Worksheet_Change(ByVal Target As Range)
With Me
If Not Intersect(Target, .Range("M5:M2500")) Is Nothing Then
Application.EnableEvents = False
.Unprotect Password:="unlock"
Dim rng As Range, cel As Range
Set rng = Target
For Each cel In rng
If InStr(1, cel, "Completed") Then
'use offset of 12 columns to get to column "Y"
cel.Offset(, 12).Value = Now
Else
cel.Offset(, 12).ClearContents
End If
Next
Application.EnableEvents = True
End If
'.Protect Password:="unlock"
End With
End Sub

Related

Excel VBA Copy Paste issue

I am working with Excel VBA Copy Paste. Cell R7 has formula =Max ("C77:AD81").
R7 = Highest Value for Month
F7 = Highest Value to date
Q7 = the date F7 was achieved
What I am trying to achieve is if R7 > F7, copy R7 Value to F7 and change the Q7 to = today.
All I'm achieving is R7 changes to max of ("C77:AD81") and the remaining code doesn't work. My code below.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rng As Range, r As Range, rv As Long
If Not Intersect(Target, Range("R7")) Is Nothing Then
Set rng = Intersect(Target, Range("R7"))
For Each r In rng
'Change Best Peak Flow and Date Achieved
Select Case r.Value
Case Is > ("F7")
Case Range("R7").Select
Case Range("R7").Copy
Case Range("F7").Select
Case Range("F7").Paste
Case ("R7") = ("F7")
Case Range("Q5").Select
Range("Q5") = Today()
Application.CutCopyMode = False
End Select
Next r
End If
End Sub
My advice is not to use .select. You can program everything without a single .select. Recording and analyzing macros are very good starting point for learning VBA, but sometimes they are way too complicated. I prefer simple solutions so give this a try:
Private Sub Worksheet_Change(ByVal Target As Range)
If Range("F7") <> Range("R7") Then
Range("F7") = Range("R7")
Range("Q5") = Date
End If
End Sub
So, your rng object is only 1 cell, because you specified 1 target range of R7. With this being said, your For Each...Next statement is redundant.
I also wouldn't even use Select Case at all, but I will leave it in the event you later want to build off of it.
Give this a shot
Private Sub Worksheet_Change(ByVal Target As Range)
On Error GoTo ErrHandler 'Important to ensure events are reenabled
Application.EnableEvents = False
Dim rng As Range, r As Range, rv As Long
Set rng = Intersect(Target, Range("R7"))
If Not rng Is Nothing Then
'Change Best Peak Flow and Date Achieved
Select Case True
Case r.Value > Range("F7").Value
Range("F7") = Range("R7")
Range("Q5") = Date
End Select
End If
Application.EnableEvents = True
Exit Sub
ErrHandler:
Application.EnableEvents = True
MsgBox Err.Number & vbNewLine & Err.Description
End Sub
I solved it.
Here is the code I used.
Private Sub Worksheet_Change(ByVal Target As Range)
'Change Best Peak Flow and Date Achieved
If Range("R7").Value > Range("F7").Value Then
Range("R7").Select
Selection.Copy
Range("F7").Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
Range("Q5").Select
Selection.Copy
Range("K7").Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
Application.CutCopyMode = False
End If
End Sub

VBA for formula to values in a filtered range

Sometimes, I need to run a formula on a selection of a filtered range and then convert it to values, every time I want to copy and then special paste values, I must clear the filters first. I need a macro that will convert the formula to values without clearing the filters, I also want to use a shortcut key for this operation.
The code below will allow this operation, the CTRL+M shortcut is available for this operation. Edit the macro to assign this shortcut key.
Sub PasteFilterValues()
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
If Selection.Cells.Count = 1 Then
Selection.Copy
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
Else
Dim rng As Range
Set rng = Selection.SpecialCells(xlCellTypeVisible)
For Each cl In rng
cl.Copy
cl.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
Next cl
rng.Select
End If
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
Application.CutCopyMode = False
End Sub
This should be faster than the other solution
Option Explicit
Sub fla2values(rng As Range)
Dim c As Range
For Each c In rng.SpecialCells(xlCellTypeVisible)
c.Value = c.Value
Next c
End Sub
Sub test_fla2values()
fla2values Selection
End Sub

Excel VBA RemoveDuplicates function with case sensitivity

I'm trying to remove some duplicates from selected column, but the function removes all duplicates irrespective of the case. RemoveDuplicates considers lower case, upper case, etc as duplicate. E.g. the function removed CENTRAL, central and Central.
I have simply recorded the following code and only changed it a little bit. I need to keep items with different cases and don't want to remove as duplicates.
Sub Macro1()
'
' Macro1 Macro
'
' Keyboard Shortcut: Ctrl+q
'
ActiveWorkbook.Sheets(3).Range("A:A").Clear
Selection.Copy
Sheets("Sheet3").Select
Range("A1").Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
Range("A1").Select
Range(Selection, Selection.End(xlDown)).Select
Application.CutCopyMode = False
Selection.RemoveDuplicates Columns:=1, Header:=xlNo
Range("A1").Select
Range(Selection, Selection.End(xlDown)).Select
Selection.Copy
Sheets("Sheet2").Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=True
'Range("B12").Select
Selection.End(xlToRight).Select
ActiveWorkbook.Sheets(3).Range("A:A").Clear
End Sub
Try the following code using a Dictionary to remove duplicates with case sensitivity:
Option Explicit
Sub Test()
RemoveDuplicates Sheet1.Range("A1:A12")
End Sub
Sub RemoveDuplicates(rngDataColumn As Range)
'assumes rngDataColumn is a column of data
Dim dic As Object
Dim rngCell As Range
Dim varKey As Variant
Dim lngCounter As Long
'create dictionary
Set dic = CreateObject("Scripting.Dictionary")
'dictionary becomes case sensitive
dic.CompareMode = vbBinaryCompare
'iterate range for unique values
For Each rngCell In rngDataColumn
If Not dic.Exists(rngCell.Value) Then
dic.Add Key:=rngCell.Value, Item:=True
End If
Next rngCell
'clear source range
rngDataColumn.ClearContents
'output unique items - with case sensitivity
lngCounter = 1
For Each varKey In dic.Keys
rngDataColumn(lngCounter, 1).Value = varKey
lngCounter = lngCounter + 1
Next varKey
End Sub
A1:A12 in my test case is as follows:
So, to update your recorded macro, you could try:
Sub Macro1()
'
' Macro1 Macro
'
' Keyboard Shortcut: Ctrl+q
'
ActiveWorkbook.Sheets(3).Range("A:A").Clear
Selection.Copy
Sheets("Sheet3").Select
Range("A1").Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
Range("A1").Select
Range(Selection, Selection.End(xlDown)).Select
Application.CutCopyMode = False
'use the new function here
RemoveDuplicates Selection
'Selection.RemoveDuplicates Columns:=1, Header:=xlNo
Range("A1").Select
Range(Selection, Selection.End(xlDown)).Select
Selection.Copy
Sheets("Sheet2").Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=True
'Range("B12").Select
Selection.End(xlToRight).Select
ActiveWorkbook.Sheets(3).Range("A:A").Clear
End Sub
I have found here and tested some nice solution, that seems to meet your expectations. You have to paste this function into your project:
Option Compare Binary
Sub deleteExactDuplicates(ByVal rng As Range)
Application.ScreenUpdating = False
With CreateObject("scripting.dictionary")
For Each i In rng.Cells
v = i.Value
If .exists(v) Then
i.ClearContents
Else
.Add v, 1
End If
Next i
End With
On Error Resume Next
rng.SpecialCells(xlCellTypeBlanks).EntireRow.Delete
End Sub
Then, you have to call it in your code. If I understand, you want to remove duplicates from selected range, so the macro would look like this:
Sub test()
deleteExactDuplicates Selection
End Sub
Now, this solution delete not only the values in selected range, but also entire rows, where duplicated values occurred. Are you OK with that, or you need something that removes duplicates only from particular range?

Excel VBA Running Macros on Foreach Loop without Switching Sheets

I have a module on VBA which basically runs a foreach loop for every cell that contains text in a column. The contents of each cell are then copied to another sheet where another function is called upon (DailyGet). The contents generated from the function are the copied back into the original sheet (i generated the code for this by recordings a macros). However, since there are many cells to process in the foreach loop, it is quite time consuming because the macros switches between sheets each time to run. Is there any way to speed up the process?
Sub DailyComposite()
Dim SrchRng As Range, cel As Range
Set SrchRng = Range("B2:B100")
For Each cel In SrchRng
If cel.Value <> "" Then
Worksheets("Calculations").Range("B1").Value = cel.Value
Sheets("Calculations").Select
Call DailyGet
Range("D3:Z3").Select
Application.CutCopyMode = False
Selection.copy
Sheets("Summary").Select
cel.Offset(0, 1).Select
Selection.PasteSpecial Paste:=xlPasteValuesAndNumberFormats, Operation:= _
xlNone, SkipBlanks:=False, Transpose:=False
End If
Next cel
Sheets("Calculations").Select
Application.CutCopyMode = False
Range("A1").Select
Sheets("Summary").Select
Range("A1").Select
End Sub
For starters, you can get rid of all the selecting
Range("D3:Z3").Select
Application.CutCopyMode = False
Selection.copy
Sheets("Summary").Select
cel.Offset(0, 1).Select
Selection.PasteSpecial Paste:=xlPasteValuesAndNumberFormats, Operation:= _
xlNone, SkipBlanks:=False, Transpose:=False
Should be:
Sheets("Calculations").Range("D3:Z3").Copy
cel.Offset(0, 1).PasteSpecial Paste:=xlPasteValuesAndNumberFormats
Second, why must you switch to the Calculations sheet before running DailyGet. If the function dailyGet uses ActiveSheet, change it to Sheets("Calculations"). If you do that, you never need to switch sheets.
Third, turn off ScreenUpdating when you start the macro, and turn it back on when done:
Application.ScreenUpdating = False
In general you should always avoid select. Instead try and declare/instantiate your variables as shown. I've commented the code below to explain what is going on. Let me know if you have any questions.
Option Explicit 'Always use this it helps prevent simple errors like misspelling a variable
Sub DailyComposite()
'Declare all variables you are going to use
Dim wb As Workbook 'The workbook youa re working with
Dim wsCalc As Worksheet 'Calculations sheet
Dim wsSum As Worksheet 'Summary Sheet
Dim SrchRng As Range, cel As Range
'Instantiate your variables
Set wb = ThisWorkbook
Set wsCalc = wb.Worksheets("Calculations") 'now you can simply use the variable to refer to the sheet NO SELECTING
Set wsSum = wb.Worksheets("Summary") 'SAME AS ABOVE
Set SrchRng = Range("B2:B100")
Application.ScreenUpdating = False 'Turn this off to speed up your macro
For Each cel In SrchRng
If cel.Value <> "" Then
'This ... Worksheets("Calculations").Range("B1").Value = cel.Value becomes...
wsCalc.Range("B1").Value = cel.Value
'Sheets("Calculations").Select ... this line can be deleted
Call DailyGet
'Range("D3:Z3").Select
'Application.CutCopyMode = False
'Selection.Copy
'Sheets("Summary").Select
'cel.Offset(0, 1).Select
'Selection.PasteSpecial Paste:=xlPasteValuesAndNumberFormats, Operation:= _
' xlNone, SkipBlanks:=False, Transpose:=False
'All of the above can be replaced by...
wsCalc.Range("D3:Z3").Copy
cel.Offset(0, 1).PasteSpecial Paste:=xlPasteValuesAndNumberFormats, Operation:= _
xlNone, SkipBlanks:=False, Transpose:=False
Application.CutCopyMode = False
End If
Next cel
'You can keep these if you truly want to select the A1 cell at the end
'Sheets("Calculations").Select
wsCalc.Activate
Range("A1").Select
'Sheets("Summary").Select
wsSum.Activate
Range("A1").Select
Application.ScreenUpdating = True 'Turn it back on
End Sub
There is no need to copy and paste values. I select Worksheets("Calculations") to insure that DailyGet will run as before.
Sub DailyComposite()
Dim SrchRng As Range, cel As Range
Set SrchRng = Worksheets("Summary").Range("B2:B100")
With Worksheets("Calculations")
.Select
For Each cel In SrchRng
If cel.Value <> "" Then
Range("B1").Value = cel.Value
Call DailyGet
cel.Offset(0, 1).Resize(, 23).Value = Range("D3:Z3").Value
End If
Next cel
End With
End Sub

Trying to copy specific columns in a row to another excel sheet based on it meeting certain criteria

Im very new to excel/vba and trying to use a macro to check a column for the value true, when it sees that value I'd like it to copy parts of that row to another sheet in my column. Then I need it to iterate through the other rows and perform the same checks. Here is my code currently.
Sub Macro3()
'
' Macro3 Macro
'
'
Sheets("Aspen Data").Select
Dim tfCol As Range, Cell As Object
Set tfCol = Range("G26:G56")
Sheets("Code").Select
ActiveSheet.Calculate
Sheets("Aspen Data").Select
ActiveSheet.Calculate
For Each Cell In tfCol
If IsEmpty(Cell) Then
Exit Sub
End If
If Cell.Value = "True" Then
Range("I26:Q26").Select
Selection.Copy
Sheets("AspenHist").Select
Range("B" & Rows.Count).End(xlUp).Offset(1).Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
End If
Next
End Sub
The issue appears to be in getting my Range("I26:Q26) to increment by one as it goes through the loop.
Try this
Sheets("Aspen Data").Select
Dim i As Integer
Sheets("Code").Calculate
Sheets("Aspen Data").Calculate
For i = 26 To 56
If IsEmpty(Cells(i, 7)) Then
Exit Sub
ElseIf Cells(i, 7).Value = "True" Then
Range(Cells(i, 9), Cells(i, 12)).Copy
Sheets("AspenHist").Activate
Range("B" & Rows.Count).End(xlUp).Offset(1).Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
Sheets("Aspen Data").Activate
End If
Next i
There's no need to use .Select/.Activate/ActiveSheet (see this) to accomplish your goals, and you can definitely use For Each. Try this:
Option Explicit
Sub Macro1()
'
' Macro1 Macro
'
'
Dim tfCol As Range, Cell As Object
Set tfCol = Sheets("Aspen Data").Range("G26:G56")
Application.ScreenUpdating = False
Sheets("Code").Calculate
Sheets("Aspen Data").Calculate
For Each Cell In tfCol
If IsEmpty(Cell) Then
Exit For
End If
If Cell.Value = "True" Then
Sheets("Aspen Data").Range("I" & Cell.Row & ":Q" & Cell.Row).Copy
Sheets("AspenHist").Range("B" & Rows.Count).End(xlUp).Offset(1).PasteSpecial _
Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks:=False, Transpose:=False
End If
Next
Application.ScreenUpdating = True
End Sub