Friday, March 28, 2008

Using Flash Charts in a Flex3 Application

If you're looking for Chart components to work with Flex applications:
- ILOG Elixir (Flex charts) http://www.ilog.com/products/elixir/
- Fusioncharts (Flash) is producing in a couple weeks support for Flex. http://www.fusioncharts.com/
- Anycharts (Flash)
http://www.anychart.com/blog/category/anychart-for-flex/


In this post i will try to create a wrapper for flash charts. This can also be handy when you're in a need of any Flash AS2 Components. I have seen some people doing it ( or saying they did it ) so it wouldn't be any problem...right?

Google brought me the following results which can be handy:

1 http://flexexamples.blogspot.com/2007/12/load-swf-from-outside-and-call-its.html
2 http://wiki.apexdevnet.com/index.php/Creating_Flex_Charts_Easily
3 http://kb.adobe.com/selfservice/viewContent.do?externalId=749eaa47&sliceId=1
4 http://www.fusioncharts.com/forum/Topic5068-28-1.aspx

Since these flashcharts all use ActionScript 2.0 they cant really communicate with Flex2/3 which uses AS 3.0. If i build an AS2.0 Wrapper Flash that communicates trough localconnection with my flex application and also communicates with the chart object then it should work.

First i will use the FusionCharts ( http://www.blogger.com/www.fusioncharts.com/LiveDemos.asp ) ( since most examples use them ) then i will try the amCharts ( www.amcharts.com/ ).

Here's what i want to do:

- Create a wrapper flash object to communicate between Flex and the Flash chart
- Setup a link in Flex to use the Wrapped Flash object.
- Building a chart with XML data from Flex
- Doing the same with amCharts

First i need to install Flash CS3 to create the wrapper, but then i never used Flash CS3..

Following code is for my wrapper class and will change the text:





//local Connection with flex
var flash_flex:LocalConnection = new
LocalConnection();
//here connection can be made inter-domain
flash_flex.allowDomain("*");
flash_flex.connect("flex_connector");
flash_flex.allowInsecureDomain("*");
flash_flex.changeText =
function(test : String):Void
{
TEXTBOX.text = test;
}


In flash:
- choose to create a new flash file with AS2.0
- create an text object on the screen
- Change the instance name to TEXTBOX
- Change to dynamic text
- Right click on the timeframe and choose for "Actions"
- A code like editor comes up, paste the code above on it and close it.
- Export this using Flash7 and AS2.0 to an SWF file and call it WRAPPER.swf


Now in flex create an localconnection and call the function with one parameter:

MXML





<mx:SWFLoader source="WRAPPER.swf" scaleContent="true" width="300" height="300" x="10" y="10"></mx:SWFLoader>
<mx:Button x="374" y="57" label="Button" click="click();"></mx:Button>


AS




import mx.states.State;
import mx.events.SliderEvent;
import
mx.controls.Alert;
import flash.net.LocalConnection;
[Bindable]
public
var lc:LocalConnection;
private function initApp() : void
{
lc = new
LocalConnection();
lc.allowInsecureDomain("*");
}
private function
click():void
{
lc.send("flex_connector", "changeText","THIS IS A TEST");
}

Oke run and works like a charm.. or doesnt it?

Next thing is loading the chart files...

Here's how i changed the wrapper function:

1. Added a symbol containing MovieClip
2. Giving it as name MC ( its case sensitive !! )
3. put it on X 0 and Y 0
4. Changed the code:



var flash_flex:LocalConnection = new
LocalConnection();
_root.flash_flex.allowDomain("*");
_root.flash_flex.allowInsecureDomain("*");
_root.flash_flex.connect("flex_connector");

flash_flex.methodToExecute = function(param1:String,param2:String) {
TEXTBOX.text = param1;
_root.flash_flex.close();
/*_root.chartWidth = 400;
_root.chartHeight = 400;
_root.strXML = param2;
_root.MC.chartWidth = 400;
_root.MC.chartHeight = 400;
_root.MC.strXML = param2;*/
_root.MC.loadMovie(param1);
_root.param2 = param2;
_root.interid = setInterval(interFunc,1000);

};


function interFunc(){
clearInterval(_root.interid);

_root.MC.setDataXML(_root.param2);
_root.MC._height = 200;
_root.MC._width = 400;
_root.MC._x = 0;
_root.MC._y = 0;
_root._height = 400;
_root._width = 400;
_root._x = 0;
_root._y = 0;
TEXTBOX.text = _root.param2;
}

And here are my changes to the Flex code:




<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" initialize="initApp()">
<mx:Panel x="0" y="0" width="492" height="486" layout="absolute" title="Flash 8 SWF">
<mx:SWFLoader source="WRAPPER.swf" scaleContent="false" width="400" height="400" x="36" y="10"/>


</mx:Panel>
<mx:Button x="435" y="10" label="Button" click="click();"></mx:Button>
<mx:Script>
<![CDATA[
import mx.states.State;
import mx.events.SliderEvent;
import mx.controls.Alert;
import flash.net.LocalConnection;

[Bindable]
public var lc:LocalConnection;
public var myXML:String="<chart><set label='A' value='10' /><set label='B' value='11' /></chart>";
private function initApp() : void
{
lc = new LocalConnection();
lc.allowInsecureDomain("*");
}

private function click():void
{
lc.send("flex_connector", "methodToExecute","Column3D.swf?flashId=MC&chartWidth=400&chartHeight=300",myXML);
}



]]>
</mx:Script>
</mx:Application>


Next problem is the scale, now i have a column chart but not 400x400.. the scale is weird. Sorry but i haven't figured this out yet.

Ok from here i make a jump to amCharts since FusionCharts is making a Flex compatible version. Following code creates an amChart with variables from Flex. Be sure to have the following files in your bin map:

amcolumn.swf
amcolumn_data.xml ( from examples)
amcolumn_settings.xml ( from examples)

Forget the symbols and text object from the project above.
Create a clean flash file and put this in actions:


var flash_flex:LocalConnection = new LocalConnection();
_root.flash_flex.allowDomain("*");
_root.flash_flex.allowInsecureDomain("*");
_root.flash_flex.connect("flex_connector");

_root.flash_flex.methodToExecute = function(chartswf:String)
{
_root.createEmptyMovieClip("holder", this.getNextHighestDepth());
holder.loadMovie(chartswf);
};


And here's the flex code:


<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" initialize="initApp()">
<mx:SWFLoader id="mySWFLOADER" source="WRAPPER.swf" scaleContent="false" width="500" height="500" x="0" y="0"/>
<mx:Button x="435" y="10" label="Button" click="click();"></mx:Button>
<mx:Script>
<![CDATA[
import mx.states.State;
import mx.events.SliderEvent;
import mx.controls.Alert;
import flash.net.LocalConnection;
[Bindable]
public var lc:LocalConnection;
private function initApp() : void
{
lc = new LocalConnection();
lc.allowInsecureDomain("*");
}
private function click():void
{
lc.send("flex_connector", "methodToExecute","amcolumn.swf?settings_file=amcolumn_settings.xml&data_file=amcolumn_data.xml&flash_width="+mySWFLOADER.width+"&flash_height="+mySWFLOADER.height);
}
]]>
</mx:Script>
</mx:Application>




So far this post, good luck creating wrappers!

C#.net communicating with UDT types in Oracle.

First headache on this blog.

Im trying to call a function in oracle which needs an custom type object in C#.net. To achieve this you need the new Oracle ODP.net client software.

note: If you want to connect to any Oracle database you have to get a oracle client, its not like MS SQL which has build in reference and you're ready to go.

The ODP.net software with Visual Studio Tools can be found here: http://www.oracle.com/technology/software/tech/windows/odpnet/index.html

You can use an 11g client on an Oracle 9i database! Please also install the Visual studio tools you need these later.

After installing the client you need to adjust the following 2 files for your connection:

tnsnames.ora and sqlnet.ora

My tnsnames is like this:


filki.omnext.net =
(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=filki.omnext.net)(PORT=1521)))(CONNECT_DATA=(SID=filki)(SERVER=DEDICATED)))


In visual studio the connection string would be:

"Data Source=filki.omnext.net;UserID=test;Password=secret;"

Now you can set your Client to connect in Visual Studio with Oracle, this is all wizzard and shouldn't be any problem :). After connection you can see your custom types created in Oracle. Right click on the type and select generate class files! After that you have a nice C# class of your UDT from Oracle.

The function and my UDT im calling in Oracle is like this:

CREATE OR REPLACE TYPE TEST.T_NUMBER_FIELD AS
OBJECT
(
AVALUE NUMBER(8) ,
COLOUR_CODE NUMBER(2)
);
/
CREATE OR REPLACE TYPE TEST.T_VARCHAR_FIELD AS OBJECT
(
AVALUE
VARCHAR2(20) ,
COLOUR_CODE NUMBER(2)
);
/
CREATE OR REPLACE TYPE
TEST.T_CONTAINER AS OBJECT
(
BOEKING T_VARCHAR_FIELD ,
EENHEID_NUMMER T_VARCHAR_FIELD ,
GEWICHT T_NUMBER_FIELD ,
X_POS
T_NUMBER_FIELD ,
Y_POS T_NUMBER_FIELD ,
Z_POS T_NUMBER_FIELD ,
MEMBER PROCEDURE INIT
);
/
CREATE OR REPLACE TYPE BODY
TEST.T_CONTAINER
AS
MEMBER PROCEDURE INIT
IS
BEGIN
SELF :=
T_CONTAINER(T_VARCHAR_FIELD(NULL, NULL), T_VARCHAR_FIELD(NULL, NULL),
T_NUMBER_FIELD(NULL, NULL),
T_NUMBER_FIELD(NULL, NULL), T_NUMBER_FIELD(NULL,
NULL), T_NUMBER_FIELD(NULL, NULL));
END;
END;
/
CREATE OR
REPLACE FUNCTION TEST.FUNCTION1 ( P_CONTAINER_GEGEVENS IN OUT T_CONTAINER )
RETURN T_CONTAINER
IS
V_COLOUR_CODE NUMBER(2) := 0;
BEGIN
V_COLOUR_CODE := 4;
P_CONTAINER_GEGEVENS.BOEKING.COLOUR_CODE :=
V_COLOUR_CODE;
P_CONTAINER_GEGEVENS.EENHEID_NUMMER.COLOUR_CODE :=
V_COLOUR_CODE;
P_CONTAINER_GEGEVENS.GEWICHT.COLOUR_CODE := MOD(V_COLOUR_CODE
+ 3, 10);
P_CONTAINER_GEGEVENS.X_POS.COLOUR_CODE := MOD(V_COLOUR_CODE + 1,
10);
P_CONTAINER_GEGEVENS.Y_POS.COLOUR_CODE := MOD(V_COLOUR_CODE + 1, 10);
P_CONTAINER_GEGEVENS.Z_POS.COLOUR_CODE := MOD(V_COLOUR_CODE + 1, 10);
RETURN P_CONTAINER_GEGEVENS;
END;
/

Now in C# i have created the following function to call my Oracle function with my C# UDT's

private T_CONTAINER colorContainer()
{
//Connection to Oracle
OracleConnection oraclecon = new OracleConnection("Data
Source=filki.omnext.net;User ID=test;Password=secret;");
//Oracle PL/SQL
Function.
OracleCommand colourCommand = new OracleCommand("FUNCTION1",
oraclecon);
//It has to be a stored procedure.
colourCommand.CommandType
= CommandType.StoredProcedure;
//Create my C# UDT class with values
T_CONTAINER curContainer = new T_CONTAINER();
curContainer.EENHEID_NUMMER = new T_VARCHAR_FIELD(tbEenheidNummer.Text);
curContainer.BOEKING = new T_VARCHAR_FIELD(tbBoeking.Text);
curContainer.GEWICHT = new T_NUMBER_FIELD(Decimal.Parse(tbGewicht.Text));
curContainer.X_POS = new T_NUMBER_FIELD(numXPositie.Value);
curContainer.Y_POS = new T_NUMBER_FIELD(numYPositie.Value);
curContainer.Z_POS = new T_NUMBER_FIELD(numZPositie.Value);
//Parameter
OUT - first send the return value!!
OracleParameter colourOutParameter = new
OracleParameter();
colourOutParameter.OracleDbType = OracleDbType.Object;
colourOutParameter.Direction = ParameterDirection.ReturnValue;
colourOutParameter.ParameterName = "P_CONTAINER_GEGEVENS2";
colourOutParameter.UdtTypeName = "TEST.T_CONTAINER";
colourOutParameter.Value = new T_CONTAINER();
//Parameter IN - object
the container object to send as input.
OracleParameter colourInParameter =
new OracleParameter();
colourInParameter.OracleDbType = OracleDbType.Object;
colourInParameter.Direction = ParameterDirection.Input;
colourInParameter.ParameterName = "P_CONTAINER_GEGEVENS";
colourInParameter.UdtTypeName = "TEST.T_CONTAINER";
colourInParameter.Value = curContainer;
//Parameters to add, first OUT
then IN
colourCommand.Parameters.Add(colourOutParameter);
colourCommand.Parameters.Add(colourInParameter);
//Connection open
if (oraclecon.State == ConnectionState.Closed)
oraclecon.Open();
//Call the function.
try
{
colourCommand.ExecuteNonQuery();
}
catch (OracleException ex)
{
MessageBox.Show(ex.ToString(),
"Oracle exception", MessageBoxButtons.OK, MessageBoxIcon.Error);
oraclecon.Close();
return null;
}
//Connection close
oraclecon.Close();
//Here's the object with filled in values.
return
(T_CONTAINER)colourCommand.Parameters[0].Value;
}

Remember that the return value have to come first because for SOME reason it wont work!

Here's the form which helped me alot, i also posted my problem there:
http://forums.oracle.com/forums/thread.jspa?threadID=386484


Thanks for reading, and goodluck.

-Sjoerd

Thursday, March 27, 2008

First post

Hello there,

The reason i started this blog is because i had a lot of headaches last year and i want to share those to the world. From frustrating simple problems to complex never ending google searches. All combined with lovely solutions or ugly walkarrounds.

Hope i can keep this blog alive (which i suppose is up to my headaches) and save some ones day for having the same headache!

Enjoy,

Sjoerd