I am using the below code from this link
Need help
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
The above code works fine only one problem I am facing.
It creates a history of 9 changes in 9 lines in comments if the changes exceeded or Total Character (with space) in comments is 268 more then the previous line is automatically erased.
Can anyone help me to overcome the above problem?
I want no bindings of changes or character input or line limits.
Thanks in advance and appreciate any help.
Characters is limited in the length of text you can address. You could instead delete and re-add the comment with the new text added.
This worked for me in testing:
Private Sub Worksheet_Change(ByVal Target As Range)
Const sRng As String = "A5:AQ155" ' change as required: must be a contiguous range
Dim c As Range, rng As Range, oldVals, newVals, newValsTgt, usr
Dim col As Long, rw As Long, txt As String, s As String
If Target.Areas.Count > 1 Then Exit Sub 'only handling single Area changes
Set rng = Application.Intersect(Target, Me.Range(sRng))
If rng Is Nothing Then Exit Sub
On Error GoTo haveError
Application.EnableEvents = False 'don't re-trigger event
newValsTgt = ToArray(Target) 'get current Target values
newVals = ToArray(rng) 'get current values for range of interest
Application.Undo 'restore previous values
oldVals = ToArray(rng) 'get pre-update values for range of interest
Target.Value = newValsTgt 'restore the Target range values
Application.EnableEvents = True
usr = Application.UserName
For rw = 1 To UBound(newVals, 1) 'loop over the new values
For col = 1 To UBound(newVals, 2)
If newVals(rw, col) <> oldVals(rw, col) Then 'was the content changed?
Set c = rng.Cells(rw, col)
s = "Edit: " & Format$(Now, "dd Mmm YYYY hh:nn:ss") & _
" by " & usr & Chr(10) & _
"Previous Text :- " & oldVals(rw, col)
If c.Comment Is Nothing Then
c.AddComment s
Else
txt = c.Comment.Text
c.Comment.Delete
c.AddComment s & vbLf & txt
End If
c.Comment.Shape.TextFrame.AutoSize = True
End If
Next col
Next rw
Exit Sub 'normal exit
haveError:
Debug.Print "Error: " & Err.Description
Application.EnableEvents = True 'ensure events are back on
End Sub
'returns the value of both single cells and ranges as an array...
Function ToArray(rng As Range)
Dim rv
If rng.CountLarge = 1 Then
ReDim rv(1 To 1, 1 To 1)
rv(1, 1) = rng.Value
ToArray = rv
Else
ToArray = rng.Value
End If
End Function
Related
I have a working macro which changes a linked sheet in a cell according to month. Say from April to March.
From
='C:\Data\Name\[Time.xlsx]2021-04!A1"
to
='C:\Data\Name\[Time.xlsx]2021-05!A1".
This works as long as the user remembers to add a new sheet at the beginning of each month, which is not always done in time. As a result i get a "sheet cannot be found -> choose one from below" Prompt". How can I avoid this selector and add a "table not found" string in the cell instead and move on to the next operation in the loop?
Thanks!
EDIT:
Code Added upon request:
Sub Month()
Set rngB = Range("B2:B6")
strColB = "Range("B1")
iRowB = 1
strMonth = InputBox ("Insert Month as integer","Month")
strMonth = Trim(strMonth)
For Each cellB In rngB
cellB.Formula = "='C:Data\[" & strColB & "Time.xlsx]2021-" & strMonth & "'!B" & iRowB
iRowB = iRowB + 1
Next cellB
End Sub
Here's an easy function to test if a sheet exists prior to accessing its cells
Function SheetExists(ByVal SheetName As String, ByRef InWorkbook As Workbook) As Boolean
On Error Resume Next
SheetExists = Not InWorkbook.Sheets(SheetName) Is Nothing
On Error GoTo 0
End Function
Here's how you would use it.
Sub test()
MsgBox SheetExists("2021-04", Application.Workbooks("Time.xlsx"))
End Sub
Another example:
Sub test()
If SheetExists("2021-04", Application.Workbooks("Time.xlsx")) Then
'do stuff
Else
[a1] = "table not found"
End If
End Sub
Edit:
After the code was added to the original post. Here is an example of how to implement this function with that code:
Sub Month()
Set rngB = Range("B2:B6")
strColB = Range("B1").Text
iRowB = 1
strMonth = InputBox("Insert Month as integer", "Month")
strMonth = Trim(strMonth)
For Each cellB In rngB
If SheetExists("2021-" & strMonth, Application.Workbooks("Time.xlsx")) Then
cellB.Formula = "='C:Data\[" & strColB & "Time.xlsx]2021-" & strMonth & "'!B" & iRowB
Else
cellB.Formula = "table not found"
End If
iRowB = iRowB + 1
Next cellB
End Sub
I have encountered a few issues with some code in VBA. I am trying to have the changes made to a cells on an excel sheet show up in comments on the cell the change was made to and I wish for these changes to be stored in a list so I can view them all later. I have tried lots of different pieces of code I have found to try and implement it into the code but none have worked.
Any ideas on how to get this to work?
Worksheet
The below code is what I am currently using
Private Sub Worksheet_Change(ByVal Target As Range)
Dim Adding As Boolean, Finding As Boolean, Subtracting As Boolean
Dim f As Range, v
Select Case Target.Address(True, True)
Case "$A$4": Adding = True
Case "$C$4": Subtracting = True
Case "$E$4": Finding = True
Case Else: Exit Sub
End Select
v = Trim(Target.Value)
If Len(v) = 0 Then Exit Sub
Set f = Me.Range("C8").Resize(1000, 1).Find(v, lookat:=xlWhole)
If Adding Then
If f Is Nothing Then
'not found: add as new row
Set f = Me.Cells(Rows.Count, 3).End(xlUp).Offset(1, 0)
f.Value = v
End If
f.Offset(0, 1).Value = f.Offset(0, 1).Value + 1
doDate f.Offset(0, 2)
Target.Value = ""
ElseIf Subtracting Then
If f Is Nothing Then
MsgBox v & " not found for subtraction!"
Else
f.Offset(0, 1).Value = f.Offset(0, 1).Value - 1
doDate f.Offset(0, 3)
Target.Value = ""
End If
Else 'finding
If Not f Is Nothing Then
f.EntireRow.Select
Target.Value = ""
Else
MsgBox v & " not found."
End If
End If
If Adding Or Subtracting Then Target.Select
End Sub
Sub doDate(c As Range)
With c
.NumberFormat = "m/d/yyyy h:mm AM/PM"
.Value = Now
End With
End Sub
I have implemented a few formulas on the worksheet but don't see any reason why it would matter in this situation since they only track quantity of items with the same unique identifier.
I also tried some code that added comments to the cells as they were changed that worked but always returned the previous cell value as blank. It is not actually added into the current code though.
Option Explicit
Public preValue As Variant
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Count > 1 Then Exit Sub
Target.ClearComments
Target.AddComment.Text Text:="Previous Value was " & preValue & Chr(10) & "Revised " & Format(Date, "mm-dd-yyyy") & Chr(10) & "By " & Environ("UserName")
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Target.Count > 1 Then Exit Sub
If Target = "" Then
preValue = "a blank"
Else: preValue = Target.Value
End If
End Sub
By and large, the code below should do what you want. I marveled at your use of A4 and C4 to express addition and subtraction. As it is, whatever you change in those two cells, apart from clearing them, will result in a quantity of 1 being added or subtracted. I would have expected that a quantity must be entered there which is processed. If the quantity is fixed at 1 the system appears too elaborate.
Anyway, here's the code. I guess you'll be able to modify it to better suit your needs.
Private Sub Worksheet_Change(ByVal Target As Range)
' 038
Dim LookUp As Variant ' subject
Dim Action As Variant ' add = 1, subtract = -1, find = 2
Dim Fnd As Range ' Result of Find method
Dim Txt As String ' comment text
With Target
If (.Row <> 4) Or (.CountLarge > 1) Then Exit Sub
LookUp = Cells(4, "E").Value
On Error Resume Next
Action = Array(0, 1, 0, -1, 0, 2)(.Column)
End With
If Action And (LookUp <> "") Then
' C8 to end of column C
With Range(Cells(8, "C"), Cells(Rows.Count, "C").End(xlUp))
Set Fnd = .Find(LookUp, .Cells(.Cells.Count), xlValues, xlWhole, xlByRows)
End With
End If
If Fnd Is Nothing Then
Select Case Action
Case -1
MsgBox """" & LookUp & """ not found.", vbInformation, "Can't subtract"
Action = -2
Case 2
MsgBox """" & LookUp & """ not found.", vbInformation, "No record"
Action = -2
Case Else
Set Fnd = Cells(Rows.Count, "C").End(xlUp).Offset(1)
Fnd.Value = LookUp
End Select
End If
With Fnd
If Abs(Action) <> 2 Then
With .Offset(0, 1)
If .Comment Is Nothing Then
.AddComment
Else
Txt = Chr(10)
End If
Txt = "Previous Qty = " & .Value & Chr(10) & _
"Revised " & Format(Date, "mm-dd-yyyy") & Chr(10) & _
"by " & Environ("UserName") & Txt
.Comment.Text Txt, 1, False
.Value = Val(.Value) + Action
With .Offset(0, 2)
.NumberFormat = "m/d/yyyy h:mm AM/PM"
.Value = Now
End With
End With
ElseIf Action = 2 Then
.EntireRow.Select
End If
End With
If Action <> 2 Then Target.Select
End Sub
I am getting an error when I am trying to capture an old value from a cell:
run-time error '13' Type mismatch.
This is the code I am using:
Dim oldValue As Variant
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
oldValue = Target(1, 1).Value
MsgBox oldValue
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
'check if one of the target columns is changed
If Target.Cells.Column = 6 Or Target.Cells.Column = 9 Or Target.Cells.Column = 10 Or Target.Cells.Column = 11 Then
'Set variables
Dim LogActivity As String
Dim cRow As Integer
Dim pRowCount As Integer
Dim wsPBS As Worksheet
Dim wsHistoric As Worksheet
Set wsPBS = Sheets("PBS")
Set wsHistoric = Sheets("Historic")
cRow = Target.Cells.Row
pRowCount = wsHistoric.Range("A" & Rows.Count).End(xlUp).Row + 1
'Check for blanks on PBS sheet and exit if entry is not complete
Dim BlankCount As Integer
BlankCount = 0
If wsPBS.Range("D" & cRow).Value = "" Then BlankCount = BlankCount + 1
If wsPBS.Range("E" & cRow).Value = "" Then BlankCount = BlankCount + 1
If wsPBS.Range("F" & cRow).Value = "" Then BlankCount = BlankCount + 1
If wsPBS.Range("H" & cRow).Value = "" Then BlankCount = BlankCount + 1
If wsPBS.Range("I" & cRow).Value = "" Then BlankCount = BlankCount + 1
If wsPBS.Range("J" & cRow).Value = "" Then BlankCount = BlankCount + 1
If BlankCount >= 1 Then Exit Sub
Application.ScreenUpdating = False
Application.DisplayAlerts = False
If Target.Cells.Column = 6 Then LogActivity = "Owner change"
If Target.Cells.Column = 9 Then LogActivity = "Status change"
If Target.Cells.Column = 10 Then LogActivity = "Priority change"
If Target.Cells.Column = 11 Then LogActivity = "Completion rate"
Range("C" & cRow & ":O" & cRow).Select
Selection.Copy
wsHistoric.Select
wsHistoric.Range("F" & pRowCount).Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks:=False, Transpose:=False
Application.CutCopyMode = False
wsHistoric.Range("A" & pRowCount).Value = Date
wsHistoric.Range("B" & pRowCount).Value = Time
wsHistoric.Range("C" & pRowCount).Value = Application.UserName
wsHistoric.Range("D" & pRowCount).Value = LogActivity
wsHistoric.Range("E" & pRowCount).Value = oldValue
Application.ScreenUpdating = True
Application.DisplayAlerts = True
End If
End Sub
The value is supposed to be stored in a global dim called 'oldValue' so I can use it later on in my code.
The cell I am clicking does contain a string.
Any suggestions?
The main issue:
You're Selecting within the Worksheet_Change event.
Range("C" & cRow & ":O" & cRow).Select
Selection.Copy
That fires the Selection_Change event again, overwriting oldValue.
No need to Select here. See How to avoid using Select in Excel VBA.
Range("C" & cRow & ":O" & cRow).Copy
The secondary (yet still very important issue):
In your original version of the selection change:
Dim oldValue As String
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
oldValue = Target.Value
End Sub
This will throw a type mismatch error if Target doesn't contain a String or something that can be coerced to a String.
In your instance, that was because Target actually was multiple cells: Range("C" & cRow & ":O" & cRow).
But your code would also throw an error if you selected a cell with an error value (#N/A, #DIV/0, etc.).
The fix:
First of all, avoid using Select, as already noted.
If for some (rare) reason you absolutely need to Select, then toggle events off and on:
Private Worksheet_Change(ByVal Target As Range)
Application.EnableEvents = False
... do your stuff
Application.EnableEvents = True
End Sub
Lastly, within the selection change, instead of assuming that you'll only select a string, or only select one cell, add some validation.
Dim oldValue As Variant
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Target.CountLarge <> 1 Then Exit Sub ' ignore a multi-cell selection
If IsError(Target.Value) Then Exit Sub ' ignore selection of errors
oldValue = Target.Value
End Sub
Try identifying a single cell withing Target:
oldValue = Target(1,1).Value
I have a Userform with a listbox for which I am using conditional logic to determine output values to the sheet of selected or non-selected items in the listbox. The issue is that when the Textbox (Tbl_AliasName) is blank, the code executes this:
ElseIf .Selected(k) = True And Tbl_AliasName = vbNullString Then
lstbxRow = lstbxRow + 1
Worksheets("New TRAX").Cells(lstbxRow, 3) = Trim(Cells(2, 1).Value2) & "." & .Column(1, k)
But if Tbl_AliasName is not blank then the code does nothing, but it is supposed to do this:
ElseIf .Selected(k) = True And Tbl_AliasName <> vbNullString Then
lstbxRow = lstbxRow + 1
Worksheets("New TRAX").Cells(lstbxRow, 3) = Tbl_AliasName & "." & .Column(1, k)
I have used several variations of If statements, and non of which have worked.
Below is My Code:
Option Explicit
Public Tbl_AliasName As String
Tbl_AliasName = Trim(UserForm_Finder.txtConcat.Value)
Private Sub BtnConcat_Click()
Dim k As Long, lstbxRow As Long, LR As Long
lstbxRow = 1
'****************
'This if statement works perfectly
If (Cells(2, 1).Value2 = vbNullString Or Cells(2, 2).Value2 = vbNullString) _
And Tbl_AliasName = vbNullString Then
MsgBox "You must Search for a Table or Column first.", _
vbExclamation, "Error Encountered"
Exit Sub
ElseIf (UserForm_Finder.ListBx_TblsCols.ListCount = 0 And Tbl_AliasName <> vbNullString) Then
MsgBox "You must Search for a Table or Column first.", _
vbExclamation, "Error Encountered"
'(Cells(2, 1).Value2 = vbNullString Or Cells(2, 2).Value2 = vbNullString) And _
Exit Sub
End If
With UserForm_Finder.ListBx_TblsCols
For k = 0 To .ListCount - 1
'****************
This is where the problems begin
If .Selected(k) = False Then
MsgBox "You must Select 1 or more items from the list box.", _
vbExclamation, "Error Encountered"
Exit Sub
ElseIf .Selected(k) = True And Tbl_AliasName <> vbNullString Then
lstbxRow = lstbxRow + 1
Worksheets("New TRAX").Cells(lstbxRow, 3) = Tbl_AliasName & "." & .Column(1, k)
ElseIf .Selected(k) = True And Tbl_AliasName = vbNullString Then
lstbxRow = lstbxRow + 1
Worksheets("New TRAX").Cells(lstbxRow, 3) = Trim(Cells(2, 1).Value2) & "." & .Column(1, k)
End If
Next k
End With
End Sub
My goal is to do the following:
If a Textbox (Tbl_AliasName) is not blank and the user has selected one or more items in the listbox (ListBx_TbleCols) then concatenate the Tbl_AliasName to the selected items in the listbox
If Tbl_AliasName is blank, then use the value in Cells(2,1) to concatenate to the selected Items in the list box.
I have tried the following additions:
Dim LstBxItemSelected As Boolean
'This was placed in the for loop
LstBxItemSelected = True
'this was placed outside the for loop
If LstBxItemSelected = False Then
MsgBox "You must Select 1 or more items from the list box.", _
vbExclamation, "Error Encountered"
Exit Sub
End If
Is there a better way to tell if items are selected, because I feel that the way I have it structured in my loop, the code will throw the error if everything isn't selected? Thank you in advance for any ideas, answers, or suggestions!
Note: The Listbox is populated by the click of another button on the userform which calls the following sub:
Sub FillLstBxCols()
Dim ListBx_Target As MSForms.ListBox
Dim rngSource As Range
Dim LR As Long
If Cells(2, 1).Value2 <> vbNullString Then
LR = Worksheets("New TRAX").Cells(Rows.Count, 2).End(xlUp).Row
'Set reference to the range of data to be filled
Set rngSource = Worksheets("New Trax").Range("A" & 2 & ":" & "B" & LR)
'Fill the listbox
Set ListBx_Target = UserForm_Finder.ListBx_TblsCols
With ListBx_Target
.RowSource = rngSource.Address
End With
End If
End Sub
Hard to say without sample data and expected results, but I think this is what you're looking for:
Private Sub btnConcat_Click()
Dim ws As Worksheet
Dim bSelected As Boolean
Dim sConcat As String
Dim i As Long, lRowIndex As Long
Set ws = ActiveWorkbook.Sheets("New TRAX")
lRowIndex = 1
bSelected = False
sConcat = Trim(Me.txtConcat.Text)
If Len(sConcat) = 0 Then sConcat = Trim(ws.Cells(2, "A").Value)
If Len(sConcat) = 0 Then
MsgBox "You must Search for a Table or Column first.", vbExclamation, "Error Encountered"
Exit Sub
End If
For i = 0 To Me.ListBx_TblsCols.ListCount - 1
If Me.ListBx_TblsCols.Selected(i) Then
If bSelected = False Then
bSelected = True
ws.Range("C2", ws.Cells(ws.Rows.Count, "C")).Clear 'clear previous concat results (delete this line if not needed)
End If
lRowIndex = lRowIndex + 1
ws.Cells(lRowIndex, "C").Value = sConcat & "." & Me.ListBx_TblsCols.List(i)
End If
Next i
If bSelected = False Then MsgBox "Must select at least one item from the list"
End Sub
First off thank you very much. Over the last few months (i believe) my coding has progressed drastically. Any and all criticize is always welcome (rip me apart).
Recently I started to try to use different Subs (I dont quite understand when to use functions etc, but i figure it is good structure practice for when i figure it out.
I am hitting a Run-time 424 Error with the following bit of code in Sub ownerCHECK
Sub OccupationNORMALIZATION()
Dim infoBX As String
' initialize variables
LRow = ActiveSheet.UsedRange.Rows.Count
LCol = ActiveSheet.UsedRange.Columns.Count
STATUScounter = LRow
Do While infoBX = ""
infoBX = InputBox("Enter Occupation Column", "Occupation Column")
Loop
restaurCHECK (infoBX)
Application.ScreenUpdating = True
Application.StatusBar = ""
End Sub
-
Sub restaurCHECK(infoBX As String)
Dim RestaurantS(), RestaurantDQs() As Variant
Dim i, LRow, LCol, STATUScounter As Long
Dim rRng As Range
LRow = ActiveSheet.UsedRange.Rows.Count
LCol = ActiveSheet.UsedRange.Columns.Count
STATUScounter = LRow
RestaurantS = Array("estaur", "food", "cafe", "beverage", "waiter", "waitr", _
"waitstaff", "wait staff", "grill") 'array list of target occupations
RestaurantDQs = Array("fast", "pub", "import", "packing", "processing", "packag", _
"retired", "anufact", "distrib") ' disqualifying words for Restaurante category
Set rRng = Range(infoBX & "2:" & infoBX & LRow)
Application.ScreenUpdating = False
For Each cell In rRng
ownerCHECK (cell)
For i = LBound(RestaurantS) To UBound(RestaurantS)
If InStrRev(cell.Value, UCase(RestaurantS(i))) > 0 Then
cell.Offset(, 1) = "Restaurants"
cell.Interior.Color = 52479
End If
Debug.Print cell.Value
Next
For i = LBound(RestaurantDQs) To UBound(RestaurantDQs)
If InStrRev(cell.Value, UCase(RestaurantDQs(i))) And cell.Interior.Color = 52479 Then
cell.Interior.Color = 255
cell.Offset(, 1) = ""
End If
Next
STATUScounter = STATUScounter - 1
Application.StatusBar = "REMAINING ROWS " & STATUScounter & " tristram "
Next cell
End Sub
-
Sub ownerCHECK(str_owner As Range)
Dim owner() As Variant
owner() = Array("owner", "shareholder", "owns ")
For i = LBound(owner) To UBound(owner)
If InStrRev(str_owner, UCase(owner(i))) > 0 Then
cell.Offset(, 2) = "Owner"
End If
Next
End Sub
I can see a couple of issues in ownerCHECK():
"cell" is not defined (unless it's global)
you shouldn't use "cell" as a variable name (internal VBA property)
check validity of incoming range
.
Option Explicit
Sub ownerCHECK(ByRef rngOwner As Range)
If Not rngOwner Is Nothing Then
Dim owner() As Variant
owner() = Array("OWNER", "SHAREHOLDER", "OWNS ")
For i = LBound(owner) To UBound(owner)
If InStrRev(UCase(rngOwner), owner(i)) > 0 Then
rngOwner.Offset(, 2) = "Owner"
End If
Next
End If
End Sub