« Big Oliver Family - Pics with Santa, ver. 2005 | Main| Workarounds, kludges, hacks - some geek definitions... (UPDATED) »

Kewl Tip: Browse for Folder dialog in LotusScript

QuickImage   
Category
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 


How many times have you developed a robust Notes-based application, only to realize that what you really need to do at some point is to prompt the user for a folder/directory location - not a file, but a folder. You try using the NotesUIWorkspace,OpenFileDialog, you even try NotesUIWorkspace.Prompt - local browse option. But everything forces the user to choose a file. You wind up with some kludge where you are forced to ask the user to choose a file, and then you hack off the file name to get the directory name.

Well, I'm here to take you away from all that (at least all you Win32 users). How would you like to produce a nice folder dialog, like below?

FolderBrowse.jpg


Luckily I have my handy-dandy AllAPI.net API Guide to help me. It was a bit cryptic, and I needed to add a "Make new folder" button, so I did some more searching and found a few sites - this one seemed to be the most helpful.

Here is the code - I have commented it fairly well, and I will point out some highlights after the code itself.

Now I am not going to show all the declarations and such - you can download the LSS from here and look at it yourself.

The function code
Function getDirDialog(stitle As String) As String REM unless otherwise noted, the functoin calls below came from Declarations (C API) Dim iNull As Integer, lpIDList As Long, lResult As Long Dim sPath As String Dim hwd As Long Dim bi As BrowseInfo ' user type declared in declarations On Error Goto errHandler hwd = GetForegroundWindow() ' returns the handle to the currently selected program window REM Set the owner/parent window bi.hWndOwner = hwd REM lstrcat appends the two strings and returns the memory address; ' even though we're using one string, we use lstrcat to get a pointer ' to a memory address, instead of using the string directly ' NOTE: You can change the text here if you want If stitle = "" Then stitle = "Choose a folder for your attachment(s)" bi.lpszTitle = lstrcat(stitle, "") REM this next parameter is a bit flag; we're flipping bits with the constants REM this flag ensures a return only if the user selected a directory bi.ulFlags = BIF_RETURNONLYFSDIRS REM this flag lets us have a "Make New Folder" button, resize the dlg, etc. bi.ulFlags = bi.ulFlags Or BIF_NEWDIALOGSTYLE REM if you DON'T want a "Make New Folder" button, you can uncomment the next flag ' bi.ulFlags = bi.ulFlags Or BIF_NONEWFOLDERBUTTON REM the rest of this simply preps all of the parameters and loads the dialog itself lpIDList = SHBrowseForFolder(bi) If lpIDList Then sPath = String$(MAX_PATH, 0) SHGetPathFromIDList lpIDList, sPath 'Get the path from the IDList CoTaskMemFree lpIDList 'free the block of memory iNull = Instr(sPath, Chr(0)) If iNull Then sPath = Left$(sPath, iNull - 1) End If End If REM return the path getDirDialog = spath getOut: Exit Function errHandler: On Error Goto 0 Error Err, Error$ & "(" & Err & ") [in " & Lsi_info(2) & ", line: " & Erl & "]" Resume getOut End Function

There are a couple of main points I want to bring out in this code.

The GetForegroundWindow call is a Win32 C API call that returns a handle (long) to the program window that is in the foreground - in this case the Notes client. The handle is needed to pass to the SHBrowseForFolder function as a part of the BrowseInfo data type, so that the dialog has a "parent" window.

I wrote the getDirDialog function so that you can pass in your own title/instructions for the dialog. If you send an empty string in, then it will assign the default title of "Choose a folder for your attachment(s)".

Notice that when the stitle is passed into the lpszTitle parameter of the BrowseInfo data type it is passed via the lstrcat function. lstrcat is a C API function that takes two strings, munges them together, and then returns a memory pointer for where resulting string is stored. The lpszTitle parameter wants a memory pointer to the string, not the string itself, so this is the easiest way to get that.

The "ulflags" parameter in the BrowseInfo data type is a bit flag, so you use bitmasks to "flip" the bits you want. There are a ton of constants giving you a variety of parameters in the (Declarations); I am simply flipping the minimal bits to give you the dialog box pictured above.

The SHBrowseForFolder(bi) call returns a memory pointer to a complex data type. The If statement below the call parses the contents of that memory pointer using the SHGetPathFromIDList call, where it places the returned path in the sPath variable (which is a string variable set to MAX_PATH size). Then the memory is freed using the CoTaskMemFree call, and the null-terminated string in sPath is parsed to get the returned path in a LotusScript-digestable format.

Conclusion
I hope you find this call as useful as I have. Now you can create even more robust applications providing Win32 users the dialog boxes they have come to know in their OS, which helps make Notes more acceptable as a "standard" application.

Enjoy!

Rock
**Fun is a good thing but only when it spoils nothing better. - George Santayana

Comments

1 - Hot Dog! Yet another way to create a RBOD! I like it.

2 - @Manfred - that is excellent. You know, I kept looking at OpenFileDialog, and never once looked at SaveFileDialog - and I know they are basically the same thing (the windows common dialog) except for the title bar. I just got stopped by the word "Save" from even looking at it.

Excellent heads-up, Manfred. Thanks. Also, the best part of this solution is that it does work for the Mac client (well, I assume it does).

Thanks again!

Rock

3 - @Rock:
>>Excellent heads-up, Manfred.<<
Thank you very much.

@Jens:
Hallo Jens, hoffe Dir gehts gut! (Hi Jens, hope you are well!)

Thank you for pointing that out. Sure, you are right, there is a "New Folder button" in this dialog. I guess I'am getting old and need some eyeglasses soon.

Manfred

4 - @Ben - ok, I feel stoopid. Well, now people have two places to get it ;) BTW, does yours have the Make New Folder option? If not, then at least I contributed that to the effort

See, it is very hard to invent something new - but I think I've done that. More later...

Rock

5 - LOL, anything for you, Newbs - anything for you

Rock

6 - Hi Rock,

I have an application that searches the whole database then the result is put to a folder. But when the result gets bigger (1000+ documents) it starts to slowdown, what I mean is, the waiting time to display the documents from the folder took around 3-4 minutes. That kind of long waiting erritates my users who are lawyers. Is there a way to make it faster. Im trying to learn to use the C API, Im thinking, maybe its the answer to my problem. I also noticed from the built-in search of Notes after it finished search it automatically displays the documents to view from were I'am searching, then, when I click the clear result button it removes the result documents then back to the original view.

Here's my code:

Dim db As notesdatabase
Dim docSearch As New notesdocument( s.currentdatabase )
Dim coll As notesdocumentcollection
Dim dateTime As New notesdatetime( Now )

On Error Goto errorHandling

Set db = s.currentdatabase
Call dateTime.AdjustDay(-10000)

' Display the search dialog box
If Not uiwork.dialogbox("DialogSearch", True, True, False, False, False, False, msgTitleSearch$, docSearch ) Then Exit Sub

' create search string
searchString$ = GetSearchCriteria( docSearch )

strMaxDocs = "100" ' max doc's returned from search

' Search
If searchString$ <> "" Then Set coll = db.Search( searchString$, dateTime, 0 )

' countFound
If coll Is Nothing Then countFound% = 0 Else countFound% = coll.count

' Exit if nothing found
If countFound% = 0 Then
'Print searchString$ ' debug
Msgbox msgNoDocumentsFound$, MB_ICONINFO, msgTitleMain$
Exit Sub
End If

Call coll.putallinfolder("Search")
uiwork.viewrefresh ' in case search initiated from folder

If IDOK = Msgbox ( msgFoundDocs$ & Cstr(countFound%) & " documents" & Chr(10) , MB_ICONINFORMATION + MB_OK, db.Title ) Then
End If

Exit Sub

errorHandling:
Print "Search.SearchFullTextAndFields reports:"
Print Cstr(Err) & " " & Error
Resume Next

P.S.

I hope to hear from you soon. Thank you in advance.

7 - Hi Manfred

Thanks for asking, yes I am well. Unfortunately, I did not get a session at LS06, but I could arrange to go nevertheless. I could bring you some glasses back home ...

8 - I know this is a stale discussion, but, I found a scriptlibrary called APICalls in the bookmark.nsf

It has a function called GetDirDlg() that does the directory chooser.

Rock, I think there is an issue with the lstrcat function in your implementation, my titles do not show up correctly. In the Bookmark version they declare lpszTitle as string and that seems to work.

The constant BIF_NEWDIALOGSTYLE is missing from the bookmark example, but, it works fine when added.

9 - @ben,

I gotta give it to Rock here, both the implementation of the new folder button & the exposure/documenting of the other constants.

The version of directory browser that I have in my library was originally posted 6/2000 from here http://www-10.lotus.com/ldd/46dom.nsf/0/25136f17661d91d48525690700215e72?OpenDocument

I've upgraded my library code with rockys lss

10 - Rock, also give:

stringArray = notesUIWorkspace.SaveFileDialog( directoriesOnly , [title$] , [filters$] , [initialDirectory$] , [initialFile$] )

a try. This method has the boolean parameter "directoriesOnly" (no files are displayed) and it returns the selected folder - that's what you want.

Sample:
Dim ws As New NotesUIWorkspace
Dim FolderChoice As Variant
FolderChoice = ws.SaveFileDialog( True )
Msgbox "Selected folder: " + FolderChoice(0)

OK, there is no "Make new folder" button and in this dialog and you have to open (instead of only select) the folder you desire - but apart from these limitations, it works.

Manfred

11 - Manfred: There is a New Folder Button on this dialog. Rocky is invoking the old BrowseFolder Dialog, which needs the button the way he did it. The Dialog invoked by SaveFileDialog is the new one, which sports the Folder with an asterisk above the folderlist, which is the key to make a new folder.


Meet Rocky

Rock - February 2010
Rocky Oliver
If you see me at a conference, please stop me and say hi!

Calendar

Search

Categories

Proudly Employed By

Wofkflow Studios

Thawte Notary

Thawte Web of Trust Notary

LOTUS GEEK gear

Social Networking


Add to Technorati Favorites

View Rocky Oliver's profile on LinkedIn

Rocky  Oliver

LotusGeek Blog Roll

Why display a blog roll when Planet Lotus does it so much better?

Dilbert

Buy my book!

Blog Buttons

Atheist - Unitarian - Humanist

Poker Players Alliance