« Until I put some new material back up here - what are your first thoughts of ND7? | Main| Tech Tip: Dialog buttons in Notes 6.x »

Better than the original: Launch First Attachment

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


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

Comments

1 - Paul - The only problem is that when you use the AutoLaunch: First Attachment feature the form itself never loads. Since it doesn't load, the events for the form don't fire, and you can't use kewl stuff like FindWindow() and ShowWindow(). So, I rolled my own.

Rock

2 - Kewl indeed.

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 -

3 - Hi Rock,
<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

4 - Nice one Rock! Thanks for sharing ...

5 - Thanks a lot

6 - Hi Rock,

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

7 - Hi Rock,

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

8 - Do you have any idea where in LOTUS R5 or the registry where you can point to the .exe for excel and word. I can launch all word documents but cannot launch any excel documents?

9 - TimL - LOL, no, although there are a bunch of those. My client isn't in banking or reinsurance, but they are downtown in Hamilton ;)&lt;br&gt;&lt;br&gt;&lt;b&gt;Rock&lt;/b&gt;

10 - Nice !!

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 ?

11 - Where do you stay when you're in Hamilton, Rock ? The Princess ? Or do you stay outside Hamilton and "commute" into work on a moped ?

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.

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

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