How to include an attachment with Outlook Automation
Including Attachments within Outlook eMail automation is not immediatley obvious .
Once you have created the Mail Item , you need to create an 'Attachment' object using 'PropGetAttachments' to actualy create it, before you 'Add' Attachments objects to the Mail Item.
! ! Create Outlook object If NOT SalActiveXGetActiveObject( objOutlookApplication, 'Outlook.Application' ) Set bOk =objOutlookApplication2.Create() If bOk Set bOk = objOutlookApplication2.PropGetApplication( objOutlookApplication ) If bOk Set bOk = objOutlookApplication.ActiveExplorer( objOutlookExplorer ) If bOk Set bOk = objOutlookApplication.GetNamespace( 'MAPI', objOutlookNameSpace ) If bOk Set bOk = objOutlookNameSpace.GetDefaultFolder( Outlook_OlDefaultFolders_olFolderInbox, objOutlookMAPIFolder ) If bOk Set bCreated = TRUE ! Create Mail Item If bOk Set bOk = objOutlookApplication.CreateItem( Outlook_OlItemType_olMailItem, objOutlookMailItem ) ! Set Mail Item properties If bOk Set bOk = objOutlookMailItem.PropSetTo(sTo) If bOk Set bOk = objOutlookMailItem.PropSetSubject(sSubject) If bOk Set bOk = objOutlookMailItem.PropSetBody(sBody) ! 'Get' the Attachments object to create it ! Call objOutlookMailItem.PropGetAttachments( Attachments ) ! Set the Varients so they can be used in the Attachments.Add Call AttachmentFile.SetString( 'c:\\AttachmentFile.docx' ) Call AttachmentType.SetNumber( Outlook_OlAttachmentType_olByValue, VT_I1 ) Call AttachmentPos.SetNumber( 1, VT_I1 ) ! Add the Attachment object created using PropGetAttachments() earlier, to the Mail Item Call Attachments.Add( AttachmentFile, AttachmentType, AttachmentPos, AttachmentFile , Attachment )
Sample can be downloaded here:
How to disable internal TD error messages
You can disable the TD error messages when errors occur while calling ActiveX/COM functionality.
While having the internal messages off, you have to code custom error handling.
! Set internal error messages OFF Call SalActiveXAutoErrorMode( FALSE ) ! Do ActiveX/COM functionality here... ... ! Set internal error messages ON Call SalActiveXAutoErrorMode( TRUE )
Copy/Paste of ActiveX objects results in : ActiveX object creation failed
Sometimes, on specific ActiveX objects, it is impossible to copy/paste the object from one form to another.
The original object works, but a copy to a new form results in a "ActiveX object creation failed" error at runtime.
Another issue which could emerge is that in the IDE the events are not shown. Even copy/paste of implemented events
of the original ActiveX object to another one could lead to a compile error "Undefined Event Name".
This is due to a TD bug, caused by the fact that the pasted copy of the ActiveX object looses information which is needed
to correctly work in the IDE and at runtime.
But there is a workaround which could solve this annoying issue.
When a new ActiveX object is placed on a form, TD creates an invisible block of information within the object in the source.
Here an example. Below you see a form window on which an ActiveX object is placed (named axInternetExplorer)
When you open the source in notepad, and look for axInternetExplorer, you will find something like this:
.head 2 + Contents .head 3 + ActiveX: axInternetExplorer .data RESOURCE 5 0 1 4270659340 0000: 000A000009020000 0000000000000000 020000D0CF11E0A1 B11AE1F8000000FF 0020: 003E000300FEFF09 00F0060000000F01 000000F601000110 0000020000002601 0040: 00FEFFFFF8000000 F8FFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF 0060: FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF 0080: FF40FDFFFFFFFEFF FF66FEFFFEFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF 00A0: FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF 00C0: FFFFFFFFFFFFFFFF FFFFFFFF2052006F 006F740081004500 6E0074C072007900 00E0: 0000FFFFFFFFFFA0 16000500FFFF4F01 00000061F956880A 34D01100A96B00C0 0100: 4FD705A2F8000000 0FA068897600779B C90103000000F0C0 000000284A004545 0120: 5000085300724300 4F00C24F4C000000 FFFFFFFFFF821602 01FFFFFFFFFA0000 0140: FFFFFFFFF09C0000 00FFFFFFFFFFFFFF FFAFFFFFFFFA0000 FFFFFFFFFFFFFFFF 0160: FFFFFFFFFFAFFFFF FFFA0000FFFFFFFF FF460100020000F6 FEFFFFFFFFFFFFFF 0180: FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF 01A0: FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF 044C0000C51A0000 01C0: E06515000000FFFF 6F4C00FFFFFF0F08 0000006F4C00D001 1402000076C00046 01E0: F080000000FFFFFF FFF4010000FFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF 0200: FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFF .enddata .head 4 - Class Child Ref Key: 0
Just after the line ".head 3 + ActiveX: axInternetExplorer" you see a data block starting from .data up to .enddata.
In between there are a bunch of hex codes. This block holds information needed for the IDE and at runtime to reference the ActiveX object correctly.
What those codes mean and how to get the info from it is unknown to me.
Now, what happens when you copy the ActiveX object from one form to another. Just have a look with notepad at the location where
the newly copied object is placed:
.head 2 + Contents .head 3 + ActiveX: axInternetExplorer .head 4 - Class Child Ref Key: 0
As can be seen, the data block is not there, so it is lost in the copy/paste process.
This results in faulty IDE behaviour of the object (eg events are missing) or at runtime the object can not be created.
To solve this you need to manually copy/paste the missing data block from a working ActiveX object (same class) to the faulty object using notepad (or other text editor).
Copy the entire .data line up until .enddata line and place it right under the ActiveX:<<name>> line.
If done right, the newly copied ActiveX object will work like its original.
How to generate a GUID
Creating a GUID programatically can be done in two ways.
First, you may use the CDK for this. See:
Since TD is UNICODE-able, you may also call some of the OLE-APIs directly.
You have to import the following functions:
Library name: ole32.dll Function: CoCreateGuid Description: Creates a GUID, a unique 128-bit integer used for CLSIDs and interface identifiers. *Returns: S_OK - The GUID was successfully created. Export Ordinal: 0 Returns Number: DWORD Parameters structPointer Receive String: byte ! Pointer to the requested GUID on return. Function: StringFromGUID2 Description: Converts a globally unique identifier (GUID) into a string of printable characters. *Returns: 0 (zero) - Array at lpsz is too small to contain a string representation of a GUID. Non-zero value - The number of bytes (not characters) in the returned string, including the null terminator. Export Ordinal: 0 Returns Number: INT Parameters structPointer String: byte ! GUID to be converted. Receive String: LPWSTR ! Pointer to a caller-allocated string variable to contain the resulting string on return. Number: INT ! Number of characters available in the buffer indicated by lpsz.
After that, two simple calls give you a new GUID.
! Create a new GUID Call SalSetBufferLength( createdGUID, 16 ) ! a GUID has 16 bytes (= 128 bits) Call CoCreateGuid( createdGUID ) ! ! Transform to string Call SalSetBufferLength( createdGUIDString, 100 ) Call StringFromGUID2( createdGUID, createdGUIDString, 50 )
Use applets from Team Developer applications: Geogebra demo
The article describes how you can use (Java) applets which are running in a WebBrowser from Team Developer applications.
I needed to call the public methods of a 3rd party applet to be able to integrate it into the TD application.
As applets are running inside the browser, how do you actually use the methods of that API?
To show how to do this I have chosen an applet API which is widely used and is free: Geogebra !
It is software to visualize mathematics and physics and can be used for demonstration or educational purposes.
Below, the link will give you the full description in a separate document. Read it for more details on applet integration in TD:
You can also watch a video of the Geogebra demo (running in TD 6.1):
And the demo application can be found here: (TD 2.1 and above)
Use JScript from Team Developer applications: Google Maps demo
It was originally implemented as part of web browsers so that client-side scripts could interact with the user, control the browser, communicate asynchronously, and alter the document content that was displayed.
More recently, however, it has become common in both game development and the creation of desktop applications.
So, as this is used for client-based scripting in web-pages, is it possible to execute JScript functionality from TD?
Have a look at Google Maps as example. Using a web-browser, you can show and manipulate geographic maps.
Using JScript on a custom web page, Google Maps can be called to display specific information, calculate routes etc etc.
What this article will explain how you can integrate the Google Maps GUI with a TD Form Window and manipulate it using JScript, called from TD code.
First a screenshot of a TD integration example:
The GUI consists of two parts, the Google Maps 'map' showing the visuals.
Below some fields and buttons to manipulate the map.
Basically, the feature is that using TD code we call the Google Maps API using JScript.
What we need first is a HTML page which defines a Google Map.
This HTML page is loaded in the WebBrowser control which we place on a TD FormWindow.
An then we access the Google Maps API, using the scripting features the WebBrowser control offers.
Lets have a look on the HTML page:
As can be seen, the Google Maps API is initiated using:
map = new google.maps.Map( ... )
The object (variable) "map" is now initialized to the Google Map.
Then using JScript, we can set the zoom level of the map for instance:
map.setZoom( "8" );
The Map will zoom to level 8.
This is done on the webpage itself, but how to set the zoom level from TD?
We need to place a MS WebBrowser control on a TD FormWindow first.
You have to use the ActiveX Explorer and generate the classes for SHDocVw_WebBrowser and MSHTML_IHTMLDocument.
Then drop the SHDocVw_WebBrowser on a FormWindow.
When the application starts, we need to load the specific local HTML file, which initializes the Google Maps API.
When loaded, the document object and the ParentWindow object needs to be fetched.
! Open the HTML file in the webbrowser Call uVar.MakeOptional( ) If axWebbrowser.Navigate( "file:///" || wsRuntimeDir || psFile, uVar, uVar, uVar, uVar ) ! Get the HTML contents as document object ! If axWebbrowser.PropGetDocument( wuDocument ) ! Get the parent of the document (there the Exec script method is present) ! If wuDocument.PropGetparentWindow( wuParentWindow ) Set bOk = TRUE Else Call wuDocument.Detach( )
The WebBrowser control is able to execute JScript using the method execScript.
(As of InternetExplorer 11, the execScript method is replaced by the method eval( code ) )
This method is part of the class MSHTML_IHTMLWindow, which is fetched in the TD code above (as variable wuParentWindow)
So, having this we can execute a piece of JScript code within the WebBrowser document.
For instance, if we want to set the zoom level, we can do this in TD:
It is even possible to specify complete blocks of JScript code which is then executed, like:
The code above will draw a red polygon line from the USA to Australia in the map.
So, very advanced stuff can be done by coding the correct JScript sourcecode and pass it to the execScript method.
By concatenating values from TD code (from datafields, comboboxes, variables etc) into the JScript code we can call JScript using dynamic data.
But what about getting values back to TD? We can use a trick for that. When we define fields in the HTML code, we can assign values to them.
For instance, we can call this piece of JScript code which gets the latitude of a specific location and stores the value in the field "sVar1":
Call wuParentWindow.execScript( "document.getElementById('sVar1').value = results.geometry.location.lat();" .... )
The field can be queried by looking for the HTML document element by its name and then fetching the value from it, like this:
If wuDocument.getElementById( "sVar1", uElement ) Call uElement.getAttribute( "value", 2, uVariant ) Call uVariant.GetString( sValue )
If you are using Internet Explorer in a version greater than 7.0 and do not wish to use compatibility mode, you can fetch the value of an element by accessing its innerText property instead:
If wuDocument.getElementById( "sVar1", uElement ) Call uElement.PropGetinnerText( sValue )
How about a signal from JScript so that TD can react on it? For instance, when a method is called which takes time to complete or you want to react on a specific event, like clicking on the map.
Well, also a trick. We use the messages from the WebBrowser control to signal back to TD.
When we change the title of the document, TD gets the event SHDocVw_TitleChange:
ActiveX: axWebbrowser Message Actions On SHDocVw_TitleChange Parameters String: Text Actions Call OnTitleChange( Text )
So, when we change the title from JScript, and specify a predefined title, we can react on this in TD.
document.title = \"GEOCODE1_FINISHED\";
If Title = "GEOCODE1_FINISHED" ... ! Get values from the document we are interested in ...
I can imagine this info will raise questions. So the best way is to inspect a working sample and see how it all fits together.
You can download the Google Maps demo TD sources which works on ANSI and UNICODE TD versions here: