I'm developing a Flex Air (desktop) application that loads images from the local filesystem into a TileList. The user will then be able to drag (copy) these images out of the list onto another control.
I've finally got the images showing up correctly (and not disappearing after scrolling the TileList) but they seem to disappear from the TileList at the start of a drag operation.
I come from a .NET background and am just learning AS3/Flex, so if you see me using any anti-patterns here, feel free to point them out!
Sample code follows (I've tried to make this as minimal as possible).
Test.mxml:
<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx">
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import mx.events.FlexEvent;
[Bindable]
protected var _pics:ArrayCollection = new ArrayCollection();
protected function picsList_creationCompleteHandler(event:FlexEvent):void
{
const imageFolderPath:String = "c:\\users\\bbrooks\\Pictures";
var imageDir:File = new File(imageFolderPath);
var imageFiles:Array = imageDir.getDirectoryListing();
for each(var imageFile:File in imageFiles)
{
_pics.addItem(new PictureObject(imageFile.nativePath));
}
// give images a chance to load
var timer:Timer = new Timer(1000);
timer.addEventListener(TimerEvent.TIMER, onTimerExpired);
timer.start();
}
protected function onTimerExpired(event:TimerEvent):void
{
picsList.dataProvider = _pics;
}
]]>
</fx:Script>
<mx:TileList id="picsList" x="0" y="0" width="100%" height="100%" dragEnabled="true" dragMoveEnabled="false"
creationComplete="picsList_creationCompleteHandler(event)" >
<mx:itemRenderer>
<fx:Component>
<mx:Image width="75" height="75" source="{data.image}" />
</fx:Component>
</mx:itemRenderer>
</mx:TileList>
</s:WindowedApplication>
PictureObject.as:
package
{
import flash.display.Loader;
import flash.display.LoaderInfo;
import flash.events.Event;
import flash.net.URLRequest;
import mx.controls.Image;
[Bindable]
[RemoteClass]
public class PictureObject extends Object
{
protected var _image:Image = null;
public function get image():Image { return _image; }
public function set image(newValue:Image):void { _image = newValue; }
public function PictureObject(path:String)
{
var imageLoader:Loader = new Loader();
imageLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onImageLoaded);
imageLoader.load(new URLRequest(path));
}
protected function onImageLoaded(e:Event):void
{
var imageLoader:Loader = LoaderInfo(e.target).loader;
var bmp:Bitmap = Bitmap(imageLoader.content);
_image = new Image();
_image.smoothBitmapContent = true;
_image.source = new Bitmap(bmp.bitmapData);
_image.width = imageLoader.width;
_image.height = imageLoader.height;
}
}
}
I will mostly reply to your secondary question (guessing it will resolve the primary in one fell swoop): as anti-patterns go, this is not a bad example ;)
You are manually loading the images while the Image class has the loading built-in; just give a URL to its source property and it will automatically load it.
You are setting an Image instance as the source of another Image instance; the source property expects URLs or ByteArrays; I'm surprised it doesn't throw an error; the Image class is probably smart enough to extract the source of the other Image instance.
The Timer is redundant. As said, an Image automatically takes care of loading its content.
The s:Image tag isn't wrapped in an ItemRenderer and hence should have no access to a data property: this code shouldn't even compile.
There's no point in having a Bindable property _pics if you don't plan on binding it.
You use the mx TileList. Why not use the more "modern" Spark version? (This doesn't mean the mx class won't work in a Spark application though).
So you have a lot of deleting to do. You can scrap the PictureObject class altogether; remove the Timer code; and just add the URL Strings to the _pics collection. As a plus you could also replace the mx TileList with a Spark List with TileLayout; something like this:
<s:List id="picsList">
<s:layout>
<s:TileLayout />
</s:layout>
<s:itemRenderer>
<fx:Component>
<s:ItemRenderer>
<s:Image source="{data}" />
</s:ItemRenderer>
</fx:Component>
</s:itemRenderer>
</s:List>
The ActionScript part could be reduced to this:
const imageFolderPath:String = "c:\\users\\bbrooks\\Pictures";
var imageDir:File = new File(imageFolderPath);
var imageFiles:Array = imageDir.getDirectoryListing();
picsList.dataProvider = new ArrayCollection(imageFiles);
Thanks RIAstar. Your answer put me on the track to solving the problem. The new sample code appears below.
The original problem seems to have been with the mx:Image control. Not sure why, but using the s:Image control seems to work.
Granted, this could be accomplished without the PictureObject class at all by simply setting the data source to a list of file paths, but I'm using the image data later and I wanted to get it working by supplying the image data to the custom renderer dynamically.
Test.mxml:
<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx">
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import mx.events.FlexEvent;
import spark.components.Image;
protected var _pics:ArrayCollection = new ArrayCollection();
protected function picsList_creationCompleteHandler(event:FlexEvent):void
{
const imageFolderPath:String = "c:\\users\\bbbrooks\\Pictures";
var imageDir:File = new File(imageFolderPath);
var imageFiles:Array = imageDir.getDirectoryListing();
for each(var imageFile:File in imageFiles)
{
if (imageFile.extension == "jpg")
{
_pics.addItem(new PictureObject(imageFile.nativePath));
}
}
// give images a chance to load
var timer:Timer = new Timer(1000);
timer.addEventListener(TimerEvent.TIMER, onTimerExpired);
timer.start();
}
protected function onTimerExpired(event:TimerEvent):void
{
picsList.dataProvider = _pics;
}
]]>
</fx:Script>
<s:List id="picsList" x="0" y="0" width="100%" height="100%"
creationComplete="picsList_creationCompleteHandler(event)"
dragEnabled="true" dragMoveEnabled="false">
<s:layout>
<s:TileLayout />
</s:layout>
<s:itemRenderer>
<fx:Component>
<s:ItemRenderer>
<s:Image id="imageDisplay"
width="75" height="75" source="{data.bmp}" />
</s:ItemRenderer>
</fx:Component>
</s:itemRenderer>
</s:List>
</s:WindowedApplication>
PictureObject.as
package
{
import flash.display.Bitmap;
import flash.display.Loader;
import flash.display.LoaderInfo;
import flash.events.Event;
import flash.net.URLRequest;
import mx.controls.Image;
[RemoteClass]
[Bindable]
public class PictureObject extends Object
{
protected var _bmp:Bitmap = null;
public function get bmp():Bitmap { return _bmp; }
public function set bmp(newValue:Bitmap):void { _bmp = newValue; }
public function PictureObject(path:String)
{
var imageLoader:Loader = new Loader();
imageLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onImageLoaded);
imageLoader.load(new URLRequest(path));
}
protected function onImageLoaded(e:Event):void
{
var imageLoader:Loader = LoaderInfo(e.target).loader;
// create our own copy of the bits in the Loader
var bmp:Bitmap = Bitmap(imageLoader.content);
_bmp = new Bitmap( Bitmap(imageLoader.content).bitmapData );
}
}
}
Related
I am currently trying to make a game on flex and one of the problems I ran in to is how to play a short animation at the beginning. This is what I have so far:
Game.mxml
<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
name="Game"
backgroundColor="#000000"
horizontalAlign="center"
creationComplete="Init();"
enterFrame="UpdateFrame();"
paddingLeft="0"
paddingTop="0"
paddingBottom="0"
paddingRight="0"
width="800" height="600">
<mx:Script>
<![CDATA[
include "Game.as";
]]>
</mx:Script>
<mx:Canvas id="gamePanel" x="0" y="0" width="100%" height="100%" mouseDown="MouseDown(event)" mouseUp="MouseUp(event)" mouseMove="MouseMoved(event)"/>
</mx:Application>
and Game.as
import flash.display.*;
import flash.events.*;
import flash.external.ExternalInterface;
import mx.events.*;
import mx.controls.*;
[Embed(source="MyVideoClip.flv")] private var MyVideoClip:Class;
public function Init():void
{
var MyVideo:Video = new Video(800, 600);
addChild(MyVideo);
var qNetConnection:NetConnection = new NetConnection();
qNetConnection.connect(null);
var qNetStream:NetStream = new NetStream(qNetConnection);
MyVideo.attachNetStream(qNetStream);
qNetStream.client = new Object();
qNetStream.play(MyVideoClip);
}
private function UpdateFrame():void
{
}
private function MouseDown(event:MouseEvent):void
{
}
private function MouseUp(event:MouseEvent):void
{
}
private function MouseMoved(event:MouseEvent):void
{
}
I am rather new to Flex and AS3 so most of this code was ripped off web tutorials. Whenever I try to compile it I get: 'Error: 'MyVideoClip.flv' does no have a recongnized extention, and a mimeType was not provided. Error: unable to transcode 'MyVideoClip.flv''
If I remove the 'embed' line and replace MyVideoClip with "MyVideoClip.flv" in the play() function, the code compiles with no errors, but when I open the SWF all I get is a black screen. What am I doing terribly wrong?
Thanks in advance!!
Try setting the mime-type, e.g.:
[Embed(source = "MyVideoClip.flv", mimeType = "application/octet-stream")]
You embedded the video file (its bytes) into the output SWF. So now NetStream must play from a bytes source. Just set a byteArray as equal to new MyVideoClip(); and append to NetStream.
Try this...
[Embed(source="MyVideoClip.flv", mimeType="application/octet-stream")] private var MyVideoClip:Class; //# embed the FLV's bytes
public var VideoClipBytes : ByteArray;
public var MyVideo:Video;
public var qNetConnection:NetConnection;
public var qNetStream:NetStream;
public function Init():void
{
VideoClipBytes = new MyVideoClip() as ByteArray; //# fill a byte Array with embedded bytes
qNetConnection = new NetConnection(); qNetConnection.connect(null);
qNetStream = new NetStream(qNetConnection);
qNetStream.client = new Object();
MyVideo = new Video(800, 600);
MyVideo.attachNetStream(qNetStream);
addChild(MyVideo);
qNetStream.play(null); //# play mode is now bytes
qNetStream.appendBytesAction(NetStreamAppendBytesAction.RESET_BEGIN); //# ready for new audio/video data
qNetStream.appendBytes( VideoClipBytes ); //# add bytes to decoder queue (playback)
}
PS : I made some of your variables as public so later you can access & control them from any other functions. Remember if you make var inside a public function it stays valid only in that one function and doesn't exist to other functions. Best make such vars as publicly available to all functions.
yesterday me and my friend Ketan thakkar were working on an issue related with drag drop in flex.
Image can dragged easily if it is has direct your or embedded image.
But if we try to drag image which has a source from flex loader, original image loos the parent and then never go back to its original place. We tried to find the solution and with a good luck we got success.
the code is below. If anyone know why this problem exist please help us.
For now we have managed it this way.
<?xml version="1.0" encoding="utf-8"?>
<mx:Script>
<![CDATA[
import mx.controls.Image;
import mx.core.DragSource;
import mx.core.FlexLoader;
import mx.core.UIComponent;
import mx.events.DragEvent;
import mx.events.FlexEvent;
import mx.graphics.ImageSnapshot;
import mx.managers.DragManager;
private var fl:FlexLoader = new FlexLoader();
private var img1:Image = new Image();
private function doDragEnter(event:DragEvent):void
{
DragManager.acceptDragDrop(UIComponent(event.target));
}
private function doDragDrop(event:DragEvent):void
{
var img:Image;
if (event.dragInitiator.parent == dropCanvas)
img = event.dragInitiator as Image;
else
{
img = new Image();
img.width = img.height = 120;
img.source = img1.source;
img.addEventListener(MouseEvent.MOUSE_DOWN, doDragStart);
dropCanvas.addChild(img);
}
img.x = event.localX - (event.dragSource.dataForFormat("localX") as Number);
img.y = event.localY - (event.dragSource.dataForFormat("localY") as Number);
}
private function doDragStart(event:MouseEvent):void
{
var imageSnap:ImageSnapshot = ImageSnapshot.captureImage(event.currentTarget as IBitmapDrawable);
var imageByteArray:ByteArray = imageSnap.data as ByteArray;
img1.load(imageByteArray);
var dragInitiator:Image = event.currentTarget as Image;
var dragSource:DragSource = new DragSource();
var dragProxy:Image = new Image();
dragProxy.source = img1.source;
dragProxy.x = mouseX-25;
dragProxy.y = mouseY-25;
dragProxy.width = 80;
dragProxy.height= 80;
DragManager.doDrag(dragInitiator, dragSource, event, dragProxy,0,0,1,false);
}
protected function application1_creationCompleteHandler(event:FlexEvent):void
{
fl.contentLoaderInfo.addEventListener(Event.COMPLETE, oncomplete);
fl.load( new URLRequest('assets/1.swf'));
}
private function oncomplete(event:Event):void
{
img.source = fl;
}
]]>
</mx:Script>
<controls:FlexBook width="400" height="200"
itemSize="halfPage">
<controls:content>
<mx:Image id="img" mouseDown="doDragStart(event)" /><!--source="#Embed('assets/Hawk.jpg')"-->
<mx:Image id="img11" mouseDown="doDragStart(event)" /><!--source="#Embed('assets/Hawk.jpg')"-->
<mx:Image id="img2" mouseDown="doDragStart(event)" /><!--source="#Embed('assets/Hawk.jpg')"-->
</controls:content>
</controls:FlexBook>
<mx:Canvas id="dropCanvas" width="100%" height="100%" borderColor="#1C5CC7" dragEnter="doDragEnter(event)" dragDrop="doDragDrop(event)" borderStyle="solid" cornerRadius="20" borderThickness="6" backgroundColor="#7E92FC"/>
<!--<mx:Image id="dropImage" source="assets/1.swf" />-->
I had a similar problem, the problem is in this line:
dragProxy.source = img1.source;
you should to copy bitmap img1 like here instead linking it.
I'm not sure if that title is descriptive enough, but here is the basic issue. I have a spark.effects.Animate instance repeating n times with a listener on the EFFECT_REPEAT event. In that event's listener, I'm incrementing a variable. Logic would dictate that if the variable started at 0 and the animation was played with a repeatCount of 3, the variable would be 2 when finished. This works fine, until I focus a different tab. When doing this, on occasion, the repeat event isn't fired/handled (or the animation is just not actually doing the animation) and my count isn't correct when finished.
This is a fully functioning example built against Flex 4.5. To duplicate just click the GO button and maintain focus on the TAB. Then click it again and switch tabs and switch back. The counts don't match.
How can I fix this so that the repeat event is called consistently?
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" width="600" height="400" applicationComplete="application1_applicationCompleteHandler(event)">
<fx:Script>
<![CDATA[
import mx.controls.Image;
import mx.events.EffectEvent;
import mx.events.FlexEvent;
import spark.effects.Animate;
import spark.effects.animation.MotionPath;
import spark.effects.animation.SimpleMotionPath;
private var animate:Animate;
private var someCounter:int = 0;
protected function application1_applicationCompleteHandler(event:FlexEvent):void
{
// Create the graphic
var img:Image = new Image();
img.source = "http://www.google.com/intl/en_com/images/srpr/logo3w.png";
// Add the graphic
addElement( img );
// Create the motion path
var smp:SimpleMotionPath = new SimpleMotionPath();
smp.property = "x";
smp.valueFrom = 0;
smp.valueTo = this.width - 275;
// Add the motion path to a Vector
var paths:Vector.<MotionPath> = new Vector.<MotionPath>();
paths.push( smp );
// Create the animation
animate = new Animate();
animate.easer = null;
animate.target = img;
animate.duration = 150;
animate.repeatCount = 15;
animate.motionPaths = paths;
animate.addEventListener( EffectEvent.EFFECT_REPEAT, animate_effectRepeatHandler );
animate.addEventListener( EffectEvent.EFFECT_END, animate_effectEndHandler );
}
private function animate_effectRepeatHandler( event:EffectEvent ):void
{
lblCounter.text = someCounter.toString();
someCounter++;
}
private function animate_effectEndHandler( event:EffectEvent ):void
{
lblCounter.text = someCounter.toString(); // Sometimes doesn't equal animate.repeatCount - 1
}
protected function button1_clickHandler(event:MouseEvent):void
{
if( !animate.isPlaying )
{
someCounter = 0;
animate.play();
}
}
]]>
</fx:Script>
<mx:Label id="lblCounter" horizontalCenter="0" bottom="50" width="150" height="50" textAlign="center" />
<mx:Button horizontalCenter="0" bottom="0" label="GO" width="150" height="50" click="button1_clickHandler(event)" />
</s:Application>
I'm quite new to Flex and ActionScript, and I'm trying to do an application whose purpose is simply to be able to dynamically create rectangles on a panel: on mouse down the rectangle is created and updated on mouse move events (left button hold down), then the rectangle is achieved with mouse up.
<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
width="550" height="400"
creationComplete="this.onCreationComplete(event);">
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import mx.controls.Alert;
import mx.events.FlexEvent;
import mx.graphics.SolidColor;
import spark.primitives.Graphic;
import spark.primitives.Rect;
public static const WIDTH:int = 480;
public static const HEIGHT:int = 300;
public static const BACKGROUND:int = 0xEFEFEF;
public static const CURRENT_FILL:SolidColor = new SolidColor(0x00AA00, 0.75);
protected var rectangles:ArrayCollection = null;
protected var currentRect:Rect = null;
protected var dragging:Boolean = false;
protected function onCreationComplete(event:FlexEvent):void
{
this.rectangles = new ArrayCollection();
this.sprite.graphics.beginFill(BACKGROUND);
this.sprite.graphics.drawRect(0, 0, this.sprite.width, this.sprite.height);
this.sprite.graphics.endFill();
this.sprite.addEventListener(MouseEvent.MOUSE_DOWN, this.onMouseDown);
this.sprite.addEventListener(MouseEvent.MOUSE_UP, this.onMouseUp);
}
protected function onMouseDown(event:MouseEvent):void
{
this.currentRect = new Rect();
this.currentRect.y = 10;
this.currentRect.height = this.sprite.height - (10 * 2);
this.currentRect.x = event.localX;
this.currentRect.width = 1;
this.panel.addElement(this.currentRect);
this.dragging = true;
this.sprite.addEventListener(MouseEvent.MOUSE_MOVE, this.onMouseMove);
}
protected function onMouseUp(event:MouseEvent):void
{
if (this.dragging)
{
this.sprite.removeEventListener(MouseEvent.MOUSE_MOVE, this.onMouseMove);
this.rectangles.addItem(this.currentRect);
this.dragging = false;
}
}
protected function onMouseMove(event:MouseEvent):void
{
if (this.dragging)
{
this.currentRect.width = event.localX - this.currentRect.x;
}
}
]]>
</fx:Script>
<s:Panel id="panel" x="10" y="10" title="Calendar">
<s:SpriteVisualElement id="sprite" width="{WIDTH}" height="{HEIGHT}" />
</s:Panel>
</s:WindowedApplication>
All works fine but now I want to do some actions when the user clicks (or double clicks, or what you want) on a rectangle. I tried to add an event on each rectangle (this.currentRect.addEventListener(MouseEvent.DOUBLE_CLICK, myMethod) added in onMouseUp), but the corresponding method is never executed...
What's the problem?
Objects drawn with the graphics API are not event dispatchers. So use addElement to add the Spark primitive rects that you're creating but not using in any way (to a container such a Group) instead of using the graphics api to draw the shapes.
You might find it's better, however, to simply have the ArrayCollection contain a bunch of bindable data objects and use a DataGroup with itemRenderers to display the data.
You could keep track of the rectangles you have drawn in a collection and do a hit test with the rectangles on mouse clicks and determine if any rectangle is being clicked on.
I have created a scheduling application that spans upwards of 16 weeks. I would like to be able to print the schedule using Flex. I've created a test application that lists incrementing dates. These dates obviously stretch longer than the width of my computer. I would like for my print function to print the entire width of dates across several pages... currently, it prints just what appears on my screen. Is there a way to accomplish this?
Below is the app i've created. There are some calls to custom functions, but they in no way relate to the issue at hand:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="init();">
<mx:Script source="functions/dateFunctions.as" />
<mx:Script>
<![CDATA[
import mx.controls.Alert;
import mx.collections.ArrayCollection;
[Bindable] public var date:Date = new Date;
public var numDays:Number = 50;
[Bindable] public var datesAC:ArrayCollection = new ArrayCollection;
public function init():void
{
var tempDate:String = new String;
for (var i:int = 0; i < numDays; i++)
{
tempDate = dateToNumText(rightDate(date.getMonth(), date.getDate() + i, date.getFullYear()));
datesAC.addItem(tempDate);
}
}
private function printMe() :void
{
var pj:PrintJob = new PrintJob();
pj.start();
// setTimeout(function() :void { finishPrinting(pj);}, 1);
finishPrinting(pj);
}
private function finishPrinting(pj:PrintJob): void {
pj.addPage(this);
pj.send();
}
]]>
</mx:Script>
<mx:VBox width="100%" height="100%">
<mx:Button id="print" label="Start Print" click="printMe()" />
<mx:HorizontalList id="dateList" dataProvider="{datesAC}" width="100%" height="100%" useRollOver="false">
<mx:itemRenderer>
<mx:Component>
<mx:Canvas borderColor="#000000" borderSides="right" borderStyle="solid">
<mx:Text text="{data}" textAlign="center" color="#000000" width="100" />
</mx:Canvas>
</mx:Component>
</mx:itemRenderer>
</mx:HorizontalList>
</mx:VBox>
</mx:Application>
You would have to break up/paginate content on your own. You can send each such logically broken up page to PrintJob.appPage() API so it becomes a printed paper. At present, what is happening would be that content would be getting clipped.
I guess you should use PrintJob.addPage() to add your dates to print job.