Better than the original: Launch First Attachment
Category Technical
Bookmark :
First, let me thank those of you who checked in on me during my "hiatus" from this blog. I am fine, and hope to get back to regular blogging again. So, today is the beginning of that effort, and what better way to get into it than to do a technical entry.
I was in Bermuda last week at a client site. They have an application that relies on the "Launch First Attachment" feature found in the Form design element. The problem is that certain programs, such as Excel and Word, launch but don't maintain focus while others, such as Powerpoint, do launch with focus. This problem has been around for quite some time (as noted in Technote 1091159), with really no plans to fix. This is very confusing to users, so my client asked me to take a look at it and see if I could do better - and I believe I have, with this little bit of code. First, some background.
Workflow
The application in question is populated programmatically with attachment documents and some metadata that is used to display in the views. The users double-click the document in the view, and expect the attachment to automatically launch (and not the document itself) - the "classic" way Launch First Attachment works. So, I needed to mimic this functionality.
Now let's take a look deeper into the design...
Design Overview
Here are the steps I envisioned for my code:
1. Code in the QueryOpen event of the form would intercept the opening and hand off the NotesDocument object to a function that does the "real" work. Since the attachment documents already exist and the form itself cannot be composed, I was safe to use the QueryOpen event (remember, the Source.Document property is not populated in a newly composed document, because the back-end document doesn't exist yet in the QueryOpen of a new doc - it doesn't exist until the form fully loads, in the PostOpen event).
2. The attachment would be detached from the indicated rich text item, with the appropriate safeguards (i.e. not overwriting an existing doc, etc.)
3. The associated "parent" program would be determined for the attachment
4. The attachment would be launched in the parent program with focus
Of course the proper errors would be thrown if there was a problem.
Design Details
The QueryOpen event has the following code in it:
Sub Queryopen(Source As Notesuidocument, Mode As Integer, Isnewdoc As Variant, Continue As Variant)
Dim retflg As Variant
On Error Goto errHandler
Continue = False
retflg = detachFileOpen(Source.Document, "AttachBody")
getOut:
Exit Sub
errHandler:
Msgbox Error$ & " (" & Err & ") [in queryOpen event]",48,"Error"
Resume getOut
End Sub
The actual code that makes all this work is in the LGLaunchFirstAttach.LSS library, which can be downloaded here. Here's a brief overview of that code.
The detachFileOpen function looks like this:
Function detachFileOpen(doc As NotesDocument, rtname As String) As Variant
On Error Goto errHandler
detachFileOpen = True
Dim ws As New NotesUIWorkspace
Dim rtitem As NotesRichTextItem
Dim filepath As String, filename As String
Dim retflg As Boolean, res As Long, res2 As Integer
Dim exepath As String, shellpath As String
REM this holds the executable path
exepath = String(MAX_FILENAME_LEN, 32)
REM detach the file
retflg = detachFile(doc, rtname, filename, filepath)
REM use the C API call to find the associated executable
res = FindExecutable(filepath, "", exepath)
REM shorten the executable, removing the extra spaces
exepath = Left$(exepath, Instr(exepath, Chr$(0)) - 1)
REM prep the string to pass to the shell command
If Fulltrim(exepath) = "" Then Error 1001, "Unable to locate associated program..."
REM this computes a path to launch the executable formatted as
REM PATH/TO/EXECUTABLE "PATH/TO/FILE"
shellpath = exepath & | "| & filepath & |"|
REM open the file with the associated executable
REM the "1" param tells it to launch with focus
res2 = Shell(shellpath, 1)
getOut:
Exit Function
errHandler:
Select Case Err
Case 1001
Msgbox Error$, 48, "Error"
Case Else
On Error Goto 0
Error Err, "(" & Err & ") " & Error$ & " [in " & Lsi_info(2) & "]"
End Select
Resume getOut
End Function
The first thing the detachFileOpen function does is detach the attachment using the detachFile function. This funciton is also a part of the LGLaunchFirstAttachment.FE script library. I am not going to go into the code of this function, but here is an explanation of it from the docs in the script library:
Function detachFile(doc As NotesDocument, rtname As String, fname As String, fpath As String) As Variant
Used to detach a file. Returns True if successful, False if not.
doc = the document containing the attachment
rtname = the name of the Rich Text item containing the file
fname = the name of the file to detach. optional. If an empty string (""),
it grabs the first attachment.
fpath = the path where the file is to be detached. optional. If an empty string (""),
it detaches the file to a "LaunchAttachTEMP" subdirectory under the user's Windows temp dir
After detaching the file the detachFileOpen function uses a Win32 C API call that is declared in the (DECLARATIONS) area of the script library. The API call is FindExecutableA, and our declaration alias is FindExecutable. We give this function the path to the file, and it returns the path to the associated program executable on the exepath parameter. If the API call is unable to find an attachment, we tell the user, just like the real "Launch First Attachment" feature does. If we do find a path to an associated executable we trim that up to remove the excess spaces and the null terminator, and then we append on the path to the file in quotes. We use that resulting string in the Shell function, with a parameter of 1 - this tells Shell to launch the application and give it focus.
This function seems to be as fast as the real functionality, and is able to launch all files that the real one can. My guess is that the real feature uses the same Win32 API call that we use to determine what program to use.
Caveats
There are really only two: First, if you use the QueryOpen technique, and you allow users to compose this form, then you need to add a check for IsNewDoc and not run the code on new documents. Second, this code is Windows only. It won't work on the Mac client.
Conclusion
This has proven to be successful so far, and provides the same functionality without the problems of the real function. It also has the side benefit of demonstrating error handling and simple C API calls.
Enjoy! Comments welcome.
Rock
**If you live to be a hundred, I want to live to be a hundred minus one day, so I never have to live without you. -- Winnie the Pooh







Blog Roll









Comments
Rock
Posted by Rock At 02:35:36 PM On 05/26/2004 | - Website - |
Curious, did you consider using the FindWindow() and ShowWindow() Win32 APIs to reset focus on the Excel/Word window instead of having to manually detach & launch? You would have this fire in PostOpen probably, or if that's too soon, in say a 2-second delayed NotesTimer. That approach could shorten the code a bit.
Cheers,
- Paul -
Posted by Paul Ryan At 01:35:58 PM On 05/26/2004 | - Website - |
<br>
<br>Thanks for this code, it really help me with my application that has an embedded word on a richtext field. I change a little from your code. I put a loop on it were it launches all attachments (xls, jpg, doc, pdf). But recently while I was testing it an error occur "File not found". When I debug it I noticed that the value of the exepath after the call of C API is this "C:\Program Files\Microsoft Office\Office10\WINWORD.E".
<br>
<br>My attachment are pdf, 2 xls and a doc file.
<br>
<br>I have been researching from the Forums in developerworks but I can't find an explaination or solution to this error that I have encountered. That is why I decided to ask for your help.
<br>
<br>Thanks in advance.
<br>
<br>God Bless
Posted by Mon Imperial At 08:25:45 AM On 08/30/2006 | - Website - |
Posted by Dan At 02:36:23 PM On 05/25/2004 | - Website - |
Posted by At 09:10:13 AM On 05/10/2005 | - Website - |
Thanks for this code, it really help me with my application that has an embedded word on a richtext field. I change a little from your code. I put a loop on it were it launches all attachments (xls, jpg, doc, pdf). But recently while I was testing it an error occur "File not found". When I debug it I noticed that the value of the exepath after the call of C API is this "C:\Program Files\Microsoft Office\Office10\WINWORD.E".
My attachment are pdf, 2 xls and a doc file.
I have been researching from the Forums in developerworks but I can't find an explaination or solution to this error that I have encountered. That is why I decided to ask for your help.
Thanks in advance.
God Bless
Posted by Mon Imperial At 08:24:59 AM On 08/30/2006 | - Website - |
About my problem I encountered when launching all the attachments from a notes document. I finally solve it.
Here's my code:
Function detachFileOpen2(doc As NotesDocument, rtname As String) As Variant
On Error Goto errHandler
detachFileOpen2 = True
Dim ws As New NotesUIWorkspace
Dim rtitem As NotesRichTextItem
Dim filepath As String, filename As String
Dim retflg As Boolean, res As Long, res2 As Integer
Dim exepath As String, shellpath As String
'08292006
If doc.HasEmbedded = False Then Error 1000, "No Attachments found."
Set rtitem = doc.GetFirstItem(rtname)
If rtitem Is Nothing Then Error 1000, "Unable to find RT item: " & rtname
Forall o In rtitem.EmbeddedObjects
If ( o.Type = EMBED_ATTACHMENT ) Then
REM detach the file
retflg = detachFile2(doc, o, filename, filepath)
REM if there was an error with the detachFile function, it would throw to the error block below
REM determine if the file is an EXE - if it is, we just want to launch it
If Lcase(Right(Fulltrim(filepath), 3)) = "exe" Then
res2 = Shell(filepath, 1)
Exit Function
End If
REM this holds the executable path
exepath = String(MAX_FILENAME_LEN, 32)
REM use the C API call to find the associated executable
res = FindExecutable(filepath, "", exepath)
If res <= 32 Then Error 1000, "Problem with FindExecutable call."
REM shorten the executable, removing the extra spaces
exepath = Fulltrim(Left$(exepath, Instr(exepath, Chr$(0)) - 1))
REM check for errors
If Ucase$(Strrightback(exepath, ".")) = "DLL" Then Error 1001, "Unable to locate associated program"
If exepath = "" Then Error 1001, "Unable to locate associated program"
shellpath = exepath & | "| & filepath & |"|
REM open the file with the associated executable
REM the "1" param tells it to launch with focus
res2 = Shell(shellpath, 1)
filepath = ""
Else
Error 1000, "Unable to locate file."
End If '08292006
End Forall '08292006
getOut:
Exit Function
errHandler:
detachFileOpen2 = False
Select Case Err
Case 1001
Msgbox Error$, 48, "Error"
Case Else
On Error Goto 0
Error Err, Error$ & " (" & Err & ") [in " & Lsi_info(2) & "]"
End Select
Resume getOut
End Function
Function detachFile2(doc As NotesDocument, obj As Variant, fname As String, fpath As String) As Boolean
On Error Goto errHandler
detachFile2 = True
Dim rtitem As NotesRichTextItem
Dim tmpdir As String
Dim retflg As String
On Error Goto errHandler
If fpath = "" Then
tmpdir = Environ("TEMP") & "\SapphireTEMP"
retflg = Dir(tmpdir, 16)
If retflg = "" Then Mkdir tmpdir
tmpdir = tmpdir & "\"
fname = obj.Source
fpath = fpath & fname
retflg = Dir$(fpath, 0)
If retflg <> "" Then
fpath = tmpdir & "DUP" & Minute(Now) & Second(Now) & "-" & fname
End If
End If
Call obj.ExtractFile(fpath)
getOut:
Exit Function
errHandler:
detachFile2 = False
On Error Goto 0
Error Err, Error$ & " [in " & Lsi_info(2) & "]"
Resume getOut
End Function
Thanks a lot.
God Bless
Posted by Mon Imperial At 09:31:55 PM On 08/30/2006 | - Website - |
Posted by James Hinks At 04:50:11 PM On 03/01/2005 | - Website - |
Posted by Rock At 09:03:05 AM On 05/26/2004 | - Website - |
I used to live in Bermuda years ago. Your client wouldn't happen to me a large, white bank over-looking Hamilton harbour, would it ?
Posted by TimL At 05:51:35 AM On 05/26/2004 | - Website - |
If you've never tried it, the next time you go, do yourself a favour and take a trip on the ferry. Even if you don't get off, and all you do is go around the sound, it's a lovely way of seeing the island.
Posted by TimL At 08:18:05 AM On 05/27/2004 | - Website - |