AS3 Button with Text and Basic Styling
It’s been a while since I had the opportunity to work on a pure AS3 project. I recently had a need for a simple AS3 button that displays text and decided to customize the SimpleButton control. As I quickly discovered, the SimpleButton control isn’t really setup to support text or a lot of other features, and hacking these features into it doesn’t make much sense. So I built a quick little class that emulates the behavior of SimpleButton but could display text and offer a few other features for styling the button dynamically.
SimpleButtonProject.as
package
{
import com.components.CustomButton;
import flash.display.Sprite;
import flash.events.MouseEvent;
[SWF( width = '200', height = '200', backgroundColor = '#FFFFFF', frameRate = '20')]
public class SimpleButtonProject extends Sprite
{
private var button:CustomButton;
public function SimpleButtonProject()
{
button = new CustomButton("Welcome");
button.x = 0;
button.addEventListener(MouseEvent.CLICK, handleMouseClick);
addChild(button);
}
private function handleMouseClick(event:MouseEvent):void
{
button.label = "Clicked"
/*
// remove the button and mark it for garbage collection
button.removeEventListener(MouseEvent.CLICK, handleMouseClick);
this.removeChild(button);
button = null;
*/
}
}
}
CustomButton.as
package com.components
{
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.text.TextFormatAlign;
public class CustomButton extends Sprite
{
// ---- Properties -----
protected var __button:CustomSimpleButton;
protected var __txtFormat:TextFormat;
protected var __txtField:TextField;
protected var __background:Shape;
protected var __hitarea:Sprite;
protected var _label:String = "";
protected var _font:String = "Arial";
protected var _fontSize:int = 12;
protected var _fontColor:uint = 0x000000;
protected var _upColor:uint = 0xFFCC00;
protected var _overColor:uint = 0xCCFF00;
protected var _downColor:uint = 0x00CCFF;
protected var _buttonWidth:int;
protected var _buttonHeight:int;
protected var _buttonStrokeColor:int = 0x000000;
public function get label():String
{
return _label;
}
public function set label(value:String):void
{
_label = value;
updateDisplayList();
}
public function get buttonStrokeColor():uint
{
return _buttonStrokeColor;
}
public function set buttonStrokeColor(value:uint):void
{
_buttonStrokeColor = value;
updateDisplayList();
}
public function get font():String
{
return _font;
}
public function set font(value:String):void
{
_font = value;
updateDisplayList();
}
public function get fontSize():int
{
return _fontSize;
}
public function set fontSize(value:int):void
{
_fontSize= value;
updateDisplayList();
}
public function get fontColor():uint
{
return _fontColor;
}
public function set fontColor(value:uint):void
{
_fontColor= value;
updateDisplayList();
}
public function get upColor():uint
{
return _upColor;
}
public function set upColor(value:uint):void
{
_upColor = value;
updateDisplayList();
}
public function get overColor():uint
{
return _overColor;
}
public function set overColor(value:uint):void
{
_overColor = value;
}
public function get downColor():uint
{
return _downColor;
}
public function set downColor(value:uint):void
{
_downColor = value;
}
// ---- Constructor -----
public function CustomButton(label:String = "", w:int = 80, h:int = 22)
{
super();
if(label != "") this.label = label;
this.addEventListener(Event.REMOVED_FROM_STAGE, destroy);
_buttonWidth = w;
_buttonHeight = h;
createChildren();
updateDisplayList();
}
// ---- Public Methods -----
public function destroy(event:Event = null):void
{
this.removeEventListener(Event.REMOVED_FROM_STAGE, destroy);
if(__background) {
__background.graphics.clear();
__background = null;
}
if(__hitarea){
__hitarea.removeEventListener(MouseEvent.MOUSE_OVER, onMouseOver);
__hitarea.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
__hitarea.removeEventListener(MouseEvent.MOUSE_UP, onMouseOver);
__hitarea.removeEventListener(MouseEvent.MOUSE_OUT, onMouseOut);
__hitarea.graphics.clear();
__hitarea = null;
}
if(__txtField) {
__txtField.text = "";
__txtField = null;
__txtFormat = null;
}
}
// ---- Protected Methods -----
protected function createChildren():void
{
if(!__background) {
__background = new Shape();
addChild(__background);
}
if(!__txtField) {
__txtField = new TextField();
__txtField.selectable = false
addChild(__txtField);
}
if(!__hitarea) {
__hitarea = new Sprite();
__hitarea.addEventListener(MouseEvent.MOUSE_OVER, onMouseOver);
__hitarea.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
__hitarea.addEventListener(MouseEvent.MOUSE_UP, onMouseOver);
__hitarea.addEventListener(MouseEvent.MOUSE_OUT, onMouseOut);
addChild(__hitarea);
}
}
protected function drawButtonBackground(color:uint):void
{
if(__background){
__background.graphics.beginFill(color);
__background.graphics.lineStyle(1, buttonStrokeColor);
__background.graphics.drawRect(0, 0, _buttonWidth, _buttonHeight);
__background.graphics.endFill();
}
}
protected function updateDisplayList():void
{
if(__txtField){
__txtFormat = new TextFormat(font, fontSize, fontColor, false, null, null, null, null, TextFormatAlign.CENTER);
__txtField.width = _buttonWidth;
__txtField.defaultTextFormat = __txtFormat;
__txtField.text = _label;
__txtField.y = (_buttonHeight - __txtField.textHeight)/2;
}
if(__background) {
drawButtonBackground(upColor);
}
if(__hitarea){
__hitarea.graphics.beginFill(0x000000, 0);
__hitarea.graphics.drawRect(0, 0, _buttonWidth, _buttonHeight);
__hitarea.graphics.endFill();
}
}
protected function onMouseOver(e:Event):void
{
e.stopPropagation();
drawButtonBackground(overColor);
}
protected function onMouseDown(e:Event):void
{
e.stopPropagation();
drawButtonBackground(downColor);
}
protected function onMouseUp(e:Event):void
{
e.stopPropagation();
drawButtonBackground(overColor);
}
protected function onMouseOut(e:Event):void
{
e.stopPropagation();
drawButtonBackground(upColor);
}
}
}
So there you have it, a very simple button that displays text and has a few other features. I realize its not so simple ( # lines of code), but I added a lot of public properties to change the style and label dynamically, as well as logic for garbage collection. If you don’t need those extra features, you can easily be customize it to fit any your needs.
-Mister
Screen Capture with AIR 2 NativeProcess
Prior to AIR 2 the only way to capture the screen was to use something like Marapi Java Bridge that acts as a conduit between your application and the native system.
However, with AIR 2 Beta and the upcoming AIR 2 release, you can now use the command-line tool “screencapture” that comes with Mac OS. Nothing extra needs to be bundled with the application for this to work on a Mac, but on Windows, you need an additional piece of code to execute the screen capture process.
The process of doing a screen capture is pretty easy. You first create a File object that points to the location of the “screencapture” command line on the Mac:
var file:File = File.applicationDirectory;
file = file.resolvePath("usr/sbin/screencapture");
Then create a NativeProcessStartupInfo object that will be used when we start the NativeProcess.The arguments property of the NativeProcessStartupInfo takes a list of arguments that will be passed to the command line tool when its started. For a complete list of arguments used by the screencapture command line tool, type “screencapture –help” in the terminal window. For this example I wanted to push out a file named “screencap.png” to the desktop from a selection of the screen. Optionally, you could capture the image to the clipboard and paste it directly.
var nativeProcessStartupInfo:NativeProcessStartupInfo = new NativeProcessStartupInfo(); var args:Vector. = new Vector.(); args[0] = "-i"; args[1] = "screencapture.png"; nativeProcessStartupInfo.arguments = args; nativeProcessStartupInfo.executable = file;
Now we can start the native process:
process = new NativeProcess(); process.start(nativeProcessStartupInfo);
The screencapture command tool will be launched and you will see the selection cursor on your screen. After you make a selection, the file will automatically be saved to the desktop as “screencapture.png”.
Here is the complete project code:
<?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"
creationComplete="init()">
<fx:Script>
<![CDATA[
private var process:NativeProcess;
private function init():void
{
launchNativeProcess();
}
private function launchNativeProcess():void
{
if(NativeProcess.isSupported) {
var file:File = File.applicationDirectory;
if (Capabilities.os.toLowerCase().indexOf("win") > -1) {
//file = file.resolvePath("bin/mylocalexe.exe");
} else if (Capabilities.os.toLowerCase().indexOf("mac") > -1) {
file = file.resolvePath("/usr/sbin/screencapture");
}
var args:Vector.<String> = new Vector.<String>();
args[0] = "-i";
args[1] = "screencapture.png";
var nativeProcessStartupInfo:NativeProcessStartupInfo = new NativeProcessStartupInfo();
nativeProcessStartupInfo.arguments = args;
nativeProcessStartupInfo.executable = file;
nativeProcessStartupInfo.workingDirectory = File.desktopDirectory;
process = new NativeProcess();
process.start(nativeProcessStartupInfo);
} else {
trace("Native Process not supported");
}
}
]]>
</fx:Script>
</s:WindowedApplication>
Don’t forget to include support for extended desktop in the application xml file:
<supportedProfiles>extendedDesktop</supportedProfiles>
To get something like this to work on Windows, you need to know a little bit of C or C# so you can call your own service and launch the screen print functionality on Windows. For more about using the NativeProcess in AIR 2 for both Windows and Mac, you check out this Adobe article.
-Mr
AS3 Yammer Library
For the past few months I have been working on an AS3 library that wraps the Yammer API for use in Flash, Flex, and AIR projects. The biggest hurdle was making the application work for the web. As it is now, for Flash projects, you still need to be targeting Flash player 10 so that you can upload the ByteArray information file attachments. The second hurdle was creating an easy Oauth flow that doesn’t add items to the header that would violate the Flash player security, something you don’t have to worry about when creating AIR applications.
With that work out of the way, I would like to introduce an AS3 Yammer API library that communicates with the Yammer service. The as3yammerlib is hosted on GitHub and the source code is publicly available. To use the library you will need your own consumer key and secret for the Yammer API available from Yammer’s developer site. The library is also dependent on the as3corelib for doing JSON parsing and as3-signals for broadcasting events.
I put together a Flex web-based example using Flex Builder 4 that walks you through the Oauth process and retrieves messages for the logged in user. To use the code, just create a new Flex project and paste the Main.mxml file in your main application file and create another mxml for the MessageItemRenderer file within the same directory:
Main.mxml
<?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/halo"
xmlns:ns="library://ns.adobe.com/flex/mx"
creationComplete="init()"
minWidth="1024" minHeight="768">
<fx:Script>
<![CDATA[
import com.yammer.api.Yammer;
import com.yammer.api.events.YammerEvent;
import com.yammer.api.vo.Oauth;
import com.yammer.api.vo.YammerCurrentUser;
import com.yammer.api.vo.YammerError;
import com.yammer.api.vo.YammerMessageList;
import com.yammer.api.vo.YammerTab;
import mx.collections.ArrayCollection;
import mx.controls.Alert;
private var oauthToken:String;
private var oauthSecret:String;
// your consumer key and secret go here
private var consumerKey:String = "your consumer key";
private var consumerSecret:String = "your consumer secret";
private var service:Yammer;
[Bindable] private var currentUser:YammerCurrentUser;
private function init():void
{
this.service = new Yammer(this.consumerKey, this.consumerSecret);
this.service.errorReceived.add(errorReceived);
// we have tokens stored
if(oauthToken && oauthSecret){
service.setOuthTokens(oauthToken, oauthSecret);
this.getCurrentUser();
}
}
// get request token from service.
private function getRequestToken():void
{
service.resultsReceived.add(requestTokenReceived);
service.requestToken();
}
// submit the virify pin from the web site
private function getAccessToken(verify_pin:String):void
{
service.resultsReceived.add(accessTokenReceived);
service.accessToken(verify_pin);
}
// sends user to browser page to retrieve verification pin.
private function sendAuthorizationRequest():void
{
service.sendAuthorizationRequest(); // open browser window
}
private function getCurrentUser():void
{
service.resultsReceived.add(currentUserReceived);
service.getCurrentUser();
return;
}
public function getMessages(tab:YammerTab):void
{
service.resultsReceived.add(messageListReceived);
service.getMessages(tab.url);
return;
}
private function requestTokenReceived(value:Oauth):void
{
service.resultsReceived.remove(requestTokenReceived);
Alert.show("Request tokens received: " + value.oauth_token);
}
private function accessTokenReceived(value:Oauth):void
{
service.resultsReceived.remove(accessTokenReceived);
Alert.show("Access tokens received:: token: " + value.oauth_token + " secret: " + value.oauth_token_secret);
this.getCurrentUser();
}
private function currentUserReceived(value:YammerCurrentUser):void
{
service.resultsReceived.remove(currentUserReceived);
currentUser = value;
var tab:YammerTab = currentUser.tabs[0]; // get first user tab, usually My Feed
this.getMessages(tab);
}
private function messageListReceived(value:YammerMessageList):void
{
service.resultsReceived.remove(messageListReceived);
var messagelist:YammerMessageList = value;
var messages:ArrayCollection = new ArrayCollection(messagelist.getMessages());
messageList.dataProvider = messages;
}
private function errorReceived(error:YammerError):void
{
Alert.show("Yammer Service Error: " + error.errorMessage);
}
]]>
</fx:Script>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<s:Button label="1. Request Token" click="getRequestToken()" width="120" x="10" y="10"/>
<s:Button label="2. Open Browser" click="sendAuthorizationRequest()" width="120" x="138" y="10"/>
<s:TextInput id="pinInput" text="3. Paste Pin" width="120" textAlign="center" focusIn="pinInput.text = ''" x="266" y="10"/>
<s:Button label="4. Access Token" click="getAccessToken(pinInput.text)" width="120" x="394" y="10"/>
<s:List id="messageList" x="10" y="113" width="563" height="337" itemRenderer="MessageItemRenderer"/>
<ns:Image source="{currentUser.mugshot_url}" width="48" height="48" x="11" y="45"/>
<s:Label text="{currentUser.full_name}" x="67" y="50" fontSize="14"/>
<s:Label text="{currentUser.job_title}" x="67" y="72" fontSize="11"/>
</s:Application>
MessageItemRenderer.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:ItemRenderer width="100%"
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/halo" xmlns:ns="library://ns.adobe.com/flex/mx">
<fx:Script>
<![CDATA[
import com.yammer.api.utils.MessageUtilities;
import com.yammer.api.vo.YammerMessage;
[Bindable] private var message:YammerMessage;
override public function set data(value:Object):void
{
super.data = value;
message = data as YammerMessage;
}
/**
* override set data to set
*/
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth,unscaledHeight);
}
]]>
</fx:Script>
<s:Rect left="0" bottom="1" right="0" height="1">
<s:fill>
<s:SolidColor color="0x000000" alpha=".2"/>
</s:fill>
</s:Rect>
<s:HGroup width="100%" height="100%" paddingTop="5" paddingBottom="5" paddingRight="5" paddingLeft="5">
<ns:Image source="{message.sender.mugshot_url}" width="48" height="48"/>
<s:VGroup width="100%" height="100%">
<s:Label text="{message.sender.full_name}" maxDisplayedLines="1" left="10" top="10" right="26" fontWeight="bold" color="#000000"/>
<s:RichEditableText id="richEdTxt" text="{message.body_plain}" width="100%" editable="false" selectable="false" focusEnabled="false" />
<s:HGroup width="100%" paddingTop="2">
<s:Label text="{message.created_at}"/>
</s:HGroup>
</s:VGroup>
</s:HGroup>
</s:ItemRenderer>
I am sure there are lots of ways to improve the library, but this one is a pretty simple start with lots of functionality. Please let me know if you find any errors and feel free to fork this version and add any improvements to the code.
-Mr
Preventing scrolling and mouse events on out of focus AIR application
This has been a little bit of annoyance for me for some time. When an AIR application (like any Twitter application) is out of focus and you click on the application activate it, you inadvertently click a link or button within the application. You are also able to scroll the application when the application has no focus, which may or may not be your desired behavior.
I wanted a way to prevent mouse events and scrolling until the application has focus. I tried to find away around this annoyance by capturing if the application has focus by listening for Event.ACTIVATE or Event.DEACTIVATE events on the NativeApplication.nativeApplication:
NativeApplication.nativeApplication.addEventListener(Event.ACTIVATE, handleAppActivate);
NativeApplication.nativeApplication.addEventListener(Event.DEACTIVATE, handleAppDiactivate);
private function handleAppActivate(event:Event):void
{
// set a flag that application has focus so now links and buttons work
}
private function handleAppDiactivate(event:Event):void
{
// set some flag to let the application has no focus so disable links and buttons
}
This solution has a couple of problems. First the application has focus the instance you click on any link within the application window. So fine, the way around that is to use some kind of timer so that your flag that says application is now active is not set until some time after the application receives focus. This leads to the second problem, at least for larger scale applications. You have to pipe the flag that says the application has no focus to every button and link through the entire application, this is a huge pain.
Today I just happened to be playing with the new FramerateThrottler class created by Grant Skinner to reduce CPU usage for AIR applications running on Mac (this is yet another gripe of mine). So on his bog there are some comments about using mouseChildren and mouseEnabled on all open AIR windows to reduce CPU. Anyways, when I was messing around with this suggestion, I happened to notice my application no longer scrolled when the application was in the background, but mouse events were still active.
So I then thought of combining my previous attempt at preventing links and buttons from being clicked on with mouseChildren and mouseEnabled. I ended up only setting mouseChildren and mouseEnabled to true if the application has focus, and after a small timer dalay of 500 miliseconds. That gives me just enough time to click on the application any where without also clicking on a link or button and activating it. So now I can click on the application to give it focus any place I like without firing off click events:
NativeApplication.nativeApplication.addEventListener(Event.ACTIVATE, handleAppActivate);
NativeApplication.nativeApplication.addEventListener(Event.DEACTIVATE, handleAppDiactivate);
private var timer:Timer;
private function handleAppActivate(event:Event):void
{
// set timer to enable mouse events after short delay on app activation
timer = new Timer(500);
timer.addEventListener(TimerEvent.COMPLETE, enableAppEvents);
timer.start();
}
private function handleAppDiactivate(event:Event):void
{
this.mouseChildren = false;
this.mouseEnabled = false;
}
private function enableAppEvents(event:TimerEvent):void
{
timer.stop();
timer.removeEventListener(TimerEvent.COMPLETE, enableAppEvents);
// now scrolling and mouse events work
this.mouseChildren = true;
this.mouseEnabled = true;
}
That’s it, a simple solution that was probably sitting under my nose the whole time but I never thought of it.
UPDATE
Jonnie Hallman from Adobe posted a great article that expands the idea of handling throttling and events (mouse wheel for example).
http://www.adobe.com/devnet/air/flex/articles/framerate_throttling.html
- Mr
Custom AIR updater interface using ApplicationUpdater
Update
I have updated the project to work with Flash Builder 4, you can get the code at this post.
Original Post:
To tell you the truth, I never gave the ApplicationUpdaterUI much thought in terms of memory use before this post. I just thought it was a great way to add a complete update system to your AIR applications. I had not thought it could be so memory intensive until a recent conversation with Jesse Warden on Twitter:

* Please note that Jesse is dude that looks about 17 and I am the older more distinguished gentleman.
Problem with ApplicationUpdaterUI
Indeed, that “mofo eats mad RAM” is putting it lightly. It turns out that the ApplicationUpdaterUI consumes about 14MB of RAM within the application. Furthermore, for the ApplicationUpdaterUI to even check for available updates, it must loads the entire UI in memory even if not displayed. So just by using the framework, you end adding 14MB to your application that won’t be garbage collected. Granted, in most cases 14MB of RAM is not that big of deal. However, for smaller applications you might want to consider an alternative approach.
There happens to another class file called ApplicationUpdater. This class basically gives you all the benefits of the ApplicationUpdaterUI framework without any of the visible elements. This means you have to build your own user interface. This might actually be a benefit in the long run as you may want to customize your application updater to match your application. The other benefit is that you can use the ApplicationUpdater class to check for updates without loading any user interface. The interface only needs to be loaded if there is an actual update available.
Custom Updater Interface with ApplicationUpdater
In building my own custom updater interface, I thought it might be nice to have the same look and feel of the ApplicationUpdaterUI along with some of the more polite features (like postpone update until restart). Building your own updater using the ApplicationUpdater turns out to be a little more involved than I had originally thought. However, when you start attacking a problem you persevere until the end, no matter what the pain.
I did find some available information in the Adobe AIR 1.5 Cookbook, which has a basic custom updater example. This helped with some of the basic stuff, but I still needed a little more detail to make something similar to the ApplicationUpdaterUI. I found another good example by Jens Krause, but this example is for Flex 4. I needed something that would work for Flex 3. So here is what I built based on these examples:
The application consists of one class file and one MXML dialog for displaying all the user interface elements. I borrowed some of the icons from the ApplicationUpdaterUI which is available in the AIR SDK (I hope Adobe doesn’t mind). The update dialog UI is only loaded when needed, all checks for application updates happen using the ApplicationUpdater. The update dialog is only displayed when the user wants to manually update or the periodic update check finds a new application update.
The Good and the Bad
The good news is the updater only consumes about 2MB of RAM when you load the update dialog box, part of that is the ApplicationUpdater itself running in the background. The other good news is that you can yank out or customize any part of the this example to meet your own needs. The bad news is that even though I tried my best to fully remove the update dialog and have garbage collection clean it up, it doesn’t fully unload from memory (go figure). I don’t feel this is a huge issue as the application is most likely restarted after you install an update. Below is the code followed by a zip file containing the full application.
UpdateManager.as
package com.thanksmister
{
import air.update.ApplicationUpdater;
import air.update.events.DownloadErrorEvent;
import air.update.events.StatusFileUpdateErrorEvent;
import air.update.events.StatusFileUpdateEvent;
import air.update.events.StatusUpdateErrorEvent;
import air.update.events.StatusUpdateEvent;
import air.update.events.UpdateEvent;
import flash.desktop.NativeApplication;
import flash.events.ErrorEvent;
import flash.events.Event;
import flash.events.ProgressEvent;
import flash.filesystem.File;
public class UpdateManager
{
private var appUpdater:ApplicationUpdater;
private var appVersion:String;
private var baseURL:String;
private var updaterDialog:UpdaterDialog;
private var configurationFile:File;
private var isFirstRun:String;
private var upateVersion:String;
private var applicationName:String;
private var installedVersion:String;
private var description:String;
private var initializeCheckNow:Boolean = false;
private var isInstallPostponed:Boolean = false;
private var showCheckState:Boolean = true;
/**
* Constructer for UpdateManager Class
*
* @param showCheckState Boolean value to show the Check Now dialog box
* @param initializeCheckNow Boolean value to initialize application and run check on instantiation of the Class
* */
public function UpdateManager(showCheckState:Boolean = true, initializeCheckNow:Boolean = false)
{
this.showCheckState = showCheckState;
this.configurationFile = new File("app:/config/update.xml");
this.initializeCheckNow = initializeCheckNow;
initialize();
}
public function checkNow():void
{
//trace("checkNow");
isInstallPostponed = false;
if(showCheckState) {
createDialog(UpdaterDialog.CHECK_UPDATE);
} else {
appUpdater.checkNow();
}
}
//---------- ApplicationUpdater ----------------//
private function initialize():void
{
//trace("initialize");
if(!appUpdater){
appUpdater = new ApplicationUpdater();
appUpdater.configurationFile = configurationFile;
appUpdater.addEventListener(UpdateEvent.INITIALIZED, updaterInitialized);
appUpdater.addEventListener(StatusUpdateEvent.UPDATE_STATUS, statusUpdate);
appUpdater.addEventListener(UpdateEvent.BEFORE_INSTALL, beforeInstall);
appUpdater.addEventListener(StatusUpdateErrorEvent.UPDATE_ERROR, statusUpdateError);
appUpdater.addEventListener(UpdateEvent.DOWNLOAD_START, downloadStarted);
appUpdater.addEventListener(ProgressEvent.PROGRESS, downloadProgress);
appUpdater.addEventListener(UpdateEvent.DOWNLOAD_COMPLETE, downloadComplete);
appUpdater.addEventListener(DownloadErrorEvent.DOWNLOAD_ERROR, downloadError);
appUpdater.addEventListener(ErrorEvent.ERROR, updaterError);
appUpdater.initialize();
}
}
private function beforeInstall(event:UpdateEvent):void
{
//trace("beforeInstall");
if (isInstallPostponed) {
event.preventDefault();
isInstallPostponed = false;
}
}
private function updaterInitialized(event:UpdateEvent):void
{
//trace("updaterInitialized");
this.isFirstRun = event.target.isFirstRun;
this.applicationName = getApplicationName();
this.installedVersion = getApplicationVersion();
if(showCheckState && initializeCheckNow) {
createDialog(UpdaterDialog.CHECK_UPDATE);
} else if (initializeCheckNow) {
appUpdater.checkNow();
}
}
private function statusUpdate(event:StatusUpdateEvent):void
{
//trace("statusUpdate");
event.preventDefault();
if(event.available){
this.description = getUpdateDescription(event.details);
this.upateVersion = event.version;
if(!showCheckState) {
createDialog(UpdaterDialog.UPDATE_AVAILABLE);
} else if (updaterDialog) {
updaterDialog.applicationName = this.applicationName;
updaterDialog.installedVersion = this.installedVersion;
updaterDialog.upateVersion = this.upateVersion;
updaterDialog.description = this.description
updaterDialog.updateState = UpdaterDialog.UPDATE_AVAILABLE;
}
} else {
if (showCheckState) createDialog(UpdaterDialog.NO_UPDATE);
}
}
private function statusUpdateError(event:StatusUpdateErrorEvent):void
{
event.preventDefault();
if(!updaterDialog){
createDialog(UpdaterDialog.UPDATE_ERROR);
} else {
updaterDialog.updateState = UpdaterDialog.UPDATE_ERROR;
}
}
private function statusFileUpdate(event:StatusFileUpdateEvent):void
{
event.preventDefault();
if(event.available) {
updaterDialog.updateState = UpdaterDialog.UPDATE_DOWNLOADING;
appUpdater.downloadUpdate();
} else {
updaterDialog.updateState = UpdaterDialog.UPDATE_ERROR;
}
}
private function statusFileUpdateError(event:StatusFileUpdateErrorEvent):void
{
event.preventDefault();
updaterDialog.updateState = UpdaterDialog.UPDATE_ERROR;;
}
private function downloadStarted(event:UpdateEvent):void
{
updaterDialog.updateState = UpdaterDialog.UPDATE_DOWNLOADING;
}
private function downloadProgress(event:ProgressEvent):void
{
updaterDialog.updateState = UpdaterDialog.UPDATE_DOWNLOADING;
var num:Number = (event.bytesLoaded/event.bytesTotal)*100;
updaterDialog.downloadProgress(num);
}
private function downloadComplete(event:UpdateEvent):void
{
event.preventDefault(); // prevent default install
updaterDialog.updateState = UpdaterDialog.INSTALL_UPDATE;
}
private function downloadError(event:DownloadErrorEvent):void
{
event.preventDefault();
updaterDialog.updateState = UpdaterDialog.UPDATE_ERROR;
}
private function updaterError(event:ErrorEvent):void
{
updaterDialog.errorText = event.text;
updaterDialog.updateState = UpdaterDialog.UPDATE_ERROR;
}
//---------- UpdaterDialog Events ----------------//
private function createDialog(state:String):void
{
if(!updaterDialog) {
updaterDialog = new UpdaterDialog();
updaterDialog.isFirstRun = this.isFirstRun;
updaterDialog.applicationName = this.applicationName;
updaterDialog.installedVersion = this.installedVersion;
updaterDialog.upateVersion = this.upateVersion;
updaterDialog.updateState = state;
updaterDialog.description = this.description;
updaterDialog.addEventListener(UpdaterDialog.EVENT_CHECK_UPDATE, checkUpdate);
updaterDialog.addEventListener(UpdaterDialog.EVENT_INSTALL_UPDATE, installUpdate);
updaterDialog.addEventListener(UpdaterDialog.EVENT_CANCEL_UPDATE, cancelUpdate);
updaterDialog.addEventListener(UpdaterDialog.EVENT_DOWNLOAD_UPDATE, downloadUpdate);
updaterDialog.addEventListener(UpdaterDialog.EVENT_INSTALL_LATER, installLater);
updaterDialog.open();
}
}
/**
* Check for update.
* */
private function checkUpdate(event:Event):void
{
//trace("checkUpdate");
appUpdater.checkNow();
}
/**
* Install the update.
* */
private function installUpdate(event:Event):void
{
appUpdater.installUpdate();
}
/**
* Install the update.
* */
private function installLater(event:Event):void
{
isInstallPostponed = true;
appUpdater.installUpdate();
destoryUpdater();
}
/**
* Download the update.
* */
private function downloadUpdate(event:Event):void
{
appUpdater.downloadUpdate();
}
/**
* Cancel the update.
* */
private function cancelUpdate(event:Event):void
{
appUpdater.cancelUpdate();
destoryUpdater();
}
//---------- Destroy All ----------------//
private function destroy():void
{
if (appUpdater) {
appUpdater.configurationFile = configurationFile;
appUpdater.removeEventListener(UpdateEvent.INITIALIZED, updaterInitialized);
appUpdater.removeEventListener(StatusUpdateEvent.UPDATE_STATUS, statusUpdate);
appUpdater.removeEventListener(StatusUpdateErrorEvent.UPDATE_ERROR, statusUpdateError);
appUpdater.removeEventListener(UpdateEvent.DOWNLOAD_START, downloadStarted);
appUpdater.removeEventListener(ProgressEvent.PROGRESS, downloadProgress);
appUpdater.removeEventListener(UpdateEvent.DOWNLOAD_COMPLETE, downloadComplete);
appUpdater.removeEventListener(DownloadErrorEvent.DOWNLOAD_ERROR, downloadError);
appUpdater.removeEventListener(UpdateEvent.BEFORE_INSTALL, beforeInstall);
appUpdater.removeEventListener(ErrorEvent.ERROR, updaterError);
appUpdater = null;
}
destoryUpdater();
}
private function destoryUpdater():void
{
if(updaterDialog) {
updaterDialog.destroy();
updaterDialog.removeEventListener(UpdaterDialog.EVENT_CHECK_UPDATE, checkUpdate);
updaterDialog.removeEventListener(UpdaterDialog.EVENT_INSTALL_UPDATE, installUpdate);
updaterDialog.removeEventListener(UpdaterDialog.EVENT_CANCEL_UPDATE, cancelUpdate);
updaterDialog.removeEventListener(UpdaterDialog.EVENT_DOWNLOAD_UPDATE, downloadUpdate);
updaterDialog.removeEventListener(UpdaterDialog.EVENT_INSTALL_LATER, installLater);
updaterDialog.close();
updaterDialog = null;
}
isInstallPostponed = false;
}
//---------- Utilities ----------------//
/**
* Getter method to get the version of the application
* Based on Jens Krause blog post: http://www.websector.de/blog/2009/09/09/custom-applicationupdaterui-for-using-air-updater-framework-in-flex-4/
*
* @return String Version of application
*
*/
private function getApplicationVersion():String
{
var appXML:XML = NativeApplication.nativeApplication.applicationDescriptor;
var ns:Namespace = appXML.namespace();
return appXML.ns::version;
}
/**
* Getter method to get the name of the application file
* Based on Jens Krause blog post: http://www.websector.de/blog/2009/09/09/custom-applicationupdaterui-for-using-air-updater-framework-in-flex-4/
*
* @return String name of application
*
*/
private function getApplicationFileName():String
{
var appXML:XML = NativeApplication.nativeApplication.applicationDescriptor;
var ns:Namespace = appXML.namespace();
return appXML.ns::filename;
}
/**
* Getter method to get the name of the application, this does not support multi-language.
* Based on a method from Adobes ApplicationUpdaterDialogs.mxml, which is part of Adobes AIR Updater Framework
* Also based on Jens Krause blog post: http://www.websector.de/blog/2009/09/09/custom-applicationupdaterui-for-using-air-updater-framework-in-flex-4/
*
* @return String name of application
*
*/
private function getApplicationName():String
{
var applicationName:String;
var xmlNS:Namespace=new Namespace("http://www.w3.org/XML/1998/namespace");
var appXML:XML=NativeApplication.nativeApplication.applicationDescriptor;
var ns:Namespace=appXML.namespace();
// filename is mandatory
var elem:XMLList=appXML.ns::filename;
// use name is if it exists in the application descriptor
if ((appXML.ns::name).length() != 0)
{
elem=appXML.ns::name;
}
// See if element contains simple content
if (elem.hasSimpleContent())
{
applicationName=elem.toString();
}
return applicationName;
}
/**
* Helper method to get release notes, this does not support multi-language.
* Based on a method from Adobes ApplicationUpdaterDialogs.mxml, which is part of Adobes AIR Updater Framework
* Also based on Jens Krause blog post: http://www.websector.de/blog/2009/09/09/custom-applicationupdaterui-for-using-air-updater-framework-in-flex-4/
*
* @param detail Array of details
* @return String Release notes depending on locale chain
*
*/
protected function getUpdateDescription(details:Array):String
{
var text:String="";
if (details.length == 1)
{
text=details[0][1];
}
return text;
}
}
}
UpdateDialog.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:Window xmlns="*" xmlns:mx="http://www.adobe.com/2006/mxml" styleName="updateDialogWindow"
layout="absolute" maximizable="false" resizable="false" currentState="{_updateState}"
clipContent="false" showStatusBar="false" width="530" height="180">
<mx:Metadata>
[Event(name="checkUpdate", type="flash.events.Event")]
[Event(name="downloadUpdate", type="flash.events.Event")]
[Event(name="downloadUpdate", type="flash.events.Event")]
[Event(name="cancelUpdate", type="flash.events.Event")]
</mx:Metadata>
<mx:Script>
<![CDATA[
public static var EVENT_CHECK_UPDATE:String = "checkUpdate";
public static var EVENT_INSTALL_UPDATE:String = "installUpdate";
public static var EVENT_DOWNLOAD_UPDATE:String = "downloadUpdate";
public static var EVENT_CANCEL_UPDATE:String = "cancelUpdate";
public static var EVENT_INSTALL_LATER:String = "installLater";
[Bindable] public static var UPDATE_DOWNLOADING:String = "updateDownloading";
[Bindable] public static var INSTALL_UPDATE:String = "installUpdate";
[Bindable] public static var UPDATE_AVAILABLE:String = "updateAvailable";
[Bindable] public static var NO_UPDATE:String = "noUpdate";
[Bindable] public static var CHECK_UPDATE:String = "checkUpdate";
[Bindable] public static var UPDATE_ERROR:String = "updateError";
[Bindable] private var _isFirstRun:String;
[Bindable] private var _installedVersion:String;
[Bindable] private var _updateVersion:String;
[Bindable] private var _updateDescription:String;
[Bindable] private var _applicationName:String;
[Bindable] private var _updateState:String;
[Bindable] private var _errorText:String = "There was an error checking for updates.";
public function set isFirstRun(value:String):void
{
_isFirstRun = value;
}
public function set installedVersion(value:String):void
{
_installedVersion = value;
}
public function set upateVersion(value:String):void
{
_updateVersion = value;
}
public function set updateState(value:String):void
{
_updateState = value;
}
public function set applicationName(value:String):void
{
_applicationName = value;
}
public function set description(value:String):void
{
_updateDescription = value;
}
public function set errorText(value:String):void
{
_errorText = value;
}
public function downloadProgress(value:Number):void
{
if(progressBar) progressBar.setProgress(value, 100);
}
private function continueUpdate():void
{
if (this.currentState == UpdaterDialog.CHECK_UPDATE){
this.dispatchEvent(new Event(EVENT_CHECK_UPDATE));
} else if (this.currentState == UPDATE_AVAILABLE) {
this.dispatchEvent(new Event(EVENT_DOWNLOAD_UPDATE));
}else if (this.currentState == INSTALL_UPDATE) {
this.dispatchEvent(new Event(EVENT_INSTALL_UPDATE));
}
}
private function cancelUpdate():void
{
if (this.currentState == INSTALL_UPDATE) {
this.dispatchEvent(new Event(EVENT_INSTALL_LATER));
return;
}
this.dispatchEvent(new Event(EVENT_CANCEL_UPDATE));
}
public function destroy():void
{
iconImage.unloadAndStop(true);
iconImage.source = null;
continueButton.removeEventListener(MouseEvent.CLICK, continueUpdate);
cancelButton.removeEventListener(MouseEvent.CLICK, cancelUpdate);
// becaause we used skins, we have to clear them for garbage collection
//http://www.firstrowria.com/2009/01/flex-top-5-memory-leaks-in-flex-2-skinning-of-components-eg-button/
continueButton.styleName = null;
cancelButton.styleName = null;
while(this.numChildren > 0){
this.removeChildAt(0);
}
}
]]>
</mx:Script>
<mx:states>
<mx:State name="{CHECK_UPDATE}">
<mx:AddChild position="lastChild">
<mx:Label x="152" y="86" text="Application:" styleName="updateDialogLabel"/>
</mx:AddChild>
<mx:AddChild position="lastChild">
<mx:Text x="230" y="86" text="{this._applicationName}" styleName="updateDialogText"/>
</mx:AddChild>
<mx:AddChild position="lastChild">
<mx:Label x="107" y="19" text="Check for updates" styleName="updateTitle" />
</mx:AddChild>
<mx:AddChild position="lastChild">
<mx:Text x="107" y="50" text="Allow the application to check for updates?" styleName="updateDialogText"/>
</mx:AddChild>
<mx:SetProperty name="height" value="180"/>
</mx:State>
<mx:State name="{UPDATE_AVAILABLE}">
<mx:SetProperty name="height" value="360"/>
<mx:AddChild position="lastChild">
<mx:Text text="{this._installedVersion}" x="230" y="114" styleName="updateDialogText"/>
</mx:AddChild>
<mx:AddChild position="lastChild">
<mx:Text text="{this._updateVersion}" x="230" y="134" styleName="updateDialogText"/>
</mx:AddChild>
<mx:AddChild position="lastChild">
<mx:Label x="118" y="114" text="Installed Version:" styleName="updateDialogLabel"/>
</mx:AddChild>
<mx:AddChild position="lastChild">
<mx:Label x="127" y="134" text="Update Version:" styleName="updateDialogLabel"/>
</mx:AddChild>
<mx:AddChild position="lastChild">
<mx:Label x="152" y="96" text="Application:" styleName="updateDialogLabel"/>
</mx:AddChild>
<mx:AddChild position="lastChild">
<mx:Text x="230" y="96" text="{this._applicationName}" styleName="updateDialogText"/>
</mx:AddChild>
<mx:AddChild position="lastChild">
<mx:Label x="107" y="19" text="Update Available" styleName="updateTitle"/>
</mx:AddChild>
<mx:AddChild position="lastChild">
<mx:Text x="107" y="50" text="An updated version of the application is available for download." styleName="updateDialogText"/>
</mx:AddChild>
<mx:AddChild position="lastChild">
<mx:Label id="releaseLabel" x="10" y="222" text="Release notes" styleName="updateDialogLabel"/>
</mx:AddChild>
<mx:AddChild position="lastChild">
<mx:TextArea text="{_updateDescription}" x="10" y="248" width="508" height="100" styleName="updateDialogTextArea"/>
</mx:AddChild>
<mx:AddChild position="lastChild">
<mx:HRule x="10" y="214" width="508" styleName="updateDialogHRule"/>
</mx:AddChild>
<mx:SetProperty target="{cancelButton}" name="y" value="164"/>
<mx:SetProperty target="{continueButton}" name="y" value="164"/>
<mx:SetProperty target="{cancelButton}" name="label" value="Download later"/>
<mx:SetProperty target="{continueButton}" name="x" value="269"/>
<mx:SetProperty target="{continueButton}" name="label" value="Download now"/>
</mx:State>
<mx:State name="{NO_UPDATE}">
<mx:AddChild position="lastChild">
<mx:Label x="122" y="86" text="Application:" styleName="updateDialogLabel"/>
</mx:AddChild>
<mx:AddChild position="lastChild">
<mx:Text x="200" y="86" text="{this._applicationName}" styleName="updateDialogText"/>
</mx:AddChild>
<mx:AddChild position="lastChild">
<mx:Label x="107" y="19" text="No Updates" styleName="updateTitle"/>
</mx:AddChild>
<mx:AddChild position="lastChild">
<mx:Text x="107" y="50" text="There is no application update available at this time." styleName="updateDialogText"/>
</mx:AddChild>
<mx:RemoveChild target="{continueButton}"/>
<mx:SetProperty target="{cancelButton}" name="label" value="Close"/>
<mx:SetProperty name="height" value="180"/>
</mx:State>
<mx:State name="{UPDATE_DOWNLOADING}">
<mx:AddChild position="lastChild">
<mx:ProgressBar x="107" y="84" width="411" label=" " id="progressBar" mode="manual" height="15" styleName="updateDiallogProgress"/>
</mx:AddChild>
<mx:AddChild position="lastChild">
<mx:Text x="107" y="20" text="Downloading Update" styleName="updateTitle"/>
</mx:AddChild>
<mx:RemoveChild target="{continueButton}"/>
<mx:AddChild position="lastChild">
<mx:Label x="107" y="53" text="Download progress..." styleName="updateDialogLabel"/>
</mx:AddChild>
<mx:SetProperty name="height" value="180"/>
</mx:State>
<mx:State name="{UPDATE_ERROR}">
<mx:AddChild position="lastChild">
<mx:Label x="107" y="19" text="Unexpected error" styleName="updateTitle"/>
</mx:AddChild>
<mx:AddChild position="lastChild">
<mx:Label x="107" y="50" text="{_errorText}" styleName="updateDialogLabel"/>
</mx:AddChild>
<mx:RemoveChild target="{continueButton}"/>
<mx:SetProperty target="{cancelButton}" name="label" value="Close"/>
<mx:SetProperty name="height" value="180"/>
</mx:State>
<mx:State name="{INSTALL_UPDATE}">
<mx:AddChild position="lastChild">
<mx:Text text="{this._installedVersion}" x="230" y="139" styleName="updateDialogText"/>
</mx:AddChild>
<mx:AddChild position="lastChild">
<mx:Text text="{this._updateVersion}" x="230" y="159" styleName="updateDialogText"/>
</mx:AddChild>
<mx:AddChild position="lastChild">
<mx:Label x="118" y="139" text="Installed Version:" styleName="updateDialogLabel"/>
</mx:AddChild>
<mx:AddChild position="lastChild">
<mx:Label x="127" y="159" text="Update Version:" styleName="updateDialogLabel"/>
</mx:AddChild>
<mx:AddChild position="lastChild">
<mx:Label x="152" y="121" text="Application:" styleName="updateDialogLabel"/>
</mx:AddChild>
<mx:AddChild position="lastChild">
<mx:Text x="230" y="121" text="{this._applicationName}" styleName="updateDialogText"/>
</mx:AddChild>
<mx:AddChild position="lastChild">
<mx:Label x="107" y="19" text="Install update" id="windowTitle4" styleName="updateTitle"/>
</mx:AddChild>
<mx:AddChild position="lastChild">
<mx:Text x="107" y="50" text="The update for the application is downloaded and ready to be installed." styleName="updateDialogText"/>
</mx:AddChild>
<mx:AddChild position="lastChild">
<mx:Label x="10" y="262" text="Release notes" styleName="updateDialogLabel"/>
</mx:AddChild>
<mx:AddChild position="lastChild">
<mx:TextArea id="relaeseNotesTextArea0" text="{_updateDescription}" x="10" y="288" width="508" height="100" styleName="updateDialogTextArea"/>
</mx:AddChild>
<mx:SetProperty name="height" value="402"/>
<mx:AddChild position="lastChild">
<mx:ProgressBar id="installProgressBar" x="107" y="84" width="411" label=" " mode="manual" height="15" styleName="installDiallogProgress" creationComplete="installProgressBar.setProgress(100,100)"/>
</mx:AddChild>
<mx:AddChild position="lastChild">
<mx:HRule x="10" y="249" width="508" styleName="updateDialogHRule"/>
</mx:AddChild>
<mx:SetProperty target="{cancelButton}" name="label" value="Postpone until restart"/>
<mx:SetProperty target="{cancelButton}" name="y" value="198"/>
<mx:SetProperty target="{continueButton}" name="y" value="198"/>
<mx:SetProperty target="{continueButton}" name="x" value="320"/>
<mx:SetProperty target="{continueButton}" name="label" value="Install update"/>
</mx:State>
</mx:states>
<mx:Button x="107" y="129" label="Cancel" id="cancelButton" click="cancelUpdate()" height="34" styleName="updateDialogButton"/>
<mx:Button x="202" y="129" label="Check for Updates" id="continueButton" click="continueUpdate()" height="34" styleName="updateDialogButton"/>
<mx:Image source="@Embed('/assets/images/UpdateIcon.png')" x="15" y="25" width="81" height="74" id="iconImage"/>
</mx:Window>
Main.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"
layout="vertical" width="428" height="280" creationComplete="init()">
<mx:Style source="assets/styles.css"/>
<mx:Script>
<![CDATA[
import com.thanksmister.UpdateManager;
import mx.rpc.events.ResultEvent;
private var updater:UpdateManager;
[Bindable] private var baseURL:String;
[Bindable] private var updates:String;
private function init():void
{
configService.send();
}
private function handleResult(event:ResultEvent):void
{
var xml:XML = event.result as XML;
baseURL = xml..baseurl.toString();
updates = xml..updates.toString();
if(updates)
updater = new UpdateManager(true, false);
}
]]>
</mx:Script>
<mx:HTTPService id="configService" method="GET" resultFormat="e4x" url="config/configuration.xml" result="handleResult(event)" />
<mx:Text fontSize="14" color="0xFFFFFFF" text="Update tester. Please click the button below to begin update checking or wait for the dealy time (3 min). Once the application is successfully updated, the application version will be updated from v1 to v2." x="10" y="10" width="342" height="104"/>
<mx:Button label="Begin update process" color="0xFFFFFFF" x="125" y="104" click="updater.checkNow()"/>
<mx:Label id="versionText" fontSize="14" color="0xFFFFFFF"/>
<mx:Label text="{'Base URL: ' + baseURL}" color="0xFFFFFFF"/>
<mx:Label text="{'Updates: ' + updates}" color="0xFFFFFFF"/>
</mx:WindowedApplication>
I packed up the Flex project. You would use this basically the same as you would use the ApplicationUpdaterUI framework. You need to create an update version of your AIR file and place it and update.xm file on a server. You can test it from the local IDE if you replace the server url with “app:/”. Just remember you can not actually update an AIR application from the IDE, it has to be installed and running on your machine to update, and the update files have to be accessible just like when you use the ApplicationUpdaterUI.
The code is free to use, hose, rip apart, just post back any fixes or enhancements you make.
Source Files
Update
Don’t forget to add a closing event handler to the dialog box that calls the destory() function. This was not in the original code and there needs to be a way to handle users clicking on the close box of the Window for the update dialog user interface. I also added a change that if you don’t want to show the check for update now option, the “no update available” dialog does not appear either, here is the updated code for statusUpdate function in UpdateManager.as (not in the downloaded zip):
private function statusUpdate(event:StatusUpdateEvent):void
{
//trace("statusUpdate");
event.preventDefault();
if(event.available){
this.description = getUpdateDescription(event.details);
this.upateVersion = event.version;
if(!showCheckState) {
createDialog(UpdaterDialog.UPDATE_AVAILABLE);
} else if (updaterDialog) {
updaterDialog.applicationName = this.applicationName;
updaterDialog.installedVersion = this.installedVersion;
updaterDialog.upateVersion = this.upateVersion;
updaterDialog.description = this.description
updaterDialog.updateState = UpdaterDialog.UPDATE_AVAILABLE;
}
} else {
if (showCheckState) createDialog(UpdaterDialog.NO_UPDATE);
}
}
-Mister









