Testing if a string is null - vba

I am pretty new in VBA and I have not yet got used to the syntax completely, so I'm sorry if my question sounds stupid.
I am working with RequisitePro40 and VBA 7.0 in Word 2010. In one of my modules I have the following loop and If conditions:
Dim rqRequirements As ReqPro40.Requirements
Dim rqRequirement As ReqPro40.Requirement
Const eAttrValueLookup_Label = 4
Dim a As Integer
...
For Each vReqKey In rqRequirements
Set rqRequirement = rqRequirements(vReqKey)
If rqRequirement.AttrValue("MyAttreName", eAttrValueLookup_Label).text <> Null Then
a = 1
End If
If rqRequirement.AttrValue("MyAttreName", eAttrValueLookup_Label).text = Null Then
a = 2
End If
Next
In each iteration of the loop, both a = 1 and a = 2 are executed!!
Based on This, the equality and inequality operators are "=" and "<>". Therefore I would expect that either a = 1 or a = 2 execute for a string.
Is there something wrong with my syntax? Or is it a ReqPro related Problem?
I also tried using "Is" and "IsNot" operators but they result in Compiler error: Type mismatch
Can Someone help me with this?
Update: The actual goal is to see if the
rqRequirement.AttrValue("MyAttreName", eAttrValueLookup_Label).text
is Null or not. I added the second if to show the problem that the statement is somehow not working the way I expect it to work.
Replacing "Null" to "vbNullString" did not make any changes.
I also tried the IsNull function as #Slai suggested. the result is pretty much the same:
If IsNull(rqRequirement.AttrValue(att, eAttrValueLookup_Label).text) Then
a = 3
End If
If Not IsNull(rqRequirement.AttrValue(att, eAttrValueLookup_Label).text) Then
a = 4
End If
Both statements a = 3 and a = 4 are true and executed.

VBA doesn't support testing whether a string is "Null". VBA isn't like a .NET language or JavaScript (for example). The basic variable types all have a default value, a String is of zero length ("") from the moment the variable is declared - it has no uninstantiated state. You can also test for vbNullString.
If you test
Dim s as String
Debug.Print s = Null, s <> Null, s = "", s = "a", IsNull(s), s = vbNullString
The return is
Null Null True False False True
So if you're trying to test whether anything has been assigned to a String variable the only things you can do are:
Debug.Print Len(s), s = "", Len(s) = 0, s = vbNullString
Which returns
0 True True True
Note that the slowest of these possibilities is s = "", even though it seems the simplest to remember.

As others have noted, you want to test against the null version of a string, vbNullString, and not against Null specifically. In addition to this, you also need to make sure your object isn't null itself. For example:
Dim rqRequirements As ReqPro40.Requirements
Dim rqRequirement As ReqPro40.Requirement
Const eAttrValueLookup_Label = 4
Dim a As Long ' Avoid Integer since it has a strong habit of causing overflow errors.
...
For Each vReqKey In rqRequirements
Set rqRequirement = rqRequirements(vReqKey)
If Not rqRequirement Is Nothing Then
If rqRequirement.AttrValue("MyAttreName", eAttrValueLookup_Label).text <> vbNullString Then
a = 1
End If
If rqRequirement.AttrValue("MyAttreName", eAttrValueLookup_Label).text = vbNullString Then
a = 2
End If
End If
Next
Now, I haven't worked with this specific object type before, but I am fairly certain that AttrValue("MyAttreName", eAttrValueLookup_Label) is returning some kind of object. If this is the case, then the below pattern would be preferred:
Dim rqRequirements As ReqPro40.Requirements
Dim rqRequirement As ReqPro40.Requirement
Const eAttrValueLookup_Label = 4
Dim a As Long ' Avoid Integer since it has a strong habit of causing overflow errors.
...
For Each vReqKey In rqRequirements
Set rqRequirement = rqRequirements(vReqKey)
If Not rqRequirement Is Nothing Then
Dim Attribute as Object ' Or whatever type it should be
Set Attribute = rq.Requirement.AttrValue("MyAttreName", eAttrValueLookup)
If Not Attribute is Nothing Then
If Attribute.text <> Null Then
a = 1
End If
If Attribute.text = Null Then
a = 2
End If
End If
End If
Next
In this way, we are only ever calling upon the text property of the Attribute if we have actually set the Attribute. This avoids 424 errors down the line.
Finally, if you want to figure out what is happening in the code that is causing both if's to run, do something like this:
Debug.Print "Attribute Text: ", Attribute.Text
This will allow you to see what your code is seeing. You can consider using breakpoints as well.

1) I think you can use vbNullString to test for empty string. Otherwise use "Null" if actual string value.
2) Ensure a is declared as long
If rqRequirement.AttrValue("MyAttreName", eAttrValueLookup_Label).text <> vbNullString Then
a = 1
End If
If rqRequirement.AttrValue("MyAttreName", eAttrValueLookup_Label).text = vbNullString Then
a = 2
Else
a = 3
End If

I landed here looking for an answer to "VBA: How to test if a string is Null"
while this answer may not apply to this particular users situation, it does apply to the subject question.
Dim s As String, s2 As String
s = ""
s2 = vbNullString
Debug.Print StrPtr(s) = 0, StrPtr(s2) = 0
which returns
False True
because vbNullString is a C style NULL pointer for working with COM objects, and so its memory address when returned by the undocumented StrPtr function will always be 0

To ensure mutual exclusivity, ask the question only once.
a = IIf(rqRequirement.AttrValue("MyAttreName", eAttrValueLookup_Label).text = vbNullString , 2, 1)
You can also use an If-Then-Else construct, particularly if you have other actions you want to perform at the same time.
The above code example assumes the ~.text call is correct.

Related

Do-While loop (VBA) not looping

so I thought this would be a simple logical problem, but for the life of me I cannot find the issue with this code block. I have checked around on Stack for a solution, but all other do/while loop problems appear to be primarily with other languages.
What I am trying to do is simply loop through an array & add a new worksheet for each element in the array that is not null. Pretty simple right? Yet for some reason it simply loops through once and thats it.
Here is the code block:
Dim repNames() As String
Dim x As Integer
x = 25
ReDim repNames(1 To x)
repNames(1) = "Ahern"
repNames(2) = "Castronovo"
repNames(3) = "Glick"
repNames(4) = "Fields"
repNames(5) = "Murphy"
repNames(6) = "Sleeter"
repNames(7) = "Vivian"
repNames(8) = "Walschot"
repNames(9) = "Wilson"
Dim i As Integer
i = 1
Do 'Loop keeps creating only 1 new sheet. Should create 9.
Worksheets.Add.Name = repNames(i)
i = i + 2
Loop While repNames(i) <> Null
I believe the problem is with this line: Loop While repNames(i) <> Null,
but obviously the logical test seems to hold up.
Any help would be hugely appreciated!
As others note, Null is not the comparison you want to make. Testing anything for equivalence with Null will return Null -- even ?Null = Null returns Null, which is why your loop is exiting early. (Note: To test for a Null, you need to use the IsNull function which returns a boolean, but that is NOT how you test for an empty string.)
In VBA, to test for a zero-length string or empty string, you can use either "" or vbNullString constant, or some people use the Len function to check for zero-length.
Rectifying that error, as originally written in your code, your logical test should abort the loop if any item is an empty string, but none of the items are empty strings (at least not in the example data you've provided) so you end up with an infinite loop which will error once i exceeds the number of items in the repNames array.
This would be probably better suited as a For Each loop.
Dim rep as Variant
For Each rep in repNames
Worksheets.Add.Name = rep
Next
If you need to skip empty values, or duplicate values, you can add that logic as needed within the loop:
For Each rep in repNames
If rep <> vbNullString 'only process non-zero-length strings
Worksheets.Add.name = rep
End If
Next
Etc.
Firstly, you should be comparing to vbNullString. This loops multiple times:
' Declare variables
Dim repNames() As String
Dim x As Integer
Dim i As Integer
' Set data
x = 25
ReDim repNames(1 To x)
repNames(1) = "Ahern"
repNames(2) = "Castronovo"
repNames(3) = "Glick"
repNames(4) = "Fields"
repNames(5) = "Murphy"
repNames(6) = "Sleeter"
repNames(7) = "Vivian"
repNames(8) = "Walschot"
repNames(9) = "Wilson"
' Loop through items
i = 1
Do
Worksheets.Add.Name = repNames(i)
i = i + 2
Loop While repNames(i) <> vbNullString
There is one more problem – why i = i + 2 ? In your question you say you wanted the loop to execute 9 times, but using i = i + 2 skips every other item. If you indeed want to loop through every item:
Do
Worksheets.Add.Name = repNames(i)
i = i + 1
Loop While repNames(i) <> vbNullString
Here you go, I have changed the loop conditional, and changed i=i+2 to i=i+1. A regular while loop would be better than a do while encase the first element is empty
Dim repNames()
Dim x As Integer
x = 25
ReDim repNames(1 To x)
repNames(1) = "Ahern"
repNames(2) = "Castronovo"
repNames(3) = "Glick"
repNames(4) = "Fields"
repNames(5) = "Murphy"
repNames(6) = "Sleeter"
repNames(7) = "Vivian"
repNames(8) = "Walschot"
repNames(9) = "Wilson"
Dim i As Integer
i = 1
Do While repNames(i) <> ""
Worksheets.Add.Name = repNames(i)
i = i + 1
Loop

VB.NET - How do I use a variable in another variables name?

How would I do something similar to this:
Dim amnt As Integer
For x As Integer = 1 To 6
If amnt[x] = 0 Then
btn[x].Enabled = False
End If
Next x
What I mean is, can I reference a variable by using another variable in the name. For example, if "x" were to be 4, then I want to change the button "btn4" to .Enabled = False, but I also want to be able to change the button that I'm changing properties of. So like
If (variablesName & x) = 0 Then
If x is 4 then it should look like this
If variablesName4 = 0 Then
If you mean evaluating a variable by supplying it's name as a string - then this cannot be done in VBA.
For example:
Dim testVar As Integer
Dim v As String
v = "Var"
testVar = 5
Debug.Print test & v '// error
Debug.Print "test" & v '// prints "testVar" as a string
Debug.Print "testVar" '// prints "testVar" as a string
Debug.Print testVar '// prints "5" <~~ only way it can be done
However, for some controls, and some other items - you can access the parent collection and supply a string value to get the correct index. So something like:
For i = 1 To 10
'// Will set checkboxes 1 to 10 to be checked
myForm.Controls("Checkbox" & i).Checked = True
Next
As it stands - it's too broad a question to give a definitive answer, but depending on the kind of objects you are working with you may be able to access a parent collection in this way.

Type mismatch in for loop including tests of worksheet cell values

I am receiving a type mismatch error in my VBA macro. Here is the essential part of my code:
Public Function CalculateSum(codes As Collection, ws As Worksheet) As Double
On Error GoTo ErrorHandler
If ws Is Nothing Then
MsgBox ("Worksheet is necessery")
Exit Function
End If
Dim balanceColumnIndex, codesCulumnIndex As Integer
Dim searchStartRow, searchEndRow As Integer
balanceColumnIndex = 17
codesColumnIndex = 4
searchStartRow = 7
searchEndRow = ws.Cells(ws.Rows.Count, codesColumnIndex).End(xlUp).Row
Dim result As Double
result = 0#
For counter = searchStartRow To searchEndRow
If Len(ws.Cells(counter, codesColumnIndex)) > 0 And Len(ws.Cells(counter, balanceColumnIndex)) > 0 And _
IsNumeric(ws.Cells(counter, codesColumnIndex).Value) And IsNumeric(ws.Cells(counter, balanceColumnIndex).Value) Then
If Contains(codes, CLng(ws.Cells(counter, codesColumnIndex).Value)) Then
result = result + ws.Cells(counter, balanceColumnIndex).Value
''' ^^^ This line throws a type-mismatch error
End If
End If
Next counter
CalculateSum = result
ErrorHandler:
Debug.Print ("counter: " & counter & "\ncode: " & ws.Cells(counter, codesColumnIndex).Value & "\namount: " & ws.Cells(counter, balanceColumnIndex).Value)
End Function
Now what happens is that a type-mismatch error occures on the line where current row balance is added to result even though:
searchEndRow equals 129, and somehow counter equals 130
cells under current address are empty, yet somehow they pass test for length and numeric values (I stopped to debug at this point, IsNumeric(ws.Cells(counter, codesColumnIndex).Value) returns true!
Now I am simply confused and I don't know what to do. Please help.
As commenters have noted, Cells(...).Value is a Variant. This means that operators may not apply to .Value the way you expect. For tests using Len or other string operations, expressly convert to a string. For example, instead of Len(ws.Cells(...)), try Len(CStr(ws.Cells(...).Value)). That way you will know that Len is giving you the result you expect.
Similarly, where you add to result, use result = result + CDbl(ws.Cells(...).Value) to make sure you are adding Double values together.
To answer your question regarding errors that happen differently on different computers, what I have most often experienced is that it is the specific data in question. As one of the commenters pointed out, Empty is indeed numeric since it implicitly converts to 0! As a result, IsNumeric(Empty) is True. Using CStr guards against that in your code because IsNumeric(CStr(Empty)) = IsNumeric("") = False. Using IsNumeric(CStr(...)) prevents you from trying to add 0# + "", which is a type mismatch. So perhaps the user has an empty cell that you don't have in your test data, and that's causing the problem. That's not the only possibility, just the one I have encountered most.

String.compare returns true when strings aren't equal?

I've seen plenty of questions here about strings that are equal returning as unequal, but trust me to not get that problem.
I have this function.
Protected Sub ChkValidStockCode()
If Not (Voucher.ValidStockCode = "") Then
Dim validcount As Int32 = 0
Dim validproduct As String = Product.GetProductNameByCode(Voucher.ValidStockCode)
For Each rpi As RepeaterItem In rptCart.Items
Dim ProductID As HyperLink = CType(rpi.FindControl("hlProductID"), HyperLink)
Dim ProductName As HyperLink = CType(rpi.FindControl("hlProductName"), HyperLink)
If (String.Compare(Voucher.ValidStockCode.ToString(), ProductID.ToString())) Then
validcount = validcount + 1
End If
Next
If validcount = 0 Then
txtVoucher.Text = "Sorry, this voucher is only valid when purchasing a " & validproduct
failed = True
Exit Sub
End If
End If
End Sub
It's supposed to compare the two strings and increment a validcount integer if they're equal, and then tell you off if it gets to the end of the repeater without finding any matches.
The variables in this test are LT00004 (Voucher.ValidStockCode) and SP08076 (ProductID.ToString())
I have run the code several times, outputting the different strings as the result and can confirm they are what they should be, but when I try to compare them (and I expect validCount to be 0), they return as a match.
What did I do to screw this up?
You probably want String.Equals() and not String.Compare(). Compare is used to order things and not test for equality. What's happening is String.Compare is returning a non-zero number so the condition is being satisfied. The reason for that is because in VB "0" is False but any non-zero number evaluates to true. There's a whole history behind why that's the case but I digress.

Keeping a count in a dictionary, bad result when running the code, good result adding inspections

Weird problem. Stepping through the code with inspections gives me correct answers. Just running it doesn't.
This program loops through each cell in a column, searching for a regex match. When it finds something, checks in a adjacent column to which group it belongs and keeps a count in a dictonary. Ex: Group3:7, Group5: 2, Group3:8
Just stepping through the code gives me incorrect results at the end, but adding and inspection for each known item in the dictionary does the trick. Using Debug.Print for each Dictionary(key) to check how many items I got in each loop also gives me a good output.
Correct // What really hapens after running the code
Group1:23 // Group1:23
Group3:21 // Group3:22
Group6:2 // Group6:2
Group7:3 // Group7:6
Group9:8 // Group9:8
Group11:1 // Group11:12
Group12:2 // Group12:21
Sub Proce()
Dim regEx As New VBScript_RegExp_55.RegExp
Dim matches
Dim Rango, RangoJulio, RangoAgosto As String
Dim DictContador As New Scripting.Dictionary
Dim j As Integer
Dim conteo As Integer
Dim Especialidad As String
regEx.Pattern = "cop|col"
regEx.Global = False 'True matches all occurances, False matches the first occurance
regEx.IgnoreCase = True
i = 3
conteo = 1
RangoJulio = "L3:L283"
RangoAgosto = "L3:L315"
Julio = Excel.ActiveWorkbook.Sheets("Julio")
Rango = RangoJulio
Julio.Activate
For Each celda In Julio.Range(Rango)
If regEx.Test(celda.Value) Then
Set matches = regEx.Execute(celda.Value)
For Each Match In matches
j = 13 'column M
Especialidad = Julio.Cells(i, j).Value
If (Not DictContador.Exists(Especialidad)) Then
Call DictContador.Add(Especialidad, conteo)
GoTo ContinueLoop
End If
conteo = DictContador(Especialidad)
conteo = CInt(conteo) + 1
DictContador(Especialidad) = conteo
Next
End If
ContinueLoop:
i = i + 1
'Debug.Print DictContador(key1)
'Debug.Print DictContador(key2)
'etc
Next
'Finally, write the results in another sheet.
End Sub
It's like VBA saying "I'm going to dupe you if I got a chance"
Thanks
Seems like your main loop can be reduced to this:
For Each celda In Julio.Range(Rango)
If regEx.Test(celda.Value) Then
Especialidad = celda.EntireRow.Cells(13).Value
'make sure the key exists: set initial count=0
If (Not DictContador.Exists(Especialidad)) Then _
DictContador.Add Especialidad, 0
'increment the count
DictContador(Especialidad) = DictContador(Especialidad) +1
End If
Next
You're getting different results stepping through the code because there's a bug/feature with dictionaries that if you inspect items using the watch or immediate window the items will be created if they don't already exist.
To see this put a break point at the first line under the variable declarations, press F5 to run to the break point, then in the immediate window type set DictContador = new Dictionary so the dictionary is initialised empty and add a watch for DictContador("a"). You will see "a" added as an item in the locals window.
Collections offer an alternative method that don't have this issue, they also show values rather than keys which may be more useful for debugging. On the other hand an Exists method is lacking so you would either need to add on error resume next and test for errors instead or add a custom collection class with an exists method added. There are trade-offs with both approaches.