Need help optimizing VBA code in Sub WorkSheet_Change - excel

I have the following code that does 3 things:
in cell F3 it uppers all text and adds the word "cassa" if not already entered;
in cell F25 it enters the date and time of the last editing;
in range H5:H12 and D18:K19, if the user deletes the content of the cell, a zero is inserted.
I've noticed that when moving between cells in that sheet there's a slight lag.
I'm sure this code can be optimized to speed it up, but I'm stuck.
Any help would be really appreaciated.
Thank you.
Private Sub WorkSheet_Change(ByVal Target As Range)
Const RNG_TS As String = "F25" 'cella dove mostro "aggiornamento giacenza"
Const rng As String = "F3" 'cella "nome cassa"
Dim stringa As String
Dim TargetDateRange As Range
Set TargetDateRange = Union(Worksheets("GIACENZA MONETE").Range("H5:H12"), Worksheets("GIACENZA MONETE").Range("D18:K19"))
If Target.Cells.CountLarge > 1 Then Exit Sub
If Target.Address = Me.Range(RNG_TS).Address Then Exit Sub 'prevent re-entry
Me.Range(RNG_TS).Value = "Aggiornamento giacenza: " & _
Format(Now(), "dd/mm/yyyy - hh:mm:ss")
If Target.Address = Me.Range(rng).Address Then
stringa = UCase(Trim(Target.Value))
If InStr(1, stringa, "cassa", vbTextCompare) = 0 Then stringa = "CASSA " & stringa
On Error GoTo haveError
Application.EnableEvents = False
Target.Value = stringa
Application.EnableEvents = True
End If
If Not TargetDateRange Is Nothing Then
Application.EnableEvents = False
If ActiveCell.Value = "" Or ActiveCell.Value = vbNullString Or Trim(ActiveCell.Value) = "" Then
ActiveCell.Value = 0
End If
End If
haveError:
Application.EnableEvents = True
End Sub

Private Sub WorkSheet_Change(ByVal Target As Range)
Const RNG_TS As String = "F25" 'cella dove mostro "aggiornamento giacenza"
Const rng As String = "F3" 'cella "nome cassa"
Dim stringa As String
Dim TargetDateRange As Range
Set TargetDateRange = Intersect(Worksheets("GIACENZA MONETE").Range("H5:H12"), Worksheets("GIACENZA MONETE").Range("D18:K19"))
If Target.Cells.CountLarge > 1 Then Exit Sub
Application.EnableEvents = False
If Target.Address = Me.Range(RNG_TS).Address Or Target.Address = Me.Range(rng).Address Then Exit Sub 'prevent re-entry
With Worksheets("GIACENZA MONETE")
Me.Range(RNG_TS).Value = "Aggiornamento giacenza: " & Format(Now(), "dd/mm/yyyy - hh:mm:ss")
If Target.Address = Me.Range(rng).Address Then
stringa = UCase(Trim(Target.Value))
If InStr(1, stringa, "cassa", vbTextCompare) = 0 Then stringa = "CASSA " & stringa
On Error GoTo haveError
Target.Value = stringa
End If
If Not TargetDateRange Is Nothing Then
If Target.Value = "" Or Target.Value = vbNullString Or Trim(Target.Value) = "" Then
Target.Value = 0
End If
End If
End With
Application.EnableEvents = True
End Sub
Maybe this can help

Related

How do I make one dropdown list show as a new line and one as a separated comma list?

Working in excel, I'd like to make several dropdown menus (D3:D400 & E3:E400) a list with a new line each and one(F3:F400) that's text separated by a comma, all on one line. Current code:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim Oldvalue As String
Dim Newvalue As String
Application.EnableEvents = True
On Error GoTo Exitsub
If Not Intersect(Target, Range("D3:D400,E3:E400,F3:F400")) Is Nothing Then
If Target.SpecialCells(xlCellTypeAllValidation) Is Nothing Then
GoTo Exitsub
Else: If Target.Value = "" Then GoTo Exitsub Else
Application.EnableEvents = False
Newvalue = Target.Value
Application.Undo
Oldvalue = Target.Value
If Oldvalue = "" Then
Target.Value = Newvalue
Else
If InStr(1, Oldvalue, Newvalue) = 0 Then
Target.Value = Oldvalue & vbNewLine & Newvalue
Else:
Target.Value = Oldvalue
End If
End If
End If
End If
Application.EnableEvents = True
Exitsub:
Application.EnableEvents = True
End Sub
Use If Not Intersect(...) Is Nothing to check if the Target is in "D:E" or "F:F", and then create a variable Separator that is set to either vbNewLine or ", " depending on the result. Then later, join your strings with Separator like Target.Value = Oldvalue & Separator & Newvalue
Private Sub Worksheet_Change(ByVal Target As Range)
Dim Oldvalue As String
Dim Newvalue As String
Application.EnableEvents = True
On Error GoTo Exitsub
If Not Intersect(Target, Range("D3:D400,E3:E400,F3:F400")) Is Nothing Then
Dim Separator As String
If Not Intersect(Target, Range("F3:F400")) Is Nothing Then
Separator = ", "
Else
Separator = vbNewLine
End If
If Target.SpecialCells(xlCellTypeAllValidation) Is Nothing _
Or Target.Value = "" _
Then
GoTo Exitsub
Else
Application.EnableEvents = False
Newvalue = Target.Value
Application.Undo
Oldvalue = Target.Value
If Oldvalue = "" Then
Target.Value = Newvalue
ElseIf InStr(1, Oldvalue, Newvalue) = 0 Then
Target.Value = Oldvalue & Separator & Newvalue
Else
Target.Value = Oldvalue
End If
End If
End If
Exitsub:
Application.EnableEvents = True
End Sub

To select multiple lines from the List in Data Validation in MS Excel

I have inserted following code so that I can select multiple lines to a single cell C14, D14 and E14 from the Data validation List.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim Oldvalue As String
Dim Newvalue As String
On Error GoTo Exitsub
Target.Address = "$C$14" Or Target.Address = "$D$14" Or Target.Address = "$E$14" Then
If Target.SpecialCells(xlCellTypeAllValidation) Is Nothing Then
GoTo Exitsub
Else: If Target.Value = "" Then GoTo Exitsub Else
Application.EnableEvents = False
Newvalue = Target.Value
Application.Undo
Oldvalue = Target.Value
If Oldvalue = "" Then
Target.Value = Newvalue
Else
Target.Value = Oldvalue & ", " & Newvalue
End If
End If
End If
Application.EnableEvents = True
Exitsub:
Application.EnableEvents = True
End Sub
Sorry for noob question. How shall the Concatenation be so that Newvalue appears in next line rather in same line.
The code needs some tweeting here I think.
Target.Value = Oldvalue & ", " & Newvalue
Thanks for some hints please.
changed the code as follows:-
Target.Value = Oldvalue & vbNewLine & Newvalue

Updating form responses from one sheet to another

I created a data entry form in Excel.
I would like that input to be stored in another sheet (Table format).
Code I found online and modified:
Function ValidateForm() As Boolean
SellerSKU.BackColor = vbWhite
Description.BackColor = vbWhite
ValidateForm = True
If Trim(SellerSKU.Value) = "" Then
MsgBox "SKU can't be left blank.", vbOKOnly + vbInformation, "SKU"
SellerSKU.BackColor = vbRed
SellerSKU.Activate
ValidateForm = False
ElseIf Trim(Description.Value) = "" Then
MsgBox "Description can't be left blank.", vbOKOnly + vbInformation, "Description"
Description.BackColor = vbRed
Description.Activate
ValidateForm = False
End If
End Function
Private Sub CommandButton1_Click()
Application.ScreenUpdating = False
SellerSKU.Value = ""
SellerSKU.BackColor = vbWhite
Description.Value = ""
Description.BackColor = vbWhite
End Sub
Private Sub CommandButton2_Click()
Application.ScreenUpdating = False
Dim iRow As Long
iRow = Sheets("Reference Sheet (Order Hist)").Range("A1048576").End(xlUp).Row + 1
If ValidateForm = True Then
With ThisWorkbook.Sheets("Reference Sheet (Order Hist)")
.Range("A" & iRow).Value = SellerSKU.Value
.Range("C" & iRow).Value = Description.Value
End With
Call Reset
Else
Application.ScreenUpdating = False
Exit Sub
End If
Application.ScreenUpdating = True
End Sub
When I hit "Enter" on the data entry form, the table on the other sheet does not get updated.
Also is it possible to clear the form every time an entry has been successfully made?
This worked for me. Re-organized and removed some of the repetition...
Private Sub CommandButton2_Click()
Dim iRow As Long, valErrors As String
valErrors = ValidationErrors() 'checks the form
If Len(valErrors) = 0 Then
'no errors - add the data
With ThisWorkbook.Worksheets("Reference Sheet (Order Hist)")
iRow = .Cells(.Rows.Count, "A").End(xlUp).Row + 1
.Range("A" & iRow).Value = SellerSKU.Value
.Range("C" & iRow).Value = Description.Value
End With
ResetForm 'Call keyword is deprecated...
Else
MsgBox "One or more errors in form entries:" & vbLf & vbLf & valErrors, _
vbOKOnly + vbExclamation, "Check form data"
End If
End Sub
'check the form and return a listing of any errors
Function ValidationErrors() As String
Dim msg As String
CheckNonBlank SellerSKU, "SKU can't be left blank.", msg
CheckNonBlank Description, "Description can't be left blank.", msg
ValidationErrors = msg
End Function
'utility sub - check if a control has text, flag as error if missing,
' and add some text to the overall validation message
Sub CheckNonBlank(cntrl As Object, msgErr As String, ByRef msg As String)
Dim isErr As Boolean
isErr = Len(Trim(cntrl.Value)) = 0 'true if no content
ErrorFlag cntrl, isErr
If isErr And Len(msgErr) > 0 Then
msg = msg & IIf(Len(msg) > 0, vbLf, "") & msgErr 'append this error
End If
End Sub
Private Sub CommandButton1_Click()
ResetForm
End Sub
'clear textboxes and any error flags
Sub ResetForm()
SellerSKU.Value = ""
ErrorFlag SellerSKU, False
Description.Value = ""
ErrorFlag Description, False
End Sub
'flag a control as having a problem (pass False to second parameter to clear flag)
Sub ErrorFlag(cntrl As Object, Optional HasError As Boolean = True)
cntrl.BackColor = IIf(HasError, vbRed, vbWhite)
End Sub

Save cell changes in threaded comment VB

I would like to "Save" the previous info from the cell in a Threaded Comment when i change the value.
This script does that if a cell is empty. If a cell is not empty, then i would like it to save the last value in a threaded comment, not replacing the old comment, but making it into a discussion like it is supposed to be.
Can anybody help me with that? Attached is my code that makes a threaded comment.
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 = "Sist endra: " & Format$(Now, "dd Mmm YYYY hh:nn:ss") & " av " & Application.UserName & Chr(10) & "Tidligere info: " & sOld
If .CommentThreaded Is Nothing Then
.AddCommentThreaded sCmt
Else
.AddCommentThreaded sCmt
End If
With .CommentThreaded.Shape.TextFrame
.AutoSize = True
.Characters(Start:=iLen + 1).Insert IIf(iLen, vbLf, "") & sCmt
End With
End With
End If
End Sub
Please try this code. It should just about do what you want although it uses the Note rather than the Comment. The new "Note" equals and replaces the former "Comment". Soo how you like it.
Private Sub Worksheet_Change(ByVal Target As Range)
' 199
Const sRng As String = "A5:AQ155" ' change as required
Dim sOld As String
Dim sNew As String
Dim sCmt As String
Dim iStart As Integer ' first character position
Dim iEnd As Integer ' lastcharacter position
If Not Intersect(Target, Range(sRng)) Is Nothing Then
Application.EnableEvents = False
With Target.Cells(1)
sNew = .Value2
Application.Undo
sOld = .Value2 ' get previous value
.Value2 = sNew
On Error Resume Next
With .Comment
sCmt = .Text ' get previous Note
.Delete
End With
On Error GoTo 0
If Len(sCmt) Then sCmt = vbLf & sCmt
sCmt = "Sist endra: " & Format$(Now, "dd Mmm YYYY hh:nn:ss") & " av " & _
Application.UserName & Chr(10) & "Tidligere info: " & sOld & sCmt
With .AddComment(sCmt)
Do
iEnd = iEnd + 1
iStart = InStr(iEnd, .Text, " av ", vbTextCompare) + 4
If iStart = 4 Then Exit Do
iEnd = InStr(iStart, .Text, Chr(10))
If iEnd = 0 Then iEnd = Len(.Text)
.Shape.TextFrame.Characters(iStart, iEnd - iStart).Font.Bold = True
Loop
End With
End With
Application.EnableEvents = True
End If
End Sub
The little game at the end which boldens the user name is intended to show how you might identify and modify part of the comment's text.

VBA Last Change Method

I am looking for a function to print in a comment box, who was the users that changed the data from that cell. What I have for now is this:
Private Sub Worksheet_Change(ByVal Target As Range)
If Range("A" & Target.Row).Value = "" Then GoTo EndeSub
If Not Intersect(Range("C:JA"), Target) Is Nothing Then
On Error GoTo EndeSub
Application.EnableEvents = False
Range("B" & Target.Row) = Now
End If
EndeSub:
Application.EnableEvents = True
End Sub
It "triggers" automatically when someone types something in a cell.
And is printing only the last user name that changed the data, but I want to be some kind of a log, to print all the users. Do you think it is possible?
One way is, insert a New Sheet and name it "Log" and place the two headers like this...
On Log Sheet
A1 --> Date/Time
B1 --> User
Now replace your existing code with this...
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.CountLarge > 1 Then Exit Sub
If Range("A" & Target.Row).Value = "" Then GoTo EndeSub
Dim wsLog As Worksheet
If Not Intersect(Range("C:JA"), Target) Is Nothing Then
On Error GoTo EndeSub
Set wsLog = Sheets("Log")
Application.EnableEvents = False
Range("B" & Target.Row) = Now
wsLog.Range("A" & Rows.Count).End(xlUp).Offset(1, 1) = Environ("UserName")
wsLog.Range("A" & Rows.Count).End(xlUp).Offset(1) = Now
End If
EndeSub:
Application.EnableEvents = True
End Sub
So each time any user makes changes in the target range, the time of change and the user name will be listed on Log Sheet.
Edit:
As per the new setup, these column headers should be there on the Log Sheet.
A1 --> Date/Time
B1 --> User
C1 --> Cell
D1 --> Old Value
E1 --> New Value
Then replace the existing code with the following two codes...
Dim oVal
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.CountLarge > 1 Then Exit Sub
If Range("A" & Target.Row).Value = "" Then GoTo EndeSub
Dim wsLog As Worksheet
If Not Intersect(Range("C:JA"), Target) Is Nothing Then
On Error GoTo EndeSub
Set wsLog = Sheets("Log")
Application.EnableEvents = False
Range("B" & Target.Row) = Now
wsLog.Range("A" & Rows.Count).End(xlUp).Offset(1, 1) = Environ("UserName")
wsLog.Range("A" & Rows.Count).End(xlUp).Offset(1, 2) = Target.Address(0, 0)
wsLog.Range("A" & Rows.Count).End(xlUp).Offset(1, 3) = oVal
wsLog.Range("A" & Rows.Count).End(xlUp).Offset(1, 4) = Target.Value
wsLog.Range("A" & Rows.Count).End(xlUp).Offset(1) = Now
End If
EndeSub:
Application.EnableEvents = True
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Target.CountLarge > 1 Then Exit Sub
If Not Intersect(Range("C:JA"), Target) Is Nothing Then
oVal = Target
End If
End Sub
In a Public Module
Sub LogChange(Target As Range)
Dim cell As Range, vNew As Variant, vOld As Variant
vNew = Target.value
Application.Undo
vOld = Target.value
Target.value = vNew
With getLogWorksheet
With .Range("A" & .Rows.Count).End(xlUp).Offset(1)
' Array("Date/Time", "UserName", "Worksheet", "Address", "Old Value", "New Value")
.Resize(1, 6).value = Array(Now, Environ("UserName"), Target.Parent.Name, Target.Address(False, False), vOld, vNew)
End With
End With
End Sub
Private Function getLogWorksheet() As Workbook
Dim ws As Worksheet
On Error Resume Next
Set ws = ThisWorkbook.Worksheets("Log")
On Error GoTo 0
If ws Is Nothing Then
Set ws = ThisWorkbook.Worksheets.Add
ws.Visible = xlSheetVeryHidden
ws.Name = "Log"
ws.Range("A1").Resize(1, 6).value = Array("Date/Time", "UserName", "Worksheet", "Address", "Old Value", "New Value")
End If
End Function
In a Worksheet Module
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Target.CountLarge > 1 Then
Application.Undo
MsgBox "Changing more than 1 cell at a time is prohibited", vbCritical, "Action Undone"
ElseIf Not Intersect(Range("C:JA"), Target) Is Nothing Then
LogChange Target
End If
End Sub
Another bit of code to give you some ideas:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
val_before = Target.Value
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Count > 1 Then
MsgBox Target.Count & " cells were changed!"
Exit Sub
End If
If Target.Comment Is Nothing Then
Target.AddComment
existingcomment = ""
Else
existingcomment = Target.Comment.Text & vbLf & vbLf
End If
Target.Comment.Text Text:=Format(Now(), "yyyy-mm-dd") & ":" & vbLf & Environ$("Username") & _
" changed " & Target.Address & " from:" & vbLf & """" & val_before & _
"""" & vbLf & "to:" & vblkf & """" & Target.Value & """"
End Sub
Any time a cell is selected, it stores the cell's existing value in a variable. If the cell is changed, it creates a new comment in the cell (or appends the existing comment if there is one) with the date, username, cell address, and the "before and after" values. This could be super annoying if someone's trying to make a lot of changes, and if there are multiple changes at once, then it will just warn you without creating a comment. I'd suggest you practice on a blank workbook (or a 2nd copy of the one you're working on) in case there are any problems. Be sure to Google any of the properties/methods than you are unfamiliar with, for the sake of learning, and for building a solution to fit your needs!

Resources