VBA comparing collection.count to combobox.value? - excel

Code works fine when on the active sheet, but when it attempts to use the same code from another sheet, loops infinitely.
I forced a break, and checked the values in the immediate window.
? colRAE.count
187433
? cmbNumberOfRuns
3
? colRAE.count < cmbNumberOfRuns
True
My while loop is based on that last part, and seems to be the reason why the loop continues.
Can anyone explain this?

My only guess is that you're doing a string comparison. If cmbNumberOfRuns is a ComboBox, you're getting the default property by calling it like that - .Text. If colRAE.Count is being interpreted as a String, you'll get the result shown in your question:
Sub TextCompare()
Dim cmbNumberOfRuns As String
Dim colRAE As String
cmbNumberOfRuns = "3"
colRAE = "187433"
Debug.Print colRAE < cmbNumberOfRuns
End Sub
I'd recommend explicitly calling the property of cmbNumberOfRuns (both for readability and to avoid repeats of this issue), and explicitly casting to a Long for numerical comparisons:
Debug.Print colRAE.Count < CLng(cmbNumberOfRuns.Text)

Related

EXCEL VBA Type mismatch with "Next" highlighted

I'm creating small project in Excel, and because I'm a VBA newbie I do encounter a lot of problems that I'm trying to resolve on my own. However i can't cope with this:
I created Sub that accepts two objects: FormName and ControlName.
What i want it to do, is to loop through every Control in specific UserForm and populate every ListBox it encounters, from another ListBox.
I created this funny string comparison, because I need to operate on objects in order to execute the line with AddItem. This comparison actually works well, no matter how ridiculous it is. However when I launch the program, I got
Type Mismatch error
and to my surprise "Next" is being highlighted. I have no idea how to fix this, nor what is wrong.
Public Sub deploy(ByRef FormName As Object, ByRef ControlName As Object)
Dim i As Integer
Dim O As msforms.ListBox
i = 0
For Each O In FormName.Controls
If Left(FormName.Name & O.Name, 16) = Left(FormName.Name & ControlName.Name, 16) Then
O.AddItem (FormName.PodglÄ…d.List(i))
i = i + 1
End If
Next
End Sub
I call this sub using:
Call deploy(UserForm1, UserForm1.ListBox3)
Above, I use Listbox3 because otherwise i got error saying that variable is not defined. However in my comparison I kinda override this.
If someone can explain in simple words, how to fix this type mismatch issue or how to write it in more elegant way

Using Application.Run to evaluate Worksheet function passed as string

I have essentially a simple syntax question concering Application.Run. I want to write a bit of code where I pass a UDF a string coantaining the name of a worksheet function, e.g. 'iserror' or some other UDF returning boolean. The function will then be exectued for each cell within the passed range and do something depending on result.
However, I have not been able to work out the proper Syntax. Error Messages Change along with my Trials, but non are particularly helpfull. e.g.:
?hrCull(Range("Data!A1:B10"),"Worksheetfunction.iserror", False)
(Error message in German, I'll try my best to translate, but it probably won't 100% match the English Version):
Runtime error 1004:
The macro 'Worksheetfunction.iserror' can not be exectued. The macro may not be available in this worksheet or macros have been deactivated.
Of course, macros have not been deactivated, but it isn't really a macro anyway. Also tried without the leading 'Worksheetfunction', same error message.
In my code the call Looks like this:
Public Function hrCull(r As Range, func As String, Optional invert As Boolean = False) As Range
Dim c As Range
Dim selector As Boolean
...
selector = Application.Run(func, c)
...
end function
I omitted code not relevant.
So what is the proper Syntax?
Misc:
- I'm Aware that I can not assert that the passed function returns a boolean.
- Excel 2016 on Windows 7
A solution using CallByName:
selector = CallByName(Application.WorksheetFunction, "IsError", VbMethod, c)
Lose the WorksheetFunction. prefix, Evaluate doesn't like it as Evaluate is for worksheet functions.
In your function, use:
selector = Application.Evaluate(func & "(" & c.Address & ")")
To test, use:
Debug.Print hrCull(Range("A1"), "ISERROR")
I think you'd be better off declaring your own Enum and adding the functions that you want into this. Then execute them using built in syntax instead of trying to evaluate a string
Public Enum xlSheetFunction
xlIsError
End Enum
Public Function hrCull(r As Range, func As xlSheetFunction, Optional Invert As Boolean = False) As Range
Dim selector As Boolean
Select Case func
Case xlIsError
selector = WorksheetFunction.IsError(r)
End Select
Debug.Print selector
Set hrCull = r
End Function
Public Sub test()
Debug.Print hrCull(Range("A1"), xlIsError)
End Sub

Evaluate statement (#DbLookUp) doesn't work with Lotusscript

The last week I asked how to solve an error in an evaluate statement (Error in Evaluate statement macro).
Once fix it, I have other error with the same evaluate statement, it doesn't give me any value.
I will describe what I have and what I try.
#DbLookup in Calculate Text
I have this code into in an calculate Text and it works fine.
suc := #Trim(#Left(LlcPoliza;2));
_lkp := _lkp := #DbLookup("":"NoCache";"C1256EAD:00478951";"People2"; "D"+suc; "FullName");
#If( #IsError( _lkp ) ; " " ; _lkp );
#Name([CN];_lkp)
LlcPoliza is a document field (doc.LlcPoliza) and in a document it has for example the value C2H2H2.
The formula give first the value C2 and then look up into People2 who is D+C2 and give me a person.
It works fine.
Evaluate Statement (#DbLookup) in a Class
I have a class DirectorSucursal.
Class DirectorSucursal
Private m_branch As String
'Constructor class
Public Sub New (branch)
Dim subString As String
subString = Left(branch, 2)
me.m_branch = subString
End Sub
'Deleter Class
Public Sub Delete
End Sub
'Sub show the code about Suc
Public Sub GetCodSuc
MsgBox m_branch
End Sub
'Function get the name director
Public Function getNameDirector As String
Dim varResult As Variant
varResult = Evaluate({#DbLookup("":"NoCache";"C1256EAD:00478951";"People2"; "D} & m_branch & {"; "FullName)"})
getNameDirector = CStr( varResult(0) )
End Function
End Class
Then, in a button I instantiate the new object DirectorSucursal with the parameter of the field doc.LlcPoliza(0) like this.
Sub Click(Source As Button)
Dim director As New DirectorSucursal(doc.LlcPoliza(0))
director.GetCodSuc
director.getNameDirector
end Sub
The field doc.LlcPoliza(0) has the value C2H2H2. GetCodSuc show the value C2, but the function getNameDirector doesn't work.
It shows the error:
Operation failed
Evaluate Statement (#DbLookup) in click button
I have tried the same but into a click sub.
Sub Click(Source As Button)
Dim subString As String
subString = Left(doc.LlcPoliza(0), 2)
Dim eval As String
eval = Evaluate({#DbLookup("":"NoCache";"C1256EAD:00478951";"People2"; "D} & subString & {"; "FullName)"})
Msgbox eval
End Sub
The field doc.LlcPoliza(0) has the value C2H2H2. But it doesn't work
It shows the error:
Operation failed
My question is: what am i doing wrong? Why the code works fine in a calculate text with #Formula but with Lotusscript not?
Thanks.
EDIT 1:
I have added and Error Goto, modified the class code, modified #dblookup in calculate text and I have this error:
Error in EVALUATE macro
Please read documentation and use help! evaluate always returns an ARRAY, as stated in the help:
Return value
variant
The result of the evaluation. A scalar result is returned.
To make your code return a STRING you need to change it like this:
Public Function getNameDirector As String
Dim varResult as Variant
varResult = Evaluate({#DbLookup("":"NoCache";"C1256EAD:00478951";"People2"; "D} & m_branch & {"; "FullName")})
getNameDirector = Cstr( varResult(0) )
End Function
The CStr is just there for the case where the #DBLookup returns an error or a number (both possible)
Just a few things in general:
NEVER write even one line of LotusScript- code without error handler. It will cause you trouble FOR SURE. If you had error handling in place, then it would have told you in which line the error occured...
NEVER use the result of #DBLookup without checking for #IsError... It will cause lot of troubles when the lookup fails.
IF you use #Iserror, then don't do the Lookup twice, assign the lookup to a variable and check that one for #Iserror, like this. Otherwise performance will go down in big forms:
Example:
_lkp := #DbLookup("":"NoCache";"C1256EAD:00478951";"People2"; "D"+suc; "FullName");
#If( #IsError( _lkp ) ; " " ; _lkp )
EDIT: As Knut correctly stated in his answer the real cause for the error was a typo in the formula ( Fullname)" instead of Fullname") that I fixed in my example as well.
1) My suggestion is to never (or at least very seldom) use Evaluate() in Lotusscript. You have proper Lotusscript functionality to do almost everything.
One of the major reasons is that the code is very hard to debug (which is what you are now experiencing).
2) Don't use extended notation when you work with fields. The best practice is to use the GetItemValue and ReplaceItemValue methods of the NotesDocument class for performance reasons as well as compatibility reasons.
3) In the examples with buttons you have a reference to doc, but it is never declared or initialized in the code. If you would use Option Declare at the top of your code you would catch these kinds of errors.
4) I also reccomend against using replica ID to reference databases, that makes it very hard to maintain in the future. Unless you have a very good and convincing reason, reference them by server and filename instead.
I would suggest you refactor your code to something like this:
'Function get the name director
Public Function getNameDirector() As String
Dim db as NotesDatabase
Dim view as NotesView
Dim doc as NotesDocument
Dim key as String
Dim fullname As String
Dim varResult As Variant
Set db = New NotesDatabase("Server/Domain","path/database.nsf")
If db Is Nothing Then
MsgBox "Unable to open 'path/database.nsf'"
Exit Function
End if
Set view = db.GetView("People2")
If view Is Nothing Then
MsgBox "Unable to access the view 'People2'"
Exit Function
End if
key = "D" & m_branch
Set doc = view.GetDocumentByKey(key)
If doc Is Nothing Then
MsgBox "Could not locate document '" & key & "'"
Exit Function
End if
fullname = doc.GetItemValue("FullName")(0)
End Function
Ando of course update the button actions in the same way.
Yes, it is a few lines longer, but it is much more readable and easier to maintain and debug. And you have error handling as well.
Change your last part in #DbLoookup code line to:
"FullName")})

Excel UDF not appearing in drop down menu

I wrote a User Defined Fucntion in Excel. It works great with no issues. I even wrote a description for it under the object properties menu.
The problem is, my UDF never shows up in the Excel drop down menu that appears when I start to type a function. I want the user to be able to see my UDF, named removeNumbers, when they go into a cell and start to type out a function.
I would also like them to be able to see the description which I wrote, just like the standard Excel functions.
And finally, is there a way that I can provide a description for each argument which my function takes as input?
Here is the actual code, although I don't think it will be necessary to answer my questions.
Function removeNumbers(sInput As String, sChoice As Boolean) As String
Dim sSpecialChars As String
Dim i As Long
If (sChoice = True) Then 'if true is selected, will remove all number including 0
sSpecialChars = "0123456789" 'This is your list of characters to be removed
For i = 1 To Len(sSpecialChars)
sInput = Replace$(sInput, Mid$(sSpecialChars, i, 1), "")
Next
End If
If (sChoice = False) Then 'if false is selected, will remove all numbers excluding zero
sSpecialChars = "123456789" 'This is your list of characters to be removed
For i = 1 To Len(sSpecialChars)
sInput = Replace$(sInput, Mid$(sSpecialChars, i, 1), "")
Next
End If
removeNumbers = sInput
End Function
To make the function appear in the drop-down you must place it in a standard module rather than the worksheet code area.
Another poster has already covered the need for the code to be in a standard module. With regards the argument descriptions, you should look at the MacroOptions code in this answer - although it only works in Excel 2010 or later.
For Excel 2007 and earlier, the only solution I have seen is in an article by JK Pieterse. This involves using the ExecuteExcel4Macro and looks a bit complicated.

Stop VBA Evaluate from calling target function twice

I am having trouble getting VBA's Evaluate() function to only execute once; it seems to always run twice. For instance, consider the trivial example below. If we run the RunEval() subroutine, it will call the EvalTest() function twice. This can be seen by the two different random numbers that get printed in the immediate window. The behavior would be the same if we were calling another subroutine with Evaluate instead of a function. Can someone explain how I can get Evaluate to execute the target function once instead of twice? Thank you.
Sub RunEval()
Evaluate "EvalTest()"
End Sub
Public Function EvalTest()
Debug.Print Rnd()
End Function
This bug only seems to happen with UDFs, not with built-in functions.
You can bypass it by adding an expression:
Sub RunEval()
ActiveSheet.Evaluate "0+EvalTest()"
End Sub
But there are also a number of other limitations with Evaluate, documented here
http://www.decisionmodels.com/calcsecretsh.htm
I don't know of a way to stop it, but you can at least recognize when it is happening most of the time. That could be useful if your computation is time consuming or has side effects that you don't want to have happen twice and you want to short circuit it.
(EDIT: Charles Williams actually has an answer to your specific quesion. My answer could still be useful when you don't know what data type you might be getting back, or when you expect to get something like an array or a range.)
If you use the Application.Caller property within a routine called as a result of a call to Application.Evaluate, you'll see that one of the calls appears to come from the upper left cell of of the actual range the Evaluate call is made from, and one from cell $A$1 of the sheet that range is on. If you call Application.Evaluate from the immediate window, like you would call your example Sub, one call appears to come from the upper left cell of the currently selected range and one from cell $A$1 of the current worksheet. I'm pretty sure it's the first call that's the $A$1 in both cases. (I'd test that if it matters.)
However, only one value will ever be returned from Application.Evaluate. I'm pretty sure it's the one from the second eval. (I'd test that too.)
Obviously, this won't work with calls made from the actual cell $A$1.
(As for me, I would love to know why the double evaluation happens. I would also love to know why the evaluator is exposed at all. Anyone?)
EDIT: I asked on StackOverflow here: Why is Excel's 'Evaluate' method a general expression evaluator?
I hope this helps, although it doesn't directly answer your question.
I did a quick search and found that others have reported similar behavior and other odd bugs with Application.Evaluate (see KB823604 and this). This is probably not high on Microsoft's list to fix since it has been seen at least since Excel 2002. That knowledge base article gives a workaround that may work in your case too - put the expression to evaluate in a worksheet and then get the value from that, like this:
Sub RunEval()
Dim d As Double
Range("A1").Formula = "=EvalTest()"
d = Range("A1").Value
Range("A1").Clear
Debug.Print d
End Sub
Public Function EvalTest() As Double
Dim d As Double
d = Rnd()
Debug.Print d
EvalTest = d + 1
End Function
I modified your example to also return the random value from the function. This prints the value a second time but with the one added so the second print comes from the first subroutine. You could write a support routine to do this for any expression.
I face the same problem, after investigation i found the function called twice because i have drop down list and the value used in a user defined function.
working around by the code bellow, put the code in ThisWorkbook
Private Sub Workbook_Open()
'set the calculation to manual to stop calculation when dropdownlist updeated and again calculate for the UDF
Application.Calculation = xlCalculationManual
End Sub
Private Sub Workbook_SheetChange(ByVal Sh As Object, _
ByVal Source As Range)
'calculte only when the sheet changed
Calculate
End Sub
It looks like Application.Evaluate evaluates always twice, while ActiveSheet.Evaluate evaluates once if it is an expression.
When the object is not specified Evaluate is equivalent to Application.Evaluate.
Typing [expression] is equivalent to Application.Evaluate("expression").
So the solution is to add ActiveSheet and to make that an expression by adding zero:
ActiveSheet.Evaluate("EvalTest+0")
After seeing there is no proper way to work around this problem, I solved it by the following:
Dim RunEval as boolean
Sub RunEval()
RunEval = True
Evaluate "EvalTest()"
End Sub
Public Function EvalTest()
if RunEval = true then
Debug.Print Rnd()
RunEval = False
end if
End Function
problem solved everyone.

Resources