Vba to find if a string split is null? - vba

I have an access database, in many of the tables is a field called "Notes".
This field is used by techs to, well, make notes on the equipment.
But these notes need to be broken up into useful groups, as such we've chosen to use "|" as the delimiter. ( . , ; : - _ / \ all have valid notes uses and can not be assigned to this role)
I've tried :
If Split(rst!Notes, "|")(1).Property = "" Then
aNotesOver = ""
Else
aNotesOver = Split(rst!Notes, "|")(1)
End If
'AND:
If Split(rst!Notes, "|")(1) <> "" Then
aNotesOver = Split(rst!Notes, "|")(1)
Else
aNotesOver = Nz(Split(rst!Notes, "|"), "")
End If
'AND:
If Nz(Split(rst!Notes, "|")(1)) = "" Then
aNotesOver = ""
Else
aNotesOver = Split(rst!Notes, "|")(1)
End If
'AND I tried:
If Not IsNull(Split(rst!Notes, "|")(1)) Then
aNotesOver = Split(rst!Notes, "|")(1)
Else
aNotesOver = ""
End If
None of them work, and I keep getting the "Invalid use of Null " Error.
Anyone have any suggestions?

This is one of those unfortunate quirks of VBA. if the passed value is null, then the split function fails.
and if you pass a empty string, then the ubound() value of the array is -1!!!!
So, this is messy.
I would suggest that you build your own custom function:
Public Function MySplit(v As Variant, ix As Integer) As String
MySplit = ""
If IsNull(v) Then Exit Function
If v = "" Then Exit Function
Dim vBuf As Variant
vBuf = Split(v, "|")
If ix > UBound(vBuf) Then Exit Function
MySplit = vBuf(ix)
End Function
So you could add a delimter to this function.
But, now your code is:
aNotesOver = MySplit(rst!Notes, 1)
And if the 1 is larger then the number of values, it will still return ""

Related

VBA: loop losing variable value

I've got a strange problem with a loop in VBA, as it seems to be losing the value of my variable. Any ideas why? If i delete the loop, debug.print shows "test", otherwise it's empty (unless I print the value of "dupa" inside the loop)... Seems very strange.
Function carbon_copy(indeks As String) As String
Dim emails(1 To 3) As String
Dim i As Integer
Dim dupa As String
emails(1) = "abc#wp.pl"
emails(2) = "pabc#wp.pl"
emails(3) = "rabc#wp.pl"
i = 1
dupa = "test"
Do While emails(i) <> ""
If i = indeks Then
GoTo NextIteration
End If
dupa = dupa & ";" & emails(i)
NextIteration:
i = i + 1
Loop
Debug.Print dupa
carbon_copy = dupa
End Function
You should get a runtime error 9 since you index i will be 4 after you looped through your emails String array. As soon as it tries to compare the value of emails(4) with "" it should produce the "index out of range" since you have defined your Array to be only 3 elements long.
For a little clarification try this example code, it should produce the same error:
Function littleTest()
Dim teststr(1 To 3) As String
Dim i As Integer
teststr(1) = "abc"
teststr(2) = "def"
teststr(3) = "ghi"
i = 1
Do While teststr(i) <> ""
Debug.Print "i do it for the " & i & " time!"
i = i + 1
Loop
End Function
You have already found the solution yourself since UBound() is returning the actual length of your array which is in your case three so it will never search beyond the array.
You're indexing out of the array bounds. The condition Do While emails(i) <> "" is always true given your array, so the this fails on emails(4). Just test the array bounds and loop over that:
For i = LBound(emails) To UBound(emails)
If emails(i) <> "" And i = indeks Then
dupa = dupa & ";" & emails(i)
End If
Next
Actually, I've already solved the problem by using other loop type (For i = 1 To UBound(emails), Next i), but why the previous loop did not work is still quite mysterious to me... If anyone can explain, I'd appreciate it, as I prefer to understand things rather thank just do them correctly.
W.
this should work (explanations in comments):
Function carbon_copy(indeks As Long) As String
Dim emails(1 To 3) As String
Dim i As Long
Dim dupa As String
emails(1) = "abc#wp.pl"
emails(2) = "pabc#wp.pl"
emails(3) = "rabc#wp.pl"
i = 1
Do While emails(i) <> ""
If i <> indeks Then dupa = dupa & ";" & emails(i) ' update 'dupa' if current index doesn't natch passed 'indeks'
i = i + 1
If i > UBound(emails, 1) Then Exit Do ' be sure to exit upon exceeding 'emails()' array size
Loop
carbon_copy = dupa
End Function

VBA Handling multiple custom datatype possibilities

I have done some research and haven't found any similar question.
I have a VBA macro that imports a .CSV file containing telegrams sent by a device.
In the end of this macro, I want to create a graph with the time elapsed on the x-axis and the value corresponding to the telegram.
The issue is that this value can be of different types: hexadecimal, boolean, integer... And that they don't respect the standard Excel number format, which means that they can't be used to create a graph.
Here are some examples (with " around the value to show its start and end) :
hexadecimal : "A7 C8"
Boolean : "$00" or ""$01"
Percentage : "$30"
And here is an example of data, with custom time format and boolean value
Here is my related code so far, where I try to convert into a custom type then convert back to numeric to get a common number datatype :
If wsRes.Range("R1").Value Like "$##" Then
wsRes.Range("R1:R" & plotLine).NumberFormat = "$##"
wsRes.Range("R1:R" & plotLine).NumberFormat = General
End If
If wsRes.Range("R1").Value Like "??[ ]??" Then
Dim valArray(1) As String
For i = 1 To plotLine Step 1
valArray = Split(wsRes.Range("R" & i), " ")
wsRes.Range("R" & i).Value = ToInt32(valArray(0) + valArray(1), 16)
wsRes.Range("" & i).NumberFormat = General
Next i
End If
I haven't been able to test it with hexa yet, but the conversion trick doesn't work with percentage/boolean
EDIT :
First, thank you for your answers.
Here is my final code for anyone's interested, adapted from Vityata's.
This method will allow to easily add other datatypes if needed.
Sub TestMe()
Dim RangeData as String
Set wsRes = ActiveWorkbook.Sheets("Results")
For i = 1 To plotLine Step 1 'plotLine is the last line on which I have data
DetectType wsRes.Range("R" & i).Value, i
Next i
RangeData = "Q1:R" & plotLine
CreateGraph RangeData 'Call My sub creating the graph
End Sub
Public Sub DetectType(str As String, i As Integer)
Select Case True
Case wsRes.Range("R" & i).Value Like "??[ ]??"
wsRes.Range("R" & i).Value = HexValue(str)
Case wsRes.Range("R" & i).Value Like "?##"
wsRes.Range("R" & i).Value = DecValue(str)
Case Else
MsgBox "Unsupported datatype detected : " & str
End
End Select
End Sub
Public Function HexValue(str As String) As Long
Dim valArray(1) As String 'Needed as I have a space in the middle that prevents direct conversion
valArray(0) = Split(str, " ")(0)
valArray(1) = Split(str, " ")(1)
HexValue = CLng("&H" & valArray(0) + valArray(1))
End Function
Public Function DecValue(str As String) As Long
DecValue = Right(str, 2)
End Function
You need three boolean functions, following your business logic and some of the Clean Code principles (although the author of the book does not recognize VBA people as programmers):
IsHex()
IsBoolean()
IsPercentage()
Public Sub TestMe()
Dim myInput As Variant
myInput = Array("A7C8", "$01", "$30")
Dim i As Long
For i = LBound(myInput) To UBound(myInput)
Debug.Print IsHex(myInput(i))
Debug.Print IsBoolean(myInput(i))
Debug.Print IsPercentage(myInput(i))
Debug.Print "-------------"
Next i
'or use this with the DetectType() function below:
'For i = LBound(myInput) To UBound(myInput)
' Debug.Print DetectType(myInput(i))
'Next i
End Sub
Public Function IsHex(ByVal str As String) As Boolean
On Error GoTo IsHex_Error
IsHex = (WorksheetFunction.Hex2Dec(str) <> vbNullString)
On Error GoTo 0
Exit Function
IsHex_Error:
End Function
Public Function IsBoolean(ByVal str As String) As Boolean
IsBoolean = CBool((str = "$00") Or (str = "$01"))
End Function
Public Function IsPercentage(ByVal str As String) As Boolean
IsPercentage = (Len(str) = 3 And Left(str, 1) = "$" And IsNumeric(Right(str, 2)))
End Function
Then some additional logic is needed, because $01 is both Boolean and Percentage. In this case, you can consider it Percentage. This is some kind of a mapper, following this business logic:
Public Function DetectType(str) As String
Select Case True
Case IsHex(str)
DetectType = "HEX!"
Case IsPercentage(str) And IsBoolean(str)
DetectType = "Boolean!"
Case IsPercentage(str)
DetectType = "Percentage!"
Case Else
DetectType = "ELSE!"
End Select
End Function

Proper use of boolean/case?

I'm having issues figuring out if I properly wrote this function. It'll be fed a string that either contains a "%" or a "#" as the last character ie. "TA_PQ_SI02_%". I just want this function to tell me if it's a % or #.
Did I do this in the most effiecient/proper way? I think not, and would like to learn why.
Private Function numberOrPercentCheck(ByVal cleanCode As String) As Boolean
Select Case cleanCode
Case Right(cleanCode , 1) = "#"
numberOrPercentCheck= True
Case Right(cleanCode , 1) = "%"
numberOrPercentCheck= False
Case Else
Debug.Print "Error: " & cleanCode & " is not formatted properly with a # or % at the end, and has been set to a default #"
numberOrPercentCheck = True
End Select
End Function
When you just want a Boolean:
Private Function numberOrPercentCheck(ByVal cleanCode As String) As Boolean
numberOrPercentCheck = Right(cleanCode, 1) = "#"
End Function
When you need a third possibility:
Private Function numberOrPercentCheck(ByVal cleanCode As String) As Variant
Select Case Right(cleanCode, 1)
Case "#": numberOrPercentCheck = True
Case "%": numberOrPercentCheck = False
Case Else: numberOrPercentCheck = "Error: " & cleanCode & " is not formatted properly with a # or % at the end"
End Select
End Function

multiple criteria for an if statement

I have a form with 3 text boxes, txt_customeracc, txt_customername, txt_customercontact
These 3 text boxes are optional and by default the text boxes will have "N/A" displaying on form load, but if a user enters information into one of them I want them to enter the information in the other two boxes also.
The code I am using is below
If txt_customername.Text <> "" Or txt_customername.Text <> "N/A" Or
txt_customercontact.Text <> "" Or txt_customercontact.Text <> "N/A" And
txt_customeracc.Text = "" Or txt_customeracc.Text = "N/A"
Then error1 += vbNewLine & "Please enter a correct Customer Account Number"
So from the above code I am expecting that if a user enters information in either the txt_customername or the txt_customercontact text boxes but not in the txt_customeracc box the warning should then appear but currently the warning message is displaying regardless of whether information is or isn't entered in any of the boxes. Can anyone tell me what I am doing wrong?
What is operator precedence ?
Your main problem here is that you have an issue with operator precedence. What is that ?
It is exactly the same issue as when doing calcuations, multiplication comes first, then comes addition. Well in VB .NET, And operator comes before Or, so what you have written in your code is evaluated as follow :
If txt_customername.Text <> "" Or
txt_customername.Text <> "N/A" Or
txt_customercontact.Text <> "" Or
(txt_customercontact.Text <> "N/A" And txt_customeracc.Text = "") Or
txt_customeracc.Text = "N/A"
Then
error1 += vbNewLine & "Please enter a correct Customer Account Number"
End If
Since this is not really what you want, let's build this together :
if customername OR customercontact is filled up
AND
customeracc is empty
That would give us :
if (
(txt_customername.Text <> "" Or txt_customername.Text <> "N/A") 'CustomerName is filled up
Or
(txt_customercontact.Text <> "" Or txt_customercontact.Text <> "N/A") 'Customer Contact is filled up
)
And
(txt_customeracc.Text = "" Or txt_customeracc.Text = "N/A") 'Customer account is empty
Then
'Do whatever
End If
Make it better, call a function
Another problem here is readability, this code may have errors because it's hard to read, so hard to debug.
What we can do is build a function that will check if a textbox is empty :
Private Function IsEmpty(Tb As Textbox) As Boolean
'Here we return true if tb.Text is empty or contains "N/A"
Return Tb.Text = "" Or Tb.Text = "N/A"
End Function
So that would make this a bit more readable :
if (Not IsEmpty(txt_customername) Or Not IsEmpty(txt_customercontact)) 'CustomerName or Customer Contact is filled up
And IsEmpty(txt_customeracc) 'Customer account is empty
Then
'Do whatever
End If
Make it better (2), Compare the strings
As stated by zaggler in his comment, here we don't use String Comparison. What if a user starts typing, then decides to put it back to N/A and writes it lowercase ("n/a") ? Well, we will make a mistake, believing that he did fill up the Textbox and you will end up searching for user "n/a" in your database, which is not a very good idea...
So let's compare the String, make our function even better :
Private Function IsEmpty(Tb As Textbox) As Boolean
'Here we return true if tb.Text is empty or contains "N/A" (or "n/a")
Return Tb.Text = "" Or (String.Compare(Tb.Text, "N/A", True) = 0)
End Function
Note
You can see here the advantage of functions. I wrote it because I didn't want to change to String.Compare() six times... Whenever you have the same code twice, it should be a function...
If you want to give the user a proper error message telling him what he has missed to fill out you have to split the if statement into several parts.
First check if all text boxes contain any valid data.
If not you can skip further checks directly.
If one textbox contains data check each and set error accordingly.
If (txt_customername.Text = "" OrElse txt_customername.Text = "N/A") AndAlso
(txt_customercontact.Text = "" OrElse txt_customercontact.Text = "N/A") AndAlso
(txt_customeracc.Text = "" OrElse txt_customeracc.Text = "N/A") Then
'No Error exit sub
Exit Sub
End If
'This part is only reached if one textbox contains data
If (txt_customername.Text = "" OrElse txt_customername.Text = "N/A") Then
error1 += vbNewLine & "Please enter a correct Customer Name"
End If
If (txt_customercontact.Text = "" OrElse txt_customercontact.Text = "N/A") Then
error1 += vbNewLine & "Please enter a correct Customer Contact"
End If
If (txt_customeracc.Text = "" OrElse txt_customeracc.Text = "N/A") Then
error1 += vbNewLine & "Please enter a correct Customer Account Number"
End If
As you can see I also recommend using short circuit OrElse and AndAlso which gives a littttttttle performance.
You could count the number of filled fields.
Dim numberFilled As Integer = 0
If txt_customername.Text <> "" And txt_customername.Text <> "N/A" Then
numberFilled += 1
End If
If txt_customercontact.Text <> "" And txt_customercontact.Text <> "N/A" Then
numberFilled += 1
End If
If txt_customeracc.Text <> "" And txt_customeracc.Text <> "N/A" Then
numberFilled += 1
End If
If numberFilled = 1 Or numberFilled = 2 Then
error1 += vbNewLine & "Please enter a correct Customer Account Number"
End If
Personally I would have a function IsValueEmpty that would check:
Function IsValueEmpty(ByVal value As String) As Boolean
If String.IsNullOrEmpty(value) Or value = "N/A" Then
Return True
End If
Return False
End Function
Could also Trim.
You could have the relevant message part stored in the Tag property of each textbox and use Linq :
Dim customerTextBoxes = {txt_customeracc, txt_customername, txt_customercontact}
Dim messages = Aggregate customerTextBox In customerTextBoxes
Where customerTextBox.Text = "" OrElse customerTextBox.Text = "N/A"
Select $"Please enter a correct {customerTextBox.Tag}")
Into ToArray
Then just check it's length against the initial one and if they're not equal aggregate the message for display
If customerTextBoxes.Length <> messages.Length Then error1 = String.Join(Environment.NewLine, messages)

Type mismatch error using custom class subroutine in Excel VBA

Working in Excel VBA, I have a class module where I define my class 'Marker'. One of the properties of my class is TextLine(), which is an array that holds up to 5 strings. I have defined the two methods below in my class module. In another (regular) module, I fill markerArr() with my custom Marker objects. Loading each object's properties with data at each array index is working fine... However, after loading data into the object at each index, I try to use markerArr(count).ProcessLines but receive a type mismatch error. Since ProcessLines is a public sub in my class module, and markerArr(count) contains a Marker object, I can't seem to understand why this error is occurring... Am I overlooking something obvious?
'Serial number replacement processing function
Public Sub ProcessLines()
Dim strSerial As String
Dim toggle As Boolean
toggle = False
Dim i As Integer
For i = 0 To 4
If Trim(m_TxtLines(i)) <> "" Then
'Add linefeed char to non-empty text lines
m_TxtLines(i) = m_TxtLines(i) & Chr(10)
'Detect if it is a serialized line
If InStr(1, m_TxtLines(i), "XXXXXX-YYY") > 0 Then
m_Serial(i) = True
toggle = True
End If
End If
Next
'When at least one line on the marker is serialized, create and replace serial text
If toggle = True Then
'Only prompt for input once
If startSerNo < 1 And Num_Sers < 1 Then
startSerNo = InputBox("Enter the serial number to start printing at." & Chr(10) & _
"Entering 1 will result in -001, entering 12 will result in -012, etc.", "Starting Serial #", "1")
Num_Sers = InputBox("Enter the amount of serializations to perform." & Chr(10) & _
"This will control how many copies of the entire marker set are printed.", "Total Serializations", "1")
End If
strSerial = CreateSerial(startSerNo)
Dim j As Integer
For j = 0 To 4
If m_Serial(j) Then
m_TxtLines(j) = Replace(m_TxtLines(j), "XXXXXX-YYY", strSerial)
End If
Next
End If
End Sub
'Creates the string to replace XXXXXX-YYY by concatenating the SFC# with the starting serial number
Private Function CreateSerial(ByVal startNum As Integer)
Dim temp
temp = SFC_Num
Select Case Len(CStr(startNum))
Case 1
temp = temp & "-00" & startNum
Case 2
temp = temp & "-0" & startNum
Case 3
temp = temp & "-" & startNum
Case Else
temp = temp & "-001"
End Select
CreateSerial = temp
End Function
Your CreateSerial function takes an integer as a parameter, but you are attempting to pass a string. I've pointed out some problems:
If startSerNo < 1 And Num_Sers < 1 Then 'Here I assume, you have these semi-globals as a variant - you are using numeric comparison here
startSerNo = InputBox("Enter the serial number to start printing at." & Chr(10) & _
"Entering 1 will result in -001, entering 12 will result in -012, etc.", "Starting Serial #", "1") 'Here startSerNo is returned as a string from the inputbox
Num_Sers = InputBox("Enter the amount of serializations to perform." & Chr(10) & _
"This will control how many copies of the entire marker set are printed.", "Total Serializations", "1") 'here Num_Sers becomes a String too
End If
strSerial = CreateSerial(startSerNo) 'here you are passing a String to the CreateSerial function. Either pass an integer, or allow a variant as parameter to CreateSerial
'......more code.....
Private Function CreateSerial(ByVal startNum As Integer)