Sunday, July 19, 2009

AS3.0 and VisualBasic communication using ExternalInterface

This example deals with the communication between flash and Visual Studio 6. In this example a flash9 swf is placed on a VB form in shockwave wrapper. The requirement is reading any directory of windows OS with the help of Flash as frontend and VB as Backend.

To start of lets create one flash file. The file has to be simple just carrying one select directory button and a movieclip with textbox inside it, which will display the directory name. You can also create dynamic movie clips to deal all these but here i have placed them in library.

This is the link to the fla file if you don't want to create it. You can download it from below link.


I am using a document class here named as "main.as". I am using external interface to communicate to the VB which reads
the directory and sends us the values. Lets check the document class below.


package {
import flash.display.Sprite;
import flash.display.MovieClip;
import flash.events.MouseEvent;
import flash.external.ExternalInterface;
import flash.text.TextField;
import flash.net.navigateToURL;
import flash.net.URLRequest;
import flash.net.URLLoader;
import flash.display.Loader;

public class main extends Sprite
{
private var _directoryPath:String;
private var _directoryList:XML;
private var _sampleXML:XML;
private var _parserData:directoryParser;
private var _linkObjects:Array = new Array();
public function main()
{
_directoryPath = "C:\\Documents and Settings\\nitins\\Desktop\\Reading_Directory_Content\\data"
selectFolder.addEventListener(MouseEvent.MOUSE_DOWN, selectDirectoryInvoked);

ExternalInterface.addCallback("sendToActionScript",callFromVB);
}
private function callFromVB(_myParam)
{
_directoryList = XML(_myParam);
_parserData = new directoryParser(_directoryList);
generateTree(_parserData.returnXML());
}

public function selectDirectoryInvoked(event:MouseEvent)
{
ExternalInterface.call("selectFolder",_directoryPath);
}

public function generateTree(givenList:XML)
{
var loopLen:Number = givenList.filename.length();
var strtY:Number = 50;
for(var i=0;i<loopLen;i++)
{
var tempHold = givenList.filename[i].split(".")
var req:String = tempHold(tempHold.length-1)+".jpg"
var iconLoader:Loader = new Loader();
iconLoader.load(new URLRequest(req));
_linkObjects[i] = new ListBox();
_linkObjects[i].txt.text = givenList.filename[i];
_linkObjects[i].openFileName = givenList.filename[i];
_linkObjects[i].addEventListener(MouseEvent.MOUSE_DOWN, clikedLink);
_linkObjects[i].y = strtY
addChild(_linkObjects[i]);
strtY += _linkObjects[i].height+10;
}
}

public function clikedLink(e:MouseEvent):void
{
var _passPath:String = _directoryPath+"\\"+e.currentTarget.openFileName
ExternalInterface.call("openFile",_passPath);
}
}
}

As you can notice above - the method "selectDirectoryInvoked" is the one which gets called when the select directory button is clicked from the interface. Basically it just makes a call to VB function "selectFolder" and passes a parameter as the path of the directory. One more function you can notice above is the "callFromVB" it is the method that is registered to External Interface in the constructor of the "main" class.

/*------------------------------------------------------------*/
private function callFromVB(_myParam)
{
_directoryList = XML(_myParam);
_parserData = new directoryParser(_directoryList);
generateTree(_parserData.returnXML());
}
/*-------------------------------------------------------------*/

Above method is reading "_myParam" which is a dynamically generated XML from VB App.
Few very important things which we must keep in mind doing this communication is that the entire communication is XML oriented. From making the External interface call to reading it, all this happens in a perticular XML node format which is something like this :


<invoke name="functionName" returntype="xml">
<arguments>
... (individual argument values)
</arguments>
</invoke>
Communication between ActionScript and an application hosting the Shockwave  Flash ActiveX 
control uses a specific XML format to encode function calls and  values. There are two parts to the 
XML format used by the external API. One format is used to represent function  calls. Another 
format is used to represent individual values; this format is  used for parameters in functions 
as well as function return values. The XML  format for function calls is used for calls to and 
from ActionScript. For a  function call from ActionScript, Flash Player passes the XML to the container;  
for a call from the container, Flash Player expects the container application to  pass it an XML string 
in this format. - (From Adobe Docs)

This is the reason i have casted the parameter from VB to XML. One more thing to notice 
in the above method is 
_parserData = new directoryParser(_directoryList);
Here i also have created one class which parses the VB returned XML to flash. Lets take a look
at this class.
directoryParser.as
package {
import flash.display.Sprite;
public class directoryParser
{
private var _returnData:XML;
private var _maxLen:Number;
private var _typeArray:Array;
public function directoryParser(dataToParse:XML)
{
_maxLen = dataToParse.filename.length();
_typeArray = new Array();
var temp:Array;
for(var i=0;i<_maxLen;i++)
{
temp = dataToParse.filename[i].split(".")
dataToParse.filename[i].@type = temp[1];
_typeArray.pushUnique(temp[1]);
}
_typeArray.sort();

generateSortedXML(dataToParse);
}
private function generateSortedXML(inputXML:XML)
{
var myTempXML:XML = <rootNode></rootNode>;
for(var i=0;i<_typeArray.length;i++)
{
for(var k=0;k<inputXML.filename.length();k++)
{
if(inputXML.filename[k].@type == _typeArray[i])
{
myTempXML.appendChild(inputXML.filename[k].toXMLString());
}
}
}
_returnData = myTempXML
}
public function returnXML():XML
{
return _returnData;
}

Array.prototype.pushUnique = function( a )
{
var l:Number = this.length;
var exists:Boolean = false;
for( var i=0; i<l; i++ ) if( this[i] == a ) exists = true;
if( !exists ) this.push( a );
};
}
}

This class is basically sorting the list given from VB to file types and showing the display. The public method within this class "returnXML()" is used to fetch the sorted XML which our document class "main" processes. It generated a list of files using "generateTree()" method.
You can use the same method to do any application handling using ExternalInteface.

Link to this class below :


Now lets take a look @ the VB interface and its code, what things we should keep in mind. Look @ below image.

In the above image the two red boxes are important, mySWF is the name of the shockwave object. AllowScriptAccess you need to make it "always" if you want your application to work.
Lets take a look @ the VB Code and its methods.


Private Declare Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA" _

(ByVal hwnd As Long, ByVal lpOperation As String, _

ByVal lpFile As String, ByVal lpParameters As String, _

ByVal lpDirectory As String, ByVal nShowCmd As Long) As Long

Dim directoryPath As String

Private Sub mySWF_FlashCall(ByVal request As String)

Dim objXML As MSXML2.DOMDocument

Dim strXML As String

Set objXML = New MSXML2.DOMDocument

strXML = request

If Not objXML.loadXML(strXML) Then

Err.Raise objXML.parseError.errorCode, , objXML.parseError.reason

End If

Dim objElem As MSXML2.IXMLDOMElement

Set objElem = objXML.selectSingleNode("//invoke")

If objElem.getAttribute("name") = "selectFolder" Then

Dim xmlElem As IXMLDOMElement

Set xmlElem = objXML.selectSingleNode("invoke/arguments/string")

directoryPath = xmlElem.Text

Dim fso As FileSystemObject

Dim fso_folder As Folder

Dim txt As String

Dim fso_file As File

Dim i As Long

Dim file_names() As String

' Make a new File System object.

Set fso = New FileSystemObject

' Get the FSO Folder (directory) object.

MsgBox directoryPath + "\"

Set fso_folder = fso.GetFolder(directoryPath + "\")

' Make the list of names.

ReDim file_names(1 To fso_folder.Files.Count)

i = 1

For Each fso_file In fso_folder.Files

file_names(i) = fso_file.Name

i = i + 1

Next fso_file

'MsgBox file_names(1)

Dim postString As String

postString = "<rootNode>"

For k = 1 To UBound(file_names)

postString = postString + "<filename>" + file_names(k) + "</filename>"

Next k

postString = postString + "</rootNode>"



mySWF.CallFunction ("<invoke name='sendToActionScript' returntype='xml'><arguments><string>" + postString + "</string></arguments></invoke>")

End If

If objElem.getAttribute("name") = "openFile" Then

Dim xmlElem1 As IXMLDOMElement

Set xmlElem1 = objXML.selectSingleNode("invoke/arguments/string")

MsgBox xmlElem1.Text

temp1 = Split(xmlElem1.Text, "\")



For i = 1 To UBound(temp1) - 1

finalPath = finalPath + temp1(i) + "\"

Next i

Dim ret As Long

Dim lErr As Long

ret = ShellExecute(Me.hwnd, vbNullString, xmlElem1.Text, vbNullString, finalPath, 1)

If (ret < 0) Or (ret > 32) Then

'ShellEx = True

Else

Select Case ret

Case 0

lErr = 7: sErr = "Out of memory"

Case ERROR_FILE_NOT_FOUND

lErr = 53: sErr = "File not found"

Case ERROR_PATH_NOT_FOUND

lErr = 76: sErr = "Path not found"

Case ERROR_BAD_FORMAT

sErr = "The executable file is invalid or corrupt"

Case SE_ERR_ACCESSDENIED

lErr = 75: sErr = "Path/file access error"

Case SE_ERR_ASSOCINCOMPLETE

sErr = "This file type does not have a valid file association."

Case SE_ERR_DDEBUSY

lErr = 285: sErr = "The file could not be opened because the target application is busy. Please try again in a moment."

Case SE_ERR_DDEFAIL

lErr = 285: sErr = "The file could not be opened because the DDE transaction failed. Please try again in a moment."

Case SE_ERR_DDETIMEOUT
lErr = 286: sErr = "The file could not be opened due to time out. Please try again in a moment."
Case SE_ERR_DLLNOTFOUND
lErr = 48: sErr = "The specified dynamic-link library was not found."
Case SE_ERR_FNF
lErr = 53: sErr = "File not found"
Case SE_ERR_NOASSOC
sErr = "No application is associated with this file type."
Case SE_ERR_OOM
lErr = 7: sErr = "Out of memory"
Case SE_ERR_PNF
lErr = 76: sErr = "Path not found"
Case SE_ERR_SHARE
lErr = 75: sErr = "A sharing violation occurred."
Case Else
sErr = "File could not be opened due to invalid application"
End Select
'Err.Raise lErr, , App.EXEName & "", sErr
MsgBox sErr
End If
End If
End Sub

Lets take a look at VB code.

"Private Sub mySWF_FlashCall(ByVal request As String)" Its very important to keep the function name in same way like with the shockwave object and adding the "_FlashCall" to let your application work. "request" is the variable as in XML format with arguement as shown and explained above in the tag.

With this code

postString = "<rootNode>"
For k = 1 To UBound(file_names)
postString = postString + "<filename>" + file_names(k) + "</filename>"
Next k
postString = postString + "</rootNode>"

I am creating one Dynamic XML String which we are sending to flash function "callFromVB".

This is the line which is sending the data to AS3.0 method.

mySWF.CallFunction ("<invoke name='sendToActionScript' returntype='xml'><arguments><string>" + postString + "</string></arguments></invoke>")

The other code is for opening a perticular file with the associated application as in case of my requirement it was required. I have put in a check to see if the file is working or not corrupt and if its fine i am launching it.

You can download entire application @ below link :


A special thanks to my manager Manotosh Roy who helped me in doing this stuff :o)

I hope you find it useful.

-Nitin

2 comments:

Unknown said...

Hey there! This post is just what I was looking for the only problem is I'm getting an error when I run the vb app.

The Project is the original source as download from the web posting. I get the Error When Trying to Call the mySWF.CallFunction. The error is bellow.

"Run-time error '-2147467259 (80004005)':
Method 'CallFunction' of object 'IShockwaveFlash' Failed"

I was just wonder if you could give me some insight on how to fix it.

Sincerely,
Derek

Unknown said...

Hi,
I was looking for this everywhere, now I've found it I'm a little confused. What I want to do Is set and get variables for a presentation that I am running on VB.NET on an AS3 Flash file. How could I do this?

Using FormBuilder, Validators & FormGroup in Angular 2 to post data to Node server (node.js)

This blog explains usage of  FormBuilder , Validators , FormGroup classes in Angular 2 projects. In this post we will create HTML form a...