Thursday, June 23, 2011

Using Flex UI elements from within a Flash AS3 project

First, a bit of terminology, just in case anyone might be confused by Adobe's wondrously simple world of Flash.

  • Flash Player - A browser plugin which plays Flash (swf) files.
  • Flash - The platform itself
  • Flash Movie - A Flash application
  • Flash Video - A synonym for Flash Movie, obviously. Hah, tricked you! It's actually a video container format used in Flash; it may contain Sorenson Spark, AC6, h264 or WebM.
  • Flash CS5 - A tool for making Flash movies, and assets for use in Flash apps
  • ActionScript 3 (AS3) - The language in which Flash apps are usually written these days.
  • Flash (as an SDK) - An SDK for making Flash apps, possibly using assets created in Flash CS5. You can make a Flash app either with CS5, embedding Actionscript around the place all willy-nilly, or with AS3, importing visual assets from CS5 or elsewhere as appropriate. Or a terrifying combination.
  • Flex (as an SDK) - An SDK for making 'rich internet applications' which compile to Flash, using a peculiar and unpleasant XML-y templating system
  •  Flex (as a toolchain) - A compiler for AS3. For both conventional Flash and Flex-the-SDK
  • Flash Builder - An Eclipse plugin with debugger, profiler, etc, for the Flex toolchain. Until version 4, was called Flex Builder, for extra confusion.
  • Air - A thing for making Flash and/or Flex apps into desktop or smartphone apps
  • Flash Lite - An incompatible cut-down version of Flash for dumbphones; nobody uses this.
All clear? Good. Adobe are masters of clarity, as you can see.

Now, Flex and Flash are not necessarily used for the same things. Flex is intended to be used for 'rich internet applications'; that is fake desktop applications which run in your browser, or when used with Air fake desktop applications which run on the desktop. Flash is intended for multimedia-y things, like games.

However, it would occasionally be desirable to have the best of both worlds. For instance, you might have a game where you wanted to use Flex's graphing libraries, or its far superior UI widgets. Or you might have an application-y app where you wanted to use some fancy graphics, from Flash.

This may not be as easy as it sounds. They're not precisely interchangeable; Flash assets can be used in Flex, but with various caveats; in particular, Flex widgets may not be placed in Flash containers (Sprites, MovieClips and so forth). So, for many purposes, that route, while officially semi-endorsed by Adobe, is out; simply enclosing your Flash thing within a Flex app and using Flex widgets where appropriate is not an option.

That leaves using Flex stuff in Flash. Unfortunately, while you can use Flex libraries in a Flash app, the UI stuff won't work; it assumes that the SystemManager (a thing created on Flex startup) is there, and even if you meticulously create one, you still have the problem that Flex widgets can't be placed in Flash containers.

But there is a third option, one very much not endorsed by Adobe. You can always embed compiled SWFs (Flash's, and Flex's, ultimate output) in Flash (or Flex), and interact with them. So, the solution, or at least a solution, is to create an essentially blank Flex project, load it into your Flash project, put it in a Sprite or similar, and interact with it. Here's how you do that.

First, create a Flex project. In your main mxml file, do something like this:

public function addElement(thingToAdd:Object):void {
var mxmlApp:* = FlexGlobals.topLevelApplication;
mxmlApp.addElement(thingToAdd);
}
public var thingy:Function = setVarOne;
]]>

Set Framework Linkage to 'merged into code', and compile. You may want to make a release build, to keep down the size. Take the produced SWF.

Now, create an AS3 project, and do something like this:

public class ContainerTest extends Sprite
{
[Embed(source="../flash/AsFlex.swf", mimeType = "application/octet-stream")]
private var myWeirdThing:Class;
private var loadedSM:SystemManager;
protected function getThingInstance(handler:Function){
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, function(e:Event):void { onLoadDone(e, handler)});
loader.addEventListener("mx.managers.SystemManager.isBootstrapRoot", systemManagerHandler);
loader.addEventListener("mx.managers.SystemManager.isStageRoot", systemManagerHandler);
Ê
loader.loadBytes(new myWeirdThing());
}
private function systemManagerHandler(e:Event):void {
// Prevent default stops default behaviour here and thus stops some potential
// run time errors.
e.preventDefault();
}
protected function onApplicationComplete(e:Event):void {
trace("app complete");
var expenses:ArrayCollection = new ArrayCollection([
{Expense:"Taxes", Amount:2000},
{Expense:"Rent", Amount:1000},
{Expense:"Bills", Amount:100},
{Expense:"Car", Amount:450},
{Expense:"Gas", Amount:100},
{Expense:"Food", Amount:200}
]);
var canvas:Canvas = new Canvas();
canvas.width = 200;
canvas.height = 200;
var pieChart:PieChart = new PieChart();
pieChart.dataProvider = expenses;
var pieSeries:PieSeries = new PieSeries();
pieSeries.field = "Amount";
pieSeries.nameField = "Expense";
pieSeries.setStyle('labelPosition', 'callout');
pieChart.series.push(pieSeries);
pieChart.x = 50;
pieChart.width=150;
pieChart.height = 150;
canvas.addChild(pieChart);
var legend:Legend = new Legend();
legend.dataProvider = pieChart;
canvas.addChild(legend);
e.target.application['thingy'](canvas);
}
protected function onLoadDone(e:Event, handler:Function):void{
handler(e.currentTarget.loader.content);
e.currentTarget.loader.content.addEventListener("applicationComplete", onApplicationComplete);
loadedSM = SystemManager(e.currentTarget.loader.content);
}
public function ContainerTest()
{
getThingInstance(function(x){addChild(x)});
}
}

This requires some explanation. In this case (as it will be our main class), the function ContainerTest is our point of entry. We kick off getThingInstance. This loads the AsFlex.swf file (created in the last stage) which has been embedded as a binary blob. Note that things will not work properly if you embed it as an swf; we are essentially faking loading it from the network.

First, we add a few event listeners; the two which call systemManagerHandler should prevent crashes. onLoadDone is called when the data has finished loading; all it does is runs the pre-provided handler (which in this case just adds the imported swf to the stage), adds an event listener, for when the internal application is done loading, and fetches the app's system manager. Note that we may not, at this point, start messing with the internal app; we have to wait til it has finished loading.

Once the internal app finishes loading, onApplicationComplete is called. We create a Flex canvas, a Flex piechart and a legend for the piechart, and add both to the canvas. We then instruct the internal Flex app to add the canvas to its layout. However tempting it might be to forgo the Flex app entirely and add the Flex canvas directly to the stage, or to a Sprite or whatever, this will not work.

Because we're actually creating Flex objects in the Flash app (even if we're not displaying them there) we have to add a few support libraries. You should add framework.swc, datavisualisation.swc (for the graphs, replace as appropriate to what you're putting in the Flex canvas), and framework_rb.swc; the first two are found in your Flex SDK libs directory, and the third under frameworks/locale/en_us (or whatever locale is appropriate). Build this and it should Just Work.

Note that I haven't tested any of this too thoroughly, and am not responsible for it causing your computer or application to explode, or whatever. I'm not even saying it's necessarily a good idea, but if you really do want to use Flex components inside Flash, it's a working approach.

1 comment:

  1. Oh thank god I don't have to use flash :)

    ReplyDelete