Excel automatically add comment with cell edit history - vba

I have the following code in the "sheet macros" (right click sheet - view code). It used to work but now it's not adding comments in my specified range A5:AQ155.
Private Sub Worksheet_Change(ByVal Target As Range)
Application.EnableEvents = False
'If (Target.Row > 3 And Target.Row < 155) Then Cells(Target.Row, "AT") = Now()
Const sRng As String = "A5:AQ155" ' change as required
Dim sOld As String
Dim sNew As String
Dim sCmt As String
Dim iLen As Long
Dim bHasComment As Boolean
With Target(1)
If Intersect(.Cells, Range(sRng)) Is Nothing Then Exit Sub
sNew = .Text
sOld = .Text
.Value = sNew
Application.EnableEvents = True
sCmt = "Edit: " & Format$(Now, "dd Mmm YYYY hh:nn:ss") & " by " & Application.UserName & Chr(10) & "Previous Text :- " & sOld
If Target(1).Comment Is Nothing Then
.AddComment
Else
iLen = Len(.Comment.Shape.TextFrame.Characters.Text)
End If
With .Comment.Shape.TextFrame
.AutoSize = True
.Characters(Start:=iLen + 1).Insert IIf(iLen, vbLf, "") & sCmt
End With
End With
End Sub
What have I done wrong?

The code stopped firing because Event Firing was disabled and never turned back on. The way the code is written, as soon as someone makes a change to the worksheet outside the range A5:AQ155, the Events become disabled without being turned back on, which means subsequent event triggers will not be fired (ie. - the next time you edit a cell).
If you make these slight tweaks in the code it should work as intended going forward.
However, before you do this type Application.EnableEvents = True in the immediate window and hit Enter to turn events back on so that the code begins to fire again.
Private Sub Worksheet_Change(ByVal Target As Range)
Const sRng As String = "A5:AQ155" ' change as required
Dim sOld As String
Dim sNew As String
Dim sCmt As String
Dim iLen As Long
If Not Intersect(Target, Me.Range(sRng)) Is Nothing Then
Application.EnableEvents = False
With Target
sNew = .Value2
Application.Undo
sOld = .Value2
.Value2 = sNew
Application.EnableEvents = True
sCmt = "Edit: " & Format$(Now, "dd Mmm YYYY hh:nn:ss") & " by " & Application.UserName & Chr(10) & "Previous Text :- " & sOld
If .Comment Is Nothing Then
.AddComment
Else
iLen = Len(.Comment.Shape.TextFrame.Characters.Text)
End If
With .Comment.Shape.TextFrame
.AutoSize = True
.Characters(Start:=iLen + 1).Insert IIf(iLen, vbLf, "") & sCmt
End With
End With
End If
End Sub

Here is the final code that got me the desired behavior. I changed the first IF statement according to #Scott Holtzman's comment. The IF statement now resets Application.EnableEvents = True before ending the macro with End Sub
EDIT: Included "Me." in "Me.range(sRng)"
Private Sub Worksheet_Change(ByVal Target As Range)
Application.EnableEvents = False
'If (Target.Row > 3 And Target.Row < 155) Then Cells(Target.Row, "AT") = Now()
Const sRng As String = "A5:AQ155" ' change as required
Dim sOld As String
Dim sNew As String
Dim sCmt As String
Dim iLen As Long
Dim bHasComment As Boolean
With Target(1)
If Intersect(.Cells, Me.Range(sRng)) Is Nothing Then
Application.EnableEvents = True
Exit Sub
End If
sNew = .Text
sOld = .Text
.Value = sNew
Application.EnableEvents = True
sCmt = "Edit: " & Format$(Now, "dd Mmm YYYY hh:nn:ss") & " by " & Application.UserName & Chr(10) & "Previous Text :- " & sOld
If Target(1).Comment Is Nothing Then
.AddComment
Else
iLen = Len(.Comment.Shape.TextFrame.Characters.Text)
End If
With .Comment.Shape.TextFrame
.AutoSize = True
.Characters(Start:=iLen + 1).Insert IIf(iLen, vbLf, "") & sCmt
End With
End With
End Sub
Sub Hide_Comments_in_Workbook_Completely()
'This macro hides the comments and comment indicators - users wont know there is a comment within the excel workbook
Application.DisplayCommentIndicator = xlNoIndicator
End Sub

Related

How do I apply a macro to multiple excel files when the macro contains many subs?

I have used a macro to track changes in a workbook, but I would now like to run this macro in over a 100 excel files within a particular folder using a Do While Loop.
I am very new to VBA and will appreciate all the help I can get.
I have come across some code that should enable me to loop through excel files in a folder and run the macro in each one.
However it requires me to get rid of the 'sub' and 'end sub' from the macro when I copy and paste it into the do while loop, but I have 3 of them within the macro; some variables will be undefined if I delete all 3.
Therefore I tried 'Call Tracker' within the loop ('Tracker' being the macro name) and hoped it would run in each excel file.
Sub LoopThroughFiles()
Dim xFd As FileDialog
Dim xFdItem As Variant
Dim xFileName As String
Set xFd = Application.FileDialog(msoFileDialogFolderPicker)
If xFd.Show = -1 Then
xFdItem = xFd.SelectedItems(1) & Application.PathSeparator
xFileName = Dir(xFdItem & "*,xls*")
Do While xFileName <> ""
With Workbooks.Open(xFdItem & xFileName)
'Your code here
Call Tracker
End With
xFileName = Dir
Loop
End If
End Sub
Below is the code inside 'Tracker'
Option Explicit
Dim sOldAddress As String
Dim vOldValue As Variant
Public Sub Workbook_TrackChange(Cancel As Boolean)
Dim Sh As Worksheet
For Each Sh In ActiveWorkbook.Worksheets
Sh.PageSetup.LeftFooter = "&06" & ActiveWorkbook.FullName & vbLf & "&A"
Next Sh
End Sub
Public Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Dim wSheet As Worksheet
Dim wActSheet As Worksheet
Dim iCol As Integer
Set wActSheet = ActiveSheet
'Precursor Exits
'Other conditions that you do not want to track could be added here
If vOldValue = "" Then Exit Sub 'If you comment out this line *every* entry will be recorded
'Continue
On Error Resume Next ' This Error-Resume-Next is only to allow the creation of the tracker sheet.
Set wSheet = Sheets("Tracker")
'**** Add the tracker Sheet if it does not exist ****
If wSheet Is Nothing Then
Set wActSheet = ActiveSheet
Worksheets.Add(After:=Worksheets(Worksheets.Count)).Name = "Tracker"
End If
On Error GoTo 0
'**** End of specific error resume next
On Error GoTo ErrorHandler
With Application
.ScreenUpdating = False
.EnableEvents = False
End With
With Sheets("Tracker")
'******** This bit of code moves the tracker over a column when the first columns are full**'
If .Cells(1, 1) = "" Then '
iCol = 1 '
Else '
iCol = .Cells(1, 256).End(xlToLeft).Column - 7 '
If Not .Cells(65536, iCol) = "" Then '
iCol = .Cells(1, 256).End(xlToLeft).Column + 1 '
End If '
End If '
'********* END *****************************************************************************'
.Unprotect Password:="Secret"
'******** Sets the Column Headers **********************************************************
If LenB(.Cells(1, iCol).Value) = 0 Then
.Range(.Cells(1, iCol), .Cells(1, iCol + 7)) = Array("Cell Changed", "SAP ID", "Field Name", "Old Field Value", _
"New Field Value", "Time of Change", "Date Stamp", "User")
.Cells.Columns.AutoFit
End If
With .Cells(.Rows.Count, iCol).End(xlUp).Offset(1)
If Target.Count = 1 Then
.Offset(0, 1) = Cells(Target.Row, 2) 'SAPID
End If
'.Offset(0, 1) = Cells(Target.Row, 2) 'SAPID
If Target.Count = 1 Then
.Offset(0, 2) = Cells(Target.Column) 'Field name
End If
'.Offset(0, 2) = Cells(Target.Column) 'Field name
.Value = sOldAddress
.Offset(0, 3).Value = vOldValue
If Target.Count = 1 Then
.Offset(0, 4).Value = Target.Value
End If
.Offset(0, 5) = Time
.Offset(0, 6) = Date
.Offset(0, 7) = Application.UserName
.Offset(0, 7).Borders(xlEdgeRight).LineStyle = xlContinuous
End With
.Protect Password:="Secret" 'comment to protect the "tracker tab"
End With
ErrorExit:
With Application
.ScreenUpdating = True
.EnableEvents = True
End With
wActSheet.Activate
Exit Sub
ErrorHandler:
'any error handling you want
'Debug.Print "We have an error"
Resume ErrorExit
End Sub
Public Sub Workbook_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
With Target
sOldAddress = .Address(external:=True)
If .Count > 1 Then
vOldValue = "Multiple Cell Select"
Else
vOldValue = .Value
End If
End With
End Sub
'Call Tracker' in the loop does not produce an error. In fact the code seems to execute and loops through all the files but it does not run the macro in each one it opens.

Web query refresh error

I have 3 workbook web queries on a single data sheet , and I have a dropdown object with a list of months in the year (1-12). My idea for the automation was to have the query formula update based on the user selection of the dropdown value and update the query formula accordingly and refresh.
The VBA code works fine, but I get this message for one of the queries.
The reason is that the query (ex when changed from month 2 to month 3), has 2 more lines and is not the exact table height.
Any ideas how to debug this / circumvent this message. Code below:
Sub DropDown9_Change()
Dim wbconn As WorkbookConnection, qT As QueryTable
Dim wB As Workbook, wS As Worksheet
'For Each wbconn In ThisWorkbook.Connections
'Debug.Print wbconn.Name & " - " & wbconn.OLEDBConnection.CommandText & " - " & _
'wbconn.OLEDBConnection.SourceDataFile
''wbconn.Refresh
'Next wbconn
Set wB = Workbooks("OH Burdening Template.xlsb")
If ShData.Shapes("Drop Down 9").ControlFormat.Value > _
ShCalendar.Range("B3").Value Then
MsgBox "Cannot be based on future periods!", vbExclamation
Else
'Refresh WB queries
Call Refresh_Queries(ShData.Shapes("Drop Down 9").ControlFormat.Value, wB, ShData)
End If
ShData.Columns.AutoFit
End Sub
Private Function Refresh_Queries(ByVal Period As Integer, ByVal wB As Workbook, _
ByVal thisSheet As Worksheet)
With Application
.StatusBar = "Now refreshing queries on :" & ShData.Name
.ScreenUpdating = False
.EnableEvents = False
End With
Dim I As Integer, LObj As ListObject
Dim strL As Integer, str As String
Dim Pos As Integer
Dim F As String
Dim startPos As Integer
str = "?year=2018&period="
strL = Len(str)
For I = 1 To wB.Queries.Count
On Error GoTo view_err
F = wB.Queries(I).Formula
Pos = VBA.InStr(1, F, str, vbBinaryCompare)
startPos = Pos + strL
'Debug.Print F
'Replacing the period part of the string with the period entered in the dropdown
F = WorksheetFunction.Replace(F, startPos, 1, Period)
wB.Queries.Item(I).Formula = F
'Debug.Print Mid(F, startPos, 1)
Next I
For Each LObj In thisSheet.ListObjects
Application.StatusBar = "Refreshing " & LObj.Name
LObj.QueryTable.Refresh False
Debug.Print LObj.Name & Chr(32) & "Refreshed successfully!"
Next LObj
With Application
.StatusBar = False
.ScreenUpdating = True
.EnableEvents = True
End With
Set LObj = Nothing
Exit Function
view_err:
Debug.Print LObj.Name & Chr(32) & "Refresh Failed!"
MsgBox Err.Description & " (" & Err.Number & ")", vbExclamation
With Application
.StatusBar = False
.ScreenUpdating = True
.EnableEvents = True
End With
End Function

Changing target cell on VBA Excel

I'm just trying to figure out if there is a way to change the target cell to run the same code. This code basically opens a directory folder based on the cell A1 but what I want to happen is add a macro button on the same row that uses the value of the cell on that row. (For example, this my code uses the data on A1, I want the code to do that same for A2 if I put the macro button on row 2)
Sub OpenFolder()
Dim MyFolder As String
Dim JobNumber As String
Dim JobYearLeft As String
Dim JobYear As String
Dim FolderNumber As String
Dim i As Integer
Dim FirstFolder As String
JobNumber = Right(Range("A1"), Len(Range("A1")) - 3)
JobYearLeft = Right(Range("A1"), Len(Range("A1")) - 1)
JobYear = Left(JobYearLeft, Len(JobYearLeft) - 4)
i = CInt(JobNumber)
Select Case i
Case 0 To 500
FolderNumber = "0001_0500"
Case 500 To 1000
FolderNumber = "0501_1000"
Case 1000 To 1500
FolderNumber = "1001_1500"
Case 1500 To 2000
FolderNumber = "1501_2000"
End Select
If (JobYear = 17) Then
FirstFolder = "M:\2017\" & FolderNumber & "\" '& Range("A1").Value & "*" & "\"
Else
MyFolder = "M:\2016\" & FolderNumber & "\" '& Range("A1").Value & "*" & "\"
End If
If (JobYear = 17) Then
MyFolder = "M:\2017\" & FolderNumber & "\" & Range("A1").Value & "*" '& "\"
Else
MyFolder = "M:\2016\" & FolderNumber & "\" & Range("A1").Value & "*" '& "\"
End If
MyFolder = Replace(MyFolder, " ", "")
Dim OpenThisFolder As String
Dim GoToFolder As String
MyFolder = Dir(MyFolder, vbDirectory)
GoToFolder = FirstFolder & MyFolder & "\"
GoToFolder = Replace(GoToFolder, " ", "")
ActiveWorkbook.FollowHyperlink GoToFolder
End Sub
You could create few Subs (one for each button) that would call your Main Sub (that's the code that you posted) and pass to it variable containing your cell variable. Like this:
Sub ButtonForRow1()
MainSub "A1"
End Sub
Sub ButtonForRow2()
MainSub "A2"
End Sub
Sub MainSub(TargetCell as String)
(...)
JobNumber = Right(Range(TargetCell), Len(Range(TargetCell)) - 3)
(...)
End Sub
Hope this helps!
As commented, you can try something like this. Here are the things you need?
Form Button named Button 1 (or any other name you want just make sure you assign it correctly in below code).
Code to position your button every time you select a cell and then assign the action it will execute when clicked. Below code that goes in the Sheet Module (Sheet where you process your data) will do just that.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
On Error GoTo sureexit
Dim myButton As Shape, myAction As String
Application.EnableEvents = False
Set myButton = Me.Shapes("Button 1") '/* used a form control button */
If Not Intersect(Target, Me.Range("B1:B10")) Is Nothing Then
'/* target cell is on B1:B10, to get A1:A10 offset by -1 */
myAction = "'OpenFolder(Evaluate(""" & _
Target.Offset(, -1).Address & """))'"
'/* move the button to the selected cell */
With myButton
.Top = Target.Top
.Height = Target.Height
.Left = Target.Left
.Width = Target.Width
.OnAction = myAction
.TextFrame.Characters.Text = "Follow"
.Visible = msoCTrue
End With
Else
'/* hide button if selected cell is not between B1:B10 */
myButton.Visible = msoFalse
End If
sureexit:
Application.EnableEvents = True
End Sub
Of course you need a procedure in the regular module that you will assign in your Button 1 on the fly. Below is a simple procedure which expects 1 range argument.
Sub OpenFolder(r As Range)
MsgBox r.Address & ": " & r.Value2
End Sub
You can incorporate this with your procedure changing all Range("A1") with the variable r which is passed every time the button is clicked. Hope this gets you going.

How do I take this code that sends emails automatically based on the value and make it send an email based on the date?

Here's the code:
currently I have a spreadsheet that will send emails if a value in the table goes above 200, I want to make it so that it will send a reminder email saying "You have a program due NEXT Wednesday" 9 days before, and one saying "You have a program due THIS Wednesday" 2 days before. Any help is appreciated!
Option Explicit
Private Sub Worksheet_Calculate()
Dim FormulaRange As Range
Dim NotSentMsg As String
Dim MyMsg As String
Dim SentMsg As String
Dim MyLimit As Double
NotSentMsg = "Not Sent"
SentMsg = "Sent"
'Above the MyLimit value it will run the macro
MyLimit = 200
'Set the range with Formulas that you want to check
Set FormulaRange = Me.Range("B3:B7")
On Error GoTo EndMacro:
For Each FormulaCell In FormulaRange.Cells
With FormulaCell
If IsNumeric(.Value) = False Then
MyMsg = "Not numeric"
Else
If .Value > MyLimit Then
MyMsg = SentMsg
If .Offset(0, 1).Value = NotSentMsg Then
Call Mail_with_outlook2
End If
Else
MyMsg = NotSentMsg
End If
End If
Application.EnableEvents = False
.Offset(0, 1).Value = MyMsg
Application.EnableEvents = True
End With
Next FormulaCell
ExitMacro:
Exit Sub
EndMacro:
Application.EnableEvents = True
MsgBox "Some Error occurred." _
& vbLf & Err.Number _
& vbLf & Err.Description
End Sub

VBA - List of sheets (hyperlinked)

I have an Excel-Workbook. In this workbook a new sheet is created via VBA.
The more sheets this workbook has the more confusing is it, because I have to scroll a long time to reach any sheet in the middle.
I want to create an overview-sheet
in which the names of the sheets are listed AND
the name of the sheets have to be hyperlinks.
My code doesn't work at all -
BTW, I have to work with Excel 2003
Here's what I have:
Sub GetHyperlinks()
Dim ws As Worksheet
Dim i As Integer
i = 4
ActiveWorkbook.Sheets("overview").Cells(i, 1).Select
For Each ws In Worksheets
ActiveWorkbook.Sheets("overwies").Hyperlinks.Add _
Ancor:=Selection, _
Address:="", _
SubAddress:="'ws.name'", _
TextToDisplay:="'ws.name'"
i = i + 1
Next ws
End Sub
Altered your code a bit - this now works:
Sub GetHyperlinks()
Dim ws As Worksheet
Dim i As Integer
i = 4
For Each ws In ThisWorkbook.Worksheets
ActiveWorkbook.Sheets("overview").Hyperlinks.Add _
Anchor:=ActiveWorkbook.Sheets("overview").Cells(i, 1), _
Address:="", _
SubAddress:="'" & ws.Name & "'!A1", _
TextToDisplay:=ws.Name
i = i + 1
Next ws
End Sub
Two methods are used to create the links to the Active Workbook Sheets:
Simple hyperlinks are created for standard Worksheets.
Less commonly used Chart Sheets — and even rarer Dialog Sheets — cannot be hyperlinked. If this code detects a non-Worksheet type, a Sheet BeforeDoubleClick event is programmatically added to the TOC sheet so that these Sheets can still be referenced via a short cut.
Note that (2) requires that macros are enabled for this approach to work.
Option Explicit
Sub CreateTOC()
Dim ws As Worksheet
Dim nmToc As Name
Dim rng1 As Range
Dim lngProceed As Boolean
Dim bNonWkSht As Boolean
Dim lngSht As Long
Dim lngShtNum As Long
Dim strWScode As String
Dim vbCodeMod
'Test for an ActiveWorkbook to summarise
If ActiveWorkbook Is Nothing Then
MsgBox "You must have a workbook open first!", vbInformation, "No Open Book"
Exit Sub
End If
'Turn off updates, alerts and events
With Application
.ScreenUpdating = False
.DisplayAlerts = False
.EnableEvents = False
End With
'If the Table of Contents exists (using a marker range name "TOC_Index") prompt the user whether to proceed
On Error Resume Next
Set nmToc = ActiveWorkbook.Names("TOC_Index")
If Not nmToc Is Nothing Then
lngProceed = MsgBox("Index exists!" & vbCrLf & "Do you want to overwrite it?", vbYesNo + vbCritical, "Warning")
If lngProceed = vbYes Then
Exit Sub
Else
ActiveWorkbook.Sheets(Range("TOC_Index").Parent.Name).Delete
End If
End If
Set ws = ActiveWorkbook.Sheets.Add
ws.Move before:=Sheets(1)
'Add the marker range name
ActiveWorkbook.Names.Add "TOC_INDEX", ws.[a1]
ws.Name = "TOC_Index"
On Error GoTo 0
On Error GoTo ErrHandler
For lngSht = 2 To ActiveWorkbook.Sheets.Count
'set to start at A6 of TOC sheet
'Test sheets to determine whether they are normal worksheets
ws.Cells(lngSht + 4, 2).Value = TypeName(ActiveWorkbook.Sheets(lngSht))
If TypeName(ActiveWorkbook.Sheets(lngSht)) = "Worksheet" Then
'Add hyperlinks to normal worksheets
ws.Hyperlinks.Add Anchor:=ws.Cells(lngSht + 4, 1), Address:="", SubAddress:="'" & ActiveWorkbook.Sheets(lngSht).Name & "'!A1", TextToDisplay:=ActiveWorkbook.Sheets(lngSht).Name
Else
'Add name of any non-worksheets
ws.Cells(lngSht + 4, 1).Value = ActiveWorkbook.Sheets(lngSht).Name
'Colour these sheets yellow
ws.Cells(lngSht + 4, 1).Interior.Color = vbYellow
ws.Cells(lngSht + 4, 2).Font.Italic = True
bNonWkSht = True
End If
Next lngSht
'Add headers and formatting
With ws
With .[a1:a4]
.Value = Application.Transpose(Array(ActiveWorkbook.Name, "", Format(Now(), "dd-mmm-yy hh:mm"), ActiveWorkbook.Sheets.Count - 1 & " sheets"))
.Font.Size = 14
.Cells(1).Font.Bold = True
End With
With .[a6].Resize(lngSht - 1, 1)
.Font.Bold = True
.Font.ColorIndex = 41
.Resize(1, 2).EntireColumn.HorizontalAlignment = xlLeft
.Columns("A:B").EntireColumn.AutoFit
End With
End With
'Add warnings and macro code if there are non WorkSheet types present
If bNonWkSht Then
With ws.[A5]
.Value = "This workbook contains at least one Chart or Dialog Sheet. These sheets will only be activated if macros are enabled (NB: Please doubleclick yellow sheet names to select them)"
.Font.ColorIndex = 3
.Font.Italic = True
End With
strWScode = "Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)" & vbCrLf _
& " Dim rng1 As Range" & vbCrLf _
& " Set rng1 = Intersect(Target, Range([a6], Cells(Rows.Count, 1).End(xlUp)))" & vbCrLf _
& " If rng1 Is Nothing Then Exit Sub" & vbCrLf _
& " On Error Resume Next" & vbCrLf _
& " If Target.Cells(1).Offset(0, 1) <> ""Worksheet"" Then Sheets(Target.Value).Activate" & vbCrLf _
& " If Err.Number <> 0 Then MsgBox ""Could not select sheet"" & Target.Value" & vbCrLf _
& "End Sub" & vbCrLf
Set vbCodeMod = ActiveWorkbook.VBProject.VBComponents(ws.CodeName)
vbCodeMod.CodeModule.AddFromString strWScode
End If
'tidy up Application settins
With Application
.ScreenUpdating = True
.DisplayAlerts = True
.EnableEvents = True
End With
ErrHandler:
If Err.Number <> 0 Then MsgBox Err.Description & vbCrLf & "Please note that your Application settings have been reset", vbCritical, "Code Error!"
End Sub