I was looking for a code to automatically insert the ':' (colon) into the columns R and S, W and X, and found code that I thought I could customise to my needs, but I am facing two issues:
The code works in R and S, but also need the code to run in columns W and X as well
I get an error:
Variable not Defined - stopping at TLen and I guess it will also stop at TimeV
The programmer doesn't use the Option Explicit, (it works OK without Option Explicit). But all my code is always with Option Explicit, but I'm not sure how to write the Dim for the two variables.
This code is in a specific worksheet, in the Worksheet_Change sub, where I have other code for other things, like the timestamp when people make a selection from column B, it will automatically populate when a selection is made in column B.
I have tried the colon code in another workbook, without the Option Explicit and it works without giving errors.
The source of the code came from
Excel VBA tips n tricks #12 no more colons when typing time of day, type 123 instead of 01colon23 AM
I've adapted the code to reference columns R and S in the code below.
Private Sub Worksheet_Change(ByVal Target As Range)
' This code will ADD the COLON for TIME automatically
' The code is from: https://www.youtube.com/watch?v=ATxaNbTV2d0 (Excel is Fun -
' Excel VBA Tips n Tricks #12 NO MORE COLONS When Typing Time of Day, Type 123 instead of 01colon23 AM
' To avoid an error if you select more than 1 cell, this next line of code will exit the sub
If Selection.Count > 1 Then
Exit Sub
End If
If Not Intersect(Range("R4:S1200"), Target) Is Nothing Then
TLen = Len(Target)
[![Layout of Worksheet and sample of the columns that need automatic insertion of colons ][1]][1]
If TLen = 1 Then
TimeV = TimeValue(Target & ":00")
ElseIf TLen = 2 Then
TimeV = TimeValue(Target & ":00")
ElseIf TLen = 3 Then
TimeV = TimeValue(Left(Target, 1) & ":" & Right(Target, 2))
ElseIf TLen = 4 Then
TimeV = TimeValue(Left(Target, 2) & ":" & Right(Target, 2))
ElseIf TLen > 4 Then
'Do nothing
End If
'Target.NumberFormat = "HH:MM"
Application.EnableEvents = False
Target = TimeV
Application.EnableEvents = True
End If
End Sub
Expand the range of the Intersect Intersect(Range("R:S,W:X"),Target).
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Cells.Count > 1 Then Exit Sub
If IsNumeric(Target) = False Then
MsgBox Target & " is not a number", vbExclamation
Exit Sub
ElseIf Intersect(Range("R:S,W:X"), Target) Is Nothing Then
Exit Sub
End If
Dim n As Long
n = Len(Target)
If n >= 1 And n <= 4 Then
Application.EnableEvents = False
Target.NumberFormat = "hh:mm"
If n <= 2 Then
Target.Value2 = TimeSerial(Target, 0, 0)
Else
Target.Value2 = TimeSerial(Int(Target / 100), Target Mod 100, 0)
End If
Application.EnableEvents = True
End If
End Sub
I understand that you are 'stretching & teaching' me to work things out for myself, and it is appreciative (and I definitely have learned how to see the type (1.)). But in this instance, the 'Type' is coming as Variant/Date, even though it is meant to be time (maybe I am misunderstanding the syntax). – TheShyButterfly
You did well! Yes, that is one way to find the type. The other way is to use the VarType function:
Option Explicit
Sub Sample()
Dim TimeA
TimeA = TimeValue("01:00 PM")
MsgBox VarType(TimeA)
End Sub
This will give you 7 which is vbDate.
You can also store time as Variant and Double as shown below.
Option Explicit
Sub Sample()
Dim TimeA As Date
Dim TimeB As Double
Dim TimeC As Variant
TimeA = TimeValue("01:00 PM")
TimeB = TimeValue("01:00 PM")
TimeC = TimeValue("01:00 PM")
MsgBox "Time stored as Date : " & TimeA
MsgBox "Time stored as Double : " & TimeB
MsgBox "Time stored as Variant : " & TimeC
MsgBox "TimeA formated as Date : " & Format(TimeA, "hh:mm:ss AM/PM")
MsgBox "TimeB formated as Date : " & Format(TimeB, "hh:mm:ss AM/PM")
MsgBox "TimeC formated as Date : " & Format(TimeC, "hh:mm:ss AM/PM")
End Sub
but without an example how am I to learn, I have obviously exhausted my search on resolving this, but found nothing .. the reason why I posted the question. Thank you for encouraging me to continue solving things on my own :) TheShyButterfly
You can write the range as CDP1802 shown in his post or you can use the Application.Union method (Excel).
For example,
Option Explicit
Sub Sample()
Dim rngA As Range
Dim rngB As Range
Dim rngCombined As Range
Set rngA = Range("R4:S1200")
Set rngB = Range("W4:X1200")
Set rngCombined = Union(rngA, rngB)
MsgBox rngCombined.Address
End Sub
So in your code it becomes Intersect(rngCombined, Target) Is Nothing.
Also since you are working with Worksheet_Change and Events, I recommend seeing Working with Worksheet_Change.
Related
I might post my question here and hope for help before I bust my head through the wall.
Here is the deal: I have a date(Data_carga) and a time(Hora_carga) for the load the user changed its status. So I want to apply his change to my back-end Loads Table.
My Load table (Table5) is always custom sorted by date. So what I thought was to xmatch my Data_carga against the date column (Worksheets("CargasBD").Range("Table5[DATA]")) both from first to last (row1) and from last to first(row2). And then, using those two rows combined in an indirect function, I would xmatch the Hora_carga against that specific interval. Then I would have the map to the same load on the back-end table to update it.
But I cannot get the xmatch function to simple locate a value in a table on another tab.I keep getting this runtime error 1004.
Both my looked-up value and table are Date type. So I have no idea why I am getting this error.
Code is bellow.
Private Sub Worksheet_Change(ByVal Target As Range)
Application.EnableEvents = False
Dim Data_carga As Date
Dim Hora_carga As Date
Dim Novo_status As String
Dim row, row1, row2 As Integer
Dim addrss, Range_data As String
Set In_range = Application.Intersect(Target, Range("$D$3:$P$11"))
If Not In_range Is Nothing Then
Data_carga = Range("A" & Target.row).Value
Hora_carga = Range(Left(Target.Address, Len(Target.Address) - Len(CStr(Target.row))) & "2").Value
Novo_status = Application.WorksheetFunction.XLookup(Target.Value, Sheets("BD").Range("Table17[Abrev]"), Sheets("BD").Range("Table17[Status das cargas]"), "", 0, 1)
'This is where it breaks
row1 = Application.WorksheetFunction.XMatch(Data_carga, Worksheets("CargasBD").Range("Table5[DATA]"), 0, 1)
row2 = Application.WorksheetFunction.XMatch(Data_carga, Worksheets("CargasBD").Range("Table5[DATA]"), 0, -1)
addrss = Application.WorksheetFunction.Concat("CargasBD!C", 1 + row1, ":C", 1 + row2)
Range_data = Application.WorksheetFunction.INDIRECT(addrss, True)
row = Application.WorksheetFunction.XMatch(Hora_carga, Range_data, 0, 1)
Sheets("CargasBD").Range("D" & row + row1).Value = Novo_status
End If
Application.EnableEvents = True
End Sub
Btw, the date(Data_carga) and a time(Hora_carga) are getting fetched okay.
Thank you in advance.
Got it working finally.
So what I had to do was:
Change my looked-up variables as Range types instead of Date
Use Set to copy the data to these two i.e. Hora_cargaand Data_carga
I had messed a lot with it. It would work if I pasted the value from the Data_carga into a cell and use that on the xmatch formula but I did NOT want to take this detour. So there was something odd about my variable (that I had changed it to be a range type already) and a range in the worksheet.
One thing before continuing: the time value (Hora_carga) that was okay started being funky and while looking up the error I was directed to use the Set command for it.
So I used the Locals Window to investigate my date range variable and saw this strange difference between my two variables:
Even though I had decalred both as Range type, my hora_cargaended up becoming a Date type. And it had a weird valeu with #s. So I just replicated what I had done to my Hora_carga and it worked.
Here is the final code:
Private Sub Worksheet_Change(ByVal Target As Range)
Application.EnableEvents = False
Dim Data_carga, Hora_carga As Range
Dim Novo_status, addrss As String
Dim HoraRow, rowSubRngStrt, rowSubRngEnd, rowHdr As Integer
Set In_range = Application.Intersect(Target, Range("$D$3:$P$11"))
If Not In_range Is Nothing Then
Set Data_carga = Range("A" & Target.row)
Set Hora_carga = Range(Cells(2, Target.Column).Address(0, 0))
Novo_status = Application.WorksheetFunction.XLookup(Target.Value, Sheets("BD").Range("Table17[Abrev]"), Sheets("BD").Range("Table17[Status das cargas]"), "", 0, 1)
rowHdr = Sheets("CargasBD").ListObjects("Table5").HeaderRowRange.row
rowSubRngStrt = Application.WorksheetFunction.XMatch(Data_carga, Sheets("CargasBD").ListObjects("Table5").DataBodyRange.Columns(1), 0, 1) + rowHdr
rowSubRngEnd = Application.WorksheetFunction.XMatch(Data_carga, Sheets("CargasBD").Range("Table5[DATA]"), 0, -1) + rowHdr
addrss = Application.WorksheetFunction.Concat("C", rowSubRngStrt, ":C", rowSubRngEnd)
HoraRow = Application.WorksheetFunction.XMatch(Hora_carga, Sheets("cargasBD").Range(addrss), 0, 1)
Sheets("CargasBD").Range("D" & (rowSubRngStrt + HoraRow - 1)).Value = Novo_status
End If
Application.EnableEvents = True
End Sub
I'm making a spreadsheet template that requires the use to digitally "sign" certain boxes as they fill it out. The method for this is to double click said box, VBA puts in their application username, and if there is an associated date box for that signature (not all signatures are required to be dated) it will insert that to the relevant box.
Because this template is going to be replicated with signature locations changed slightly for each time, I didn't want the signatures hard coded into the VBA, and instead have them returned from a set of values on the sheet.
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
Dim i As Integer, counter As Integer, g As Integer
Dim location As String, dated As String
i = Range("W2").Value
If Not Application.Intersect(Target, Range("A1:L50")) Is Nothing Then
Cancel = True
For counter = 1 To i
g = (counter + 3)
location = Range("V" & g).Value
dated = Range("W" & g).Value
If Target.Address = ("V" & g) Then
MsgBox Target.Cells(1).Address
If MsgBox("You are about to sign this document as " & Application.UserName & "." & _
" Please ensure all information is correct. Once signed the document cannot be edited.", vbOKCancel) = vbCancel Then
Exit Sub
Else
Range(location).Value = Application.UserName
If Not Range("W" & g).Value = "No" Then
Range(dated).Value = Now
Else:End If
End If
End If
Next counter
End If
End Sub
if I comment out the for loop, and
If Target.Address = ("V" & g) Then
and set g as a hard number, the code will run and fill out whatever row of signature and date corresponds to that g value. I was trying to use to for loop to make the g value change based on the cell double clicked, but with the full code not commented out, it doesn't seem to run when any cell in the range is double clicked. Do I have a misunderstanding of how to use a for loop? Or have I missed a much easier method?
I currently am using 2 tables on 2 different worksheets.
The first acts as a list of items while the second acts as an active sheet where an end user can research items and has a couple of possible interactions which are not relevant to the ends of the question.
Is there a way to use a part of the button name to compose the range i will use in the code?
In alternative, is there a smart way to make this scalable, possibly keeping 1 copy of the code and somehow depending on the button assign the ranges?
Sub Button2_Click()
Dim cb As Shape
Dim x As String
Dim y As Variant
On Error Resume Next
If IsEmpty(Range("A2").Value) Then
MsgBox "Barcode appears to be empty!"
Exit Sub
Else
If IsError(Range("E2").Value) Then
MsgBox "Barcode appears to have no match!"
Exit Sub
Else
y = (Range("J2").Value)
If IsNumeric(y) Then
x = Range("J2").Value
Else
MsgBox "Ammount to add/subtract must be a number!"
Exit Sub
End If
Set cb = ActiveSheet.Shapes("chkbx2")
If cb.OLEFormat.Object.Value = 1 Then
If (Range("E2").Value - Range("J2").Value) < 0 Then
MsgBox "quantity can't go below 0"
Else
Range(Range("M2")).Value = Range("E2").Value - Range("J2").Value
MsgBox "Subtracted " + x + " part(s) to component: " + CStr(Range("A2").Value)
End If
Else
Range(Range("M2")).Value = Range("E2").Value + Range("J2").Value
MsgBox "Added " + x + " part(s) to component: " + CStr(Range("A2").Value)
End If
End If
End If
End Sub
I left the full code for the macro just so that i am sure nothing is going to be missing.
My issue is this: i have 25 of these buttons currently in my sheet and i have found no way to "automate" the change in coordinates.
For example Button2_Click() -> uses coordinates on row2 like "A2", "E2", "J2"... in the same way Button3_Click() "A3", "E3", "J3"...
Currently i manually wrote the code once per each button, which makes the project hard to escalate to bigger proportions.
I managed to solve this issue and produce a working code instead of 25 instances of the same lines by using this:
Dim r As Variant
Dim v As Variant
v = Application.Caller
v = Replace(v, "Button ", "")
r = "A" & v
To generate all the ranges i need, thus changing the value contained inside r before each range so as to have the exact range i needed there, this successfully made the project easily scaleable.
Note: an example of legal syntax using the variable to compose a range would be:
this assumes you are calling this function trough an object named "Button x"
where x is a number
Sub Button_Click()
Dim r As Variant
Dim v As Variant
v = Application.Caller
v = Replace(v, "Button ", "")
r = "A" & v
If IsEmpty(Range(r).Value) Then
MsgBox "Cell appears to be empty!"
Exit Sub
Else
MsgBox "cell " + r + " contains " + Range(r).Value
End If
End Sub
I used the cell notation made with letter+number clearly.
I used 2 variables because i needed to compose multiple ranges and it allowed for a cleaner implementation
I used variables of "variant" type thinking it neede to be compatible with different types of data in the code.
Note: this also works for the checkbox i was talking about in the question
in general this will help building a re-usable code that will be able to know where your data will be located depending on the name that was assigned to the button calling it, also assuming a naming convention is set and followed and that it's increment follows a rule in relation to the increment of the ranges.
Thanks #EvR for the help tracking down the function that allowed me to proceed forward
I often get names in which I need to work with and reconcile some of their information. These names would often come in many different formats. The current VBA script I've got on is as follows:
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Cells.Count > 1 Or Target.HasFormula Then Exit Sub
On Error Resume Next
If Not Intersect(Target, Range("B:B")) Is Nothing Then
Application.EnableEvents = False
Target.Value = StrConv(Target.Value, vbProperCase)
Application.EnableEvents = True
End If
On Error GoTo 0
End Sub
It does the trick with changing names to Proper case but I'm also hoping to automate capitalized names that has the surname in the front, separated with a comma (e.g. SMITH, JOHN).
In researching, I have found the following formula to work brilliantly, but this is not in VBA - I need it to auto convert the same cell:
=RIGHT(A1,LEN(A1)-LEN(LEFT(A1,FIND(",",A1)-1))-2) & " " & LEFT(A1,FIND(",",A1)-1)
Would there be anyway if I could have this formula functioning in the VBA script?
Lastly, it would amaze me if this script could recognize specialized surnames, especially capitalize the letter after a symbol (e.g. Mary-Lee / O'Connor).
Any suggestion would be greatly appreciated! Thanks in advance!
I converted your worksheet formula to VBA, you can use INSTR to get the same result as FIND, the arguments are passed to it in the opposite order but apart from that its the same. (I made a string called A1 to make it easier to compare to your example)
Dim A1 As String
A1 = Target.Value
If (InStr(A1, ",") > 0) Then
Target.Value = Right(A1, Len(A1) - Len(Left(A1, InStr(A1, ",") - 1)) - 2) & " " & Left(A1, InStr(A1, ",") - 1)
End If
This will capitalise the next character after an apostrophe, you can edit it to handle other punctuation
Dim A1 As String
A1 = Target.Value
Dim i As Integer
i = InStr(A1, "'") 'position of the '
If (i > 0 And Len(A1) > i) Then 'check there is a ' present and there is a character after it
Target.Value = Left(A1, i) & UCase(Mid(A1, i + 1, 1)) & Right(A1, Len(A1) - i - 1)
End If
Add just before you Application.EnableEvents = True line:
If InStr(Target.Value, ",") > 0 Then
Target.Value = UCase(Left(Target.Value, InStr(Target.Value, ",") - 1)) & Mid(Target.Value, InStr(Target.Value, ","), Len(Target.Value))
End If
NB: Add With Target as your first line of code and End With as your last and change all your Target.Value references to just .Value.
https://msdn.microsoft.com/en-us/library/wc500chb.aspx
My code so far is like this:
Sub FindMatchingValue()
Dim i As Integer, TimeValueToFind As Date
TimeValueToFind = "04:00:00"
Sheets("Vessels").Range("F07").ClearContents
For i = 1 To 25 '
If CDate(Sheets("Vessels").Cells(i, 1).Value) = TimeValueToFind Then
MsgBox ("Found value on row " & i)
Sheets("Vessels").Range("F07").Value = Cells(i, 1).Offset(1, 1).Resize(1).Value
Exit Sub
End If
Next i
MsgBox ("Value not found in the range!")
End Sub
This code checks Column A for the time inputted in the format xx:xx:xx Both where the input is, and where the times are written are set as "Time" format.
Initially the CDate edit was not added. And this caused the code to always return false because, as it had been put, I was trying to "compare apples to oranges".
However adding the CDate addition produces a mismatch error. Similarly changing both to be a double also did not work:
Sub FindMatchingValue()
Dim i As Integer, TimeValueToFind As Date
TimeValueToFind = "04:00:00"
Sheets("Vessels").Range("F07").ClearContents
For i = 1 To 25 '
If Sheets("Vessels").Cells(i, 1).Value = CDbl(TimeValueToFind) Then ' < This was the line changed
MsgBox ("Found value on row " & i)
Sheets("Vessels").Range("F07").Value = Cells(i, 1).Offset(1, 1).Resize(1).Value
Exit Sub
End If
Next i
MsgBox ("Value not found in the range!")
End Sub
However this one is a different reason, since Excel stores the values as floating points, each value is still different. "It is well known that the expression a==b is likely to return False when a and b are both doubles, even though you might think they are the same. This is due to the finite precision with which floating point numbers are stored."
The way around this would be to Set a tolerance. If abs(a-b)<tolerance Then
However i'm not particularly sure which tolerance to use nor how to write it to include without messing up the first loop.
I wonder if anyone could shed some light on this and direct me to which additions I need to make and what sort of tolerances would be acceptable? I think the question is essentially twofold. Thank you in advance!
Use TimeValue() or TimeSerial() like so:
Sub SO()
Dim x As Date
Dim y As Date
Dim z As Date
x = TimeValue("04:00:00")
y = TimeSerial(4, 0, 0)
z = CDate(Range("A1").value) '// A1 has "04:00:00" entered
Debug.Print x = y '// True
Debug.Print y = z '// True
Debug.Print x = z '// True
End Sub
Putting this into the context of your code:
Sub FindMatchingValue()
Dim i As Integer, TimeValueToFind As Date
TimeValueToFind = TimeValue("04:00:00")
Sheets("Vessels").Range("F07").ClearContents
For i = 1 To 25 '
If CDate(Sheets("Vessels").Cells(i, 1).value) = TimeValueToFind Then
MsgBox ("Found value on row " & i)
Sheets("Vessels").Range("F07").value = Cells(i, 1).Offset(1, 1).Resize(1).value
Exit Sub
End If
Next i
MsgBox ("Value not found in the range!")
End Sub
You are correct that the imprecision of floating point numbers is the cause of your problem. Remember that the underlying data in a Date data type is still a Double, formatted to look like a date.
The question of "...what sort of tolerances would be acceptable?" is really up to you. Given that your test value is "hh:mm:ss" then equal to the second may suffice.
There are many ways to achieve this. If your data is formatted as "hh:mm:ss" then this will work
If CDate(Sheets("Vessels").Cells(i,1).Text) = TimeValueToFind Then
This relies on the format applied to the sheet being to the same precision as your test value
For those interested, here is the answer:
Sub FindMatchingValue()
Dim i As Integer, TimeValueToFind As Date, Delta As Double, Tolerance As Double
TimeValueToFind = Sheets("Vessels").Range("F06")
Tolerance = 0.001
Sheets("Vessels").Range("F07").ClearContents
For i = 2 To 25 '
Delta = Sheets("Vessels").Cells(i, 1).Value - CDbl(TimeValueToFind)
If Abs(Delta) <= Tolerance Then
MsgBox ("Found value on row " & i)
Sheets("Vessels").Range("F07").Value = Cells(i, 1).Offset(0, 1).Resize(1).Value
Exit Sub
End If
Next i
MsgBox ("Value not found in the range!")
End Sub
So any time in the box F06 typed in, it now finds. A combination of tolerance was used and also converting to a Double. i = 1-25 was changed to 2-25, because I had a text header and that was producing a mismatch error.