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

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")})

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

Class constructor confusion - wrong number of arguments or invalid property assignment

I'm having a class module with some data:
Private sharedFolders() As String
Public Property Let SetSharedFolders(val As String)
Dim i As Integer
sharedFolders = Array("folder one", "folder two")
i = UBound(sharedFolders)
i = UBound(sharedFolders)
ReDim Preserve sharedFolders(i)
sharedFolders(i) = CStr(val)
End Property
Property Get GetSharedFolders()
GetSharedFolders = sharedFolders()
End Property
And I want to add something to this property from other module like this:
Sub PrepareData()
Dim e
Dim s
Dim a(2) As String
Set e = New Entry
a(0) = "add one"
a(1) = "add two"
For Each s In a
e.SetSharedFolders (s) 'Here comes exception
Next
For Each s In e.GetSharedFolders
Debug.Print s
Next
End Sub
But I receive an "wrong number of arguments or invalid property assignment vba" exception... Can anyone assist?
Addendum
Thanks to #AJD and #Freeflow to pointing out a mistake and idea to make it easier. Decided to make as like below.
Class Module:
Private sharedFolders As New Collection
Public Property Let SetSharedFolders(val As String)
If sharedFolders.Count = 0 Then ' if empty fill with some preset data and add new item
sharedFolders.Add "folder 1"
sharedFolders.Add "folder 2"
sharedFolders.Add CStr(val)
Else
sharedFolders.Add CStr(val)
End If
End Property
Property Get GetSharedFolders() As Collection
Set GetSharedFolders = sharedFolders
End Property
and regular module:
Sub AddData()
Dim e As New Entry ' creating an instance of a class
Dim s As Variant ' variable to loop through collection
Dim a(1) As String 'some array with data to insert
a(0) = "add one"
a(1) = "add two"
For Each s In a
e.SetSharedFolders = s
Next
For Each s In e.GetSharedFolders
Debug.Print s
Next
End Sub
Initially I thought the problem lies in this code:
i = UBound(sharedFolders)
i = UBound(sharedFolders)
ReDim Preserve sharedFolders(i)
sharedFolders(i) = CStr(val)
i is set twice to the same value, and then the sharedFolders is reDimmed to the same value it was before! Also, there is some trickery happening with the use of ix within a 0-based array.
But the problem is most likely how you have declared your variables.
For Each s In a
e.SetSharedFolders (s) 'Here comes exception
Next
s is a Variant, and a is a Variant. At this point VBA is trying to guess how to handle a For Each loop with two Variants. And then the improper call is made. The correct syntax is:
e.SetSharedFolders s '<-- no parenthesis
There are plenty of posts on StackOverflow explaining how to call routines and what the impact of the evaluating parenthesis are!
However, at this point we are only assuming it is passing in a single element of the array - it could be passing the full array itself (albeit unlikely).
And the third factor -
Public Property Let SetSharedFolders(val As String)
The parameter val is being passed ByRef and should be passed ByVal. This also has unintended side effects as I found out (Type mismatch trying to set data in an object in a collection).
Public Property Let SetSharedFolders(ByVal val As String)
All in all you have the perfect storm of ambiguity driving to an unknown result.
The answer here is to strongly type your variables. This removes about two layers of ambiguity and areas where errors can happen. In addition, this will slightly improve code execution.
Another aspect is to understand when you should pass something ByVal and when to use the default (preferably explicitly) ByRef.
And a final gratuitous hint: Use a Collection instead of an Array. Your code you have implies a Collection will be more efficient and easier to manage.
Addendum
(thanks to #FreeFlow):
If the OP changes the definition of sharedfolders to Variant rather than String() then the array statement will work as expected.
The line e.SetSharedFolders (s) will work fine if it is changed to e.SetSharedFolders = s because the method SetSharedFolders is a Let Property not a Sub. There are other errors but these two changes will make the code run.

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

Unable to create simple VBA function in Excel Macro and call it

I'm trying to create a macro that can collect data from an excel spreadsheet in the local active workbook and then create header file which I would later incorporate into my project. But for the life of me I must be missing something so DUMB that I can't create a working function that returns a string (which would construct a C++ structure) to the calling function. I've simplified the example code to is absolute bare minimum to isolate the problem but I still cannot figure out what I'm doing wrong. I'm not an expert at VBA but I know how to create code and I can't narrow down what VBA is unhappy about. I keep getting "compile error, syntax error." Please copy the following code into your module and see if compiles properly for you. If you know where I went wrong please let me know. Much Appreciated!!!
Sub CREATE_FACTORY_SETTING_HEADER()
Dim FS, TSsource
Set FS = CreateObject("Scripting.FileSystemObject")
Dim TSout
Set TSout = FS.Createtextfile("HeaderFile.h", True)
Dim fileHeading As String
fileHeading = "File Heading for Header file"
Dim fileBody As String
fileBody = "Some initial file body lines"
fileBody = fileBody & createStructBody
TSout.Write fileHeading & fileBody
TSout.Close
End Sub
Public Function createStructBody() As String
Dim structBody As String
structBody = "Hey I'm a struct body, but I can't be returned for some reason"
Return structBody
End Function
Both VBA and VBScript use 'assignment to function' (instead of 'return' or 'result of last statement') to return results from functions. So
Public Function createStructBody() As String
createStructBody = "Hey I'm a string and can be returned."
End Function

Excel VBA - Use an existing string in called sub

I'm pretty new to this so apologies in advance
I'm half way through a userform in Excel and I'm trying to cut some fat off my code by using Call - I have 12 buttons that all do the same thing, the only difference is that each buttons sub is dependant on the buttons caption. My problem is that I can't figure out a way to use a String I've already declared in the Buttons Sub, then use it in the called Sub. I know you can do it, but my googling skills have failed me :(
Please could someone show me how to do this? Hope that all makes sense...
Here is a very small snippet of my code, but you get the jist:
Public Sub CommandButton4_Click()
Dim Name As String
Name = CommandButton4.Caption
Call Sort1
End Sub`
And the other one (Also tried this as function for the sake of trial and error)
Public Sub Sort1(Name As String)
Label11.Caption = Name
Sheets(Name).Select
End Sub
What you're referring to is passing an argument to another subroutine or function. Let's say you want to use a function a lot of times to get the first letter of a string. A sample of this is:
Function LeftOne(StrSample As String) As String
LeftOne = Left(StrSample, 1)
End Function
The above function can be used inside another function or subroutine provided you meet its requirement: StrSample. By declaring StrSample As String in the arguments field of the function, you are basically requiring that any calls to this should require a string to be passed to it. Anything else would throw an error.
The full line LeftOne(StrSample As String) As String can be read as: "I am function LeftOne. Pass me a string and I'll return to you a string after doing something with it." Note that the name StrSample is an arbitrary name.
Anyway, calling the above is as simple as:
Sub MsgInABox()
Dim StrToFeed As String
StrToFeed = "BK201"
MsgBox LeftOne(StrToFeed) 'Returns B.
End Sub
In your example, if you want to pass Name to Sort1, your attempt is absolutely correct.
Let us know if this helps.
You hat to give your sort1 procedure the parameter name.
call sort1(name)
or
call sort1(CommandButton4.Caption)

Resources