AX Miscellaneous

From Team Developer SqlWindows Wiki
Jump to: navigation, search

ActiveX/COM/Applets/JScript


Contents


Pointer2.png How to include an attachment with Outlook Automation Pointer.png

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:
Down.png Including Attachments in Outlook Automation.zip


Pointer2.png How to disable internal TD error messages Pointer.png

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 )


Pointer2.png Copy/Paste of ActiveX objects results in : ActiveX object creation failed Pointer.png

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)


ActiveXOnForm1.gif


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.


Pointer2.png How to generate a GUID Pointer.png

Creating a GUID programatically can be done in two ways.

First, you may use the CDK for this. See:

MediaWikiLink.png How to generate a GUID using CDK


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[16]
          ! 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[16]
          ! 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 )

Down.png CreateGUID_TD52.zip


Pointer2.png Use applets from Team Developer applications: Geogebra demo Pointer.png

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.

GeogebraDemo.png


Below, the link will give you the full description in a separate document. Read it for more details on applet integration in TD:
Down.png UseAppletsFromTD.doc


You can also watch a video of the Geogebra demo (running in TD 6.1):
Video.png TD_GeogebraApplet_Demo.wmv


And the demo application can be found here: (TD 2.1 and above)
Down.png TD_GeogebraApplet_Demo.zip


Pointer2.png Use JScript from Team Developer applications: Google Maps demo Pointer.png

JavaScript/JScript (JS) is an interpreted computer programming language.
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.


Web.png Google 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:


GoogleMapsSample.png


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:

<html>
 <head>
  <meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
  <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
  <script type="text/javascript">

   var map
   var myOptions
   var marker
   var infowindow

   function initialize()
   {
     var latlng = new google.maps.LatLng(52.032654, 4.707180);
     myOptions = {
       zoom: 13,
       center: latlng,
       mapTypeId: google.maps.MapTypeId.ROADMAP,
       navigationControl: false,
       mapTypeControl: false,
       scaleControl: false
     };
     map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
     infowindow = new google.maps.InfoWindow();
   }

  </script>
 </head>

 <body onload="initialize()">
  <style type="text/css">
   html,body {
   height:100%;
   width:100%;
   overflow:auto;
   }
  </style>
   <div id="map_canvas" style="float:left;width:100%; height:100%"></div>
   <div id="directionsPanel" style="float:right;visibility:hidden;width:50%;height:100%"></div>
   <input id="sVar1" type="hidden" value="">
   <input id="sVar2" type="hidden" value="">
   <input id="sVar3" type="hidden" value="">
 </body>
</html>

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.


   execScript(code, language)

(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:


   Call wuParentWindow.execScript( 'map.setZoom( "8" );', 'JavaScript', uReturnVar )


It is even possible to specify complete blocks of JScript code which is then executed, like:


Call wuParentWindow.execScript( "
		var myLatLng = new google.maps.LatLng(0, -180);
		var myOptions = 
		     {
		        zoom: 2,
		        center: myLatLng,
		        mapTypeId: google.maps.MapTypeId.TERRAIN
		      };
		
		map = new google.maps.Map(document.getElementById(\"map_canvas\"), myOptions);
		
		var flightPlanCoordinates = [
		        new google.maps.LatLng(37.772323, -122.214897),
		        new google.maps.LatLng(21.291982, -157.821856),
		        new google.maps.LatLng(-18.142599, 178.431),
		        new google.maps.LatLng(-27.46758, 153.027892)
		    ];
		var flightPath = new google.maps.Polyline({
		      path: flightPlanCoordinates,
		      strokeColor: \"#FF0000\",
		      strokeOpacity: 1.0,
		      strokeWeight: 2
		    });
		
		   flightPath.setMap(map);
		
		", "JavaScript", uRetVar )

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[0].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.
In JScript:


document.title = \"GEOCODE1_FINISHED\";


In TD:


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:
Down.png GoogleMaps JScript Sample.zip