Wednesday, September 07, 2011
Adobe MAX 2010: Performance Tips and Tricks for Flex and Flash Development
Saturday, August 20, 2011
Intro to Spark Containers
- Types of Spark Containers
- Types of IVisualElements
- Group Basics
- The multi-faces of DOM
- Lesser known container features
- Internals of Group and SkinnableContainer
- Performance considerations
I'm moving
Saturday, June 18, 2011
Binding Dependencies
spark.components.VideoPlayer, volume is dependent on the videoDisplay.volume property. Usually to make such a property bindable, you add an event listener to know when your dependent property changes. In this case, if you check out VideoPlayer.partAdded(), you can see code like that. However, when I started using the presentation model pattern, this issue seemed to keep coming up quite frequently.
public class Order
{
[Bindable]
public var billingState:String;
[Bindable]
public var shippingState:String;
}
<s:Form>
<s:FormItem label="Billing State">
<s:DropDownList selectedItem="@{ presentationModel.order.billingState }">
<s:ArrayList>
<fx:String>AL</fx:String>
<fx:String>AS</fx:String>
<fx:String>AZ</fx:String>
<fx:String>AR</fx:String>
<fx:String>CA</fx:String>
<fx:String>CO</fx:String>
<fx:String>CT</fx:String>
<fx:String>DE</fx:String>
</s:ArrayList>
</s:DropDownList>
</s:FormItem>
<s:FormItem label="Shipping State">
<s:DropDownList selectedItem="@{ presentationModel.order.shippingState }">
<s:ArrayList>
<fx:String>AL</fx:String>
<fx:String>AS</fx:String>
<fx:String>AZ</fx:String>
<fx:String>AR</fx:String>
<fx:String>CA</fx:String>
<fx:String>CO</fx:String>
<fx:String>CT</fx:String>
<fx:String>DE</fx:String>
</s:ArrayList>
</s:DropDownList>
</s:FormItem>
<s:FormItem label="California Question1"
includeInLayout="{ presentationModel.order.shippingState == 'CA' && presentationModel.order.billingState == 'CA' }"
visible="{ presentationModel.order.shippingState == 'CA' && presentationModel.order.billingState == 'CA' }">
<s:TextInput />
</s:FormItem>
<s:FormItem label="California Question2"
includeInLayout="{ presentationModel.order.shippingState == 'CA' && presentationModel.order.billingState == 'CA' }"
visible="{ presentationModel.order.shippingState == 'CA' && presentationModel.order.billingState == 'CA' }">
<s:TextInput />
</s:FormItem>
</s:Form>
presentationModel.order.shippingState == 'CA' && presentationModel.order.billingState == 'CA' is repeated 4 times, the ampersands have to be escaped, and overall it's just quite ugly. Now, one of the neat tricks I've learned is that you can spruce it up by doing something like:
<fx:Declarations>
<fx:Boolean id="showCaliforniaQuestions">
{presentationModel.order.shippingState == "CA" &&
presentationModel.order.billingState == "CA"}
</fx:Boolean>
</fx:Declarations>
...
<s:FormItem label="California Question1"
includeInLayout="{ showCaliforniaQuestions }"
visible="{ showCaliforniaQuestions }">
<s:TextInput />
</s:FormItem>
<s:FormItem label="California Question2"
includeInLayout="{ showCaliforniaQuestions }"
visible="{ showCaliforniaQuestions }">
<s:TextInput />
</s:FormItem>
<s:FormItem label="California Question1"
includeInLayout="{ presentationModel.showCaliforniaQuestions }"
visible="{ presentationModel.showCaliforniaQuestions }">
<s:TextInput />
</s:FormItem>
<s:FormItem label="California Question2"
includeInLayout="{ presentationModel.showCaliforniaQuestions }"
visible="{ presentationModel.showCaliforniaQuestions }">
<s:TextInput />
</s:FormItem>
public class MyPM
{
public function MyPM()
{
super();
}
[Bindable]
public var order:Order = new Order();
public function get showCaliforniaQuestions():Boolean
{
return order.shippingState == "CA" && order.billingState == "CA";
}
}
showCaliforniaQuestions isn't Bindable. Now, to make it Bindable, we need to add Bindable metadata on top, add event listeners (or use a BindingWatcher) to let us know when the shipping state or the billing state changes, and dispatch a new binding event when we get notified that the shipping or billing state changes. This turns out to be quite a lot of extra, ugly code, which means most people just end up keeping this logic in the View because practically it's just too much work and too ugly to move it to the Presentation Model. This is all fine, but this kept issue kept cropping up in practice, so I finally sat down to come up with a more palatable option.
[Bindable(event="showCaliforniaQuestionsChanged",dependentProperty="order.shippingState")]
[Bindable(event="showCaliforniaQuestionsChanged",dependentProperty="order.billingState")]
public function get showCaliforniaQuestions():Boolean
{
return order.shippingState == "CA" && order.billingState == "CA";
}
showCaliforniaQuestions as Bindable, where that binding is dependent on the order.shippingState and order.billingState properties. The Parsley metadata processor will step in and handle everything else. I personally like this solution a lot as it piggy-backs off of the Flex SDK framework's Bindable metadata and just extends it for our purpose. It's easy to use and pushes our logic into Presentation Model, which makes our code cleaner and more importantly, testable.Thursday, June 09, 2011
Why Presentation Models can be confusing
- Separate out the state and behavior of the view from the presentation of the View itself
- Provide a GUI agnostic, testable interface
- Provide a consisten, performant pattern that developers can easily follow
- The View should contain very little logic--only GUI specific, layout implementation should be contained in the View
- The View should use databinding to grab information from the Presentatio Model
- The View should call methods directly into the PM (see FAQ for description of right level of abstraction)
- The View should attach Flash event listeners on the PM to receive any event information (if using Parsley, the View should not communicate to anyone using Parsley messaging as configuring a display object in Parsley is a huge performance hit)
- The PM should hold the logic, state, and data of the user interface. It is essentially an abstract, non-GUI dependent abstraction of a View
- The PM controls what the View displays (how to display it is up to the View)
- The View calls into the PM via public methods. The PM then takes those actions and talks to the Domain Layer.
- For the PM to talk to the View, it should use properties with data-binding or Flash events.
- <s:Button id="submitButton" click="{ myPM.submitButton_clickHandler }" />
- <s:Button id="submitButton" click="myPM.onSubmitButton(busIdTI.text)" />
- <s:Button id="submitButton" click="myPM.setBusId(parseInt(busIdTI.text))" />
- <s:Button id="submitButton" click="myPM.unsubscribe(); myPM.setCurrentBus(new Bus(parseInt(busIdTI.text))); myPM.subscribe();" />
- http://martinfowler.com/eaaDev/PresentationModel.html
- http://blogs.adobe.com/paulw/archives/2007/10/presentation_pa_3.html
- http://blogs.msdn.com/b/erwinvandervalk/archive/2009/08/14/the-difference-between-model-view-viewmodel-and-other-separated-presentation-patterns.aspx
- http://artinflex.blogspot.com/2010/11/presentation-model-implemented-in.html
- http://www.spicefactory.org/parsley/docs/2.2/manual/mvc.php
- http://blogs.adobe.com/tomsugden/2009/08/applying_the_presentation_mode.html
Monday, February 22, 2010
When to use what Spark Container
As always, I'm disappointed with the amount I blog. One strategy I'm going to take is to take questions asked on forums that I think are interesting and provide an answer. So, for this first question comes from a forum post.
This gets me really confused on which container i should use with my project.
For example, what's the difference between:
<s:Group>
<s:layout>
<s:HorizontalLayout />
</s:layout>
</s:Group>
...and...
<s:HGroup>
</s:HGroup>
...or even...
<mx:HBox>
</mx:HBox>
I'm sorry if i'm saying something wrong but it makes me waste so much time thinking on which one i should use cause, for me, all of them do exactly the same.
One other thing is if i need to do a container with a background color. What should i use?
<mx:Canvas backgroundcolor="{color}">
<MyComponents />
</mx:Canvas>
...or...
<s:Group>
<s:Rect>
<s:fill>
<s:SolidColor color="{color}" />
</s:fill>
</s:Rect>
<MyComponents />
</s:Group>
Can anyone tell me which one is the best pratice now for Flex 4?
Answer:
We hope you can use the new Spark components whenever possible. Group is the basic, chromeless container. SkinnableContainer is our basic container with chrome (for instance Panel extends SkinnableContainer). One of the cool new things you can do in spark is swap layouts. For instance, you can use a HorizontalLayout with a Group, or you can even create a custom layout and use it with your List. This is really powerful, and this separation between a container and the layout make a lot of things easier, That said, for really common use-cases, we found some people were annoyed with having to type:
<s:Group>
<s:layout>
<s:VerticalLayout horizontAlign=".." />
</s:layout>
...
</s:Group>
And people were used to having HBox and VBox. Because of this, we created helper classes, HGroup and VGroup, which extend Group and have the layout object baked in. They don't really add any functionality...they are just there as a shortcut. To make it easier we also proxy up some layout-dependent properties on to the container, like horizontalAlign and verticalAlign.
In summary:
HBox, VBox, and Canvas are replaced by one component, Group. However their functionality is really replaced by HorizontalLayout, VerticalLayout, and BasicLayout, respectively. We have helper objects HGroup and VGroup to make common tasks easier. If you want a skinnable form of those components (something with chrome), use SkinnableContainer and change the layout (we did not provide helper classes HSkinnableContainer and VSkinnableContainer because we don't think it's as common of a use-case).
As a side note, I think you should always switch to using these new Spark container classes whenever possible. We think they are more flexible because of swappable layouts, and they also support GraphicElements directly, while MX containers do not. In addition, the Spark classes are made to be lighter-weight and more performant. For instance, they don't have scrollbars built in, and they are chromeless. An exception is if you used Canvas and advanced constraints before. We haven't created an AdvancedConstraint layout yet.
As per the backgroundColor part of your question, you could composite it together like you did in the Group:
<s:Group>
<s:Rect>
<s:fill>
<s:SolidColor color="{color}" />
</s:fill>
</s:Rect>
<MyComponents />
</s:Group>
However, ideally, I'd tell you to use SkinnableContainer and place the Rectangle in the SkinnableContainer's skin. This way you're segregating the content of the component from the look of the component. This is part of the "container with chrome (SkinnableContainer) vs. container with no chrome (Group)". As we started out down the Spark architecture path, we really pushed hard for people to use this new skinning model, and in this case, we would push people to put the <s:Rect> in to the skin, but in balancing our "theoretical ideal of the skin vs. the component" and the usability of the framework, we received feedback that for common situations we need to provide styles. Because of this, our default SkinnableContainer skin already has a background rectangle in it, and you can control its color with the backgroundColor style, like:
<s:SkinnableContainer backgroundcolor="red">
...
</s:SkinnableContainer>
We baked this background and backgroundColor style in to the SkinnableContainer component for ease-of-use, so you don't have to create a skin just for simple changes. However, if you want something really complex (like a rounded rectangle as the background, then you should re-skin the SkinnableContainer).
Hope this helps answer a lot of questions.
-Ryan
Friday, March 27, 2009
BugQuash
Sunday, December 14, 2008
Flex Videos on your iPod
Wednesday, November 19, 2008
MAX 2008: Item Renderer Talk
Monday, November 17, 2008
MAX 2008
Tuesday, October 14, 2008
SWCs, Modules, and RSLs
Monday, July 21, 2008
Creating a Frameworkless MXML File Part 2 (Having Children)
package mx.core
{
/**
* The IMXMLObject interface defines the APIs that a non-visual component
* must implement in order to work properly with the MXML compiler.
* Currently, the only supported method is the initialized()
* method.
*/
public interface IMXMLObject
{
//--------------------------------------------------------------------------
// Methods
//--------------------------------------------------------------------------
/**
* Called after the implementing object has been created and all
* component properties specified on the MXML tag have been initialized.
*
* @param document The MXML document that created this object.
*
* @param id The identifier used by document to refer
* to this object.
* If the object is a deep property on document,
* id is null.
*/
function initialized(document:Object, id:String):void;
}
}
If your child object implements this interface, the MXML compiler will generate code to automatically call this method. It’s a pretty limited way to add children because it only has two properties, document and id. It actually doesn’t tell you who your parent is--just your document, which is your top-level root node object (usually Application).
Sticking with stupid-simple examples, I’m just going to draw some ellipses and rectangles. The code for drawing shapes has been ripped off of my co-workers, Chet Haase, and modified to make it as simple as possible and fit the example.
<?xml version="1.0" encoding="utf-8"?>
<components:MyContainer
xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:components="components.*"
xmlns:shapes="components.shapes.*">
<shapes:Rect color="0xFF0000" startX="20" startY="20" endX="100" endY="30" />
<shapes:Ellipse color="0x00FF00" startX="20" startY="120" endX="100" endY="130" />
</components:MyContainer>
Rather than post all the code here, I’ll just post some snippets (full code is in two flex builder projects: IMXMLObject example and the Default property example). The interesting part is ArtShape, which is the base class for all shapes. That class is the one that implements IMXMLObject, and all it does is:
public function initialized(document:Object, id:String):void
{
document.addChild(this);
renderShape();
}
So basically this initialized method just adds ourselves to the top-level application and calls a method that Chet had called renderShape(). The ArtShape version of that method is empty, but Rectangle’s looks like:
/**
* Draws a rectangle into this shape's display list
*/
override protected function renderShape():void
{
if (filled) {
graphics.beginFill(color);
} else {
graphics.lineStyle(strokeWidth, color);
}
graphics.drawRect(startX, startY, endX - startX, endY - startY);
}
Looking at the generated code, you’ll see what’s going on behind the scenes with our MXML.
package
{
// begin class def
public class FrameworklessChildren
extends components.MyContainer
{
// instance variables
/**
* @private
**/
public var _FrameworklessChildren_Ellipse1 : components.shapes.Ellipse;
/**
* @private
**/
public var _FrameworklessChildren_Rect1 : components.shapes.Rect;
// type-import dummies
// constructor (non-Flex display object)
/**
* @private
**/
public function FrameworklessChildren()
{
super();
// our style settings
// properties
_FrameworklessChildren_Ellipse1_i();
_FrameworklessChildren_Rect1_i();
// events
}
// scripts
// end scripts
// supporting function definitions for properties, events, styles, effects
private function _FrameworklessChildren_Ellipse1_i() : components.shapes.Ellipse
{
var temp : components.shapes.Ellipse = new components.shapes.Ellipse();
_FrameworklessChildren_Ellipse1 = temp;
temp.color = 65280;
temp.startX = 20;
temp.startY = 120;
temp.endX = 100;
temp.endY = 130;
temp.initialized(this, "_FrameworklessChildren_Ellipse1")
return temp;
}
private function _FrameworklessChildren_Rect1_i() : components.shapes.Rect
{
var temp : components.shapes.Rect = new components.shapes.Rect();
_FrameworklessChildren_Rect1 = temp;
temp.color = 16711680;
temp.startX = 20;
temp.startY = 20;
temp.endX = 100;
temp.endY = 30;
temp.initialized(this, "_FrameworklessChildren_Rect1")
return temp;
}
// embed carrier vars
// end embed carrier vars
// end class def
}
// end package def
}
So basically to create the children it calls the methods in the constructor for the top-level root class. In those methods, the child object gets created, its properties get set, and the initialized method is called. The full code for the above example can be found here.
However, as I said before, this doesn’t really support nesting--these objects are essentially just direct children of the root object. So to support this, we need to use default properties. This is actually how the majority of objects will work in Flex 4 (Flex 3 essentially uses something else entirely which is built around UIComponents and UIComponentDescriptors).
Anyways, here’s our sample MXML file that we want to support:
<?xml version="1.0" encoding="utf-8"?>
<components:MyContainer
xmlns="http://ns.adobe.com/mxml/2009"
xmlns:components="components.*"
xmlns:shapes="components.shapes.*">
<shapes:Rect color="0xFF0000" startX="20" startY="20" endX="100" endY="30" />
<shapes:Ellipse color="0x00FF00" startX="20" startY="120" endX="100" endY="130" />
<components:MyContainer x="100" y="100">
<shapes:Rect color="0xFF0000" startX="20" startY="20" endX="100" endY="30" />
<shapes:Ellipse color="0x00FF00" startX="20" startY="120" endX="100" endY="130" />
</components:MyContainer>
</components:MyContainer>
You see I’ve added another container that has children. Another important thing to note is that I changed the compiler namespace to use the new 2009 namespace (so you’ll need the new compiler to take advantage of this). This isn’t actually needed, and I’ll show you what it looks like with just the 2006 namespace later on.
The basic idea of default properties is that every MXML object has a "default property." In our case, MyContainer will have a default property of "content." This is basically the property where the children go. Instead of the MXML code above, I could’ve used:
<?xml version="1.0" encoding="utf-8"?>
<components:MyContainer
xmlns="http://www.adobe.com/2006/mxml"
xmlns:components="components.*"
xmlns:shapes="components.shapes.*">
<components:content>
<shapes:Rect color="0xFF0000" startX="20" startY="20" endX="100" endY="30" />
<shapes:Ellipse color="0x00FF00" startX="20" startY="120" endX="100" endY="130" />
<components:MyContainer x="100" y="100">
<components:content>
<shapes:Rect color="0xFF0000" startX="20" startY="20" endX="100" endY="30" />
<shapes:Ellipse color="0x00FF00" startX="20" startY="120" endX="100" endY="130" />
</components:content>
</components:MyContainer>
</components:content>
</components:MyContainer>
The difference here is that when I instantiate MyContainer, I'm explicitly saying "fill the content property with these objects". However, rather than having to explicitly say that the objects should be stuffed into the content property, I can tell MyContainer to have a default property of content. There was a bug in the MXML compiler in how it handled top-level default properties (only top-level ones), and that’s why I had to use the 2009 namespace to use it; however, if you explicitly specify the property, you can use the old compiler and the 2006 namespace.
So let’s see how we modified MyContainer to make this happen:
package components
{
import flash.display.DisplayObjectContainer;
import flash.display.Sprite;
[DefaultProperty("content")]
public class MyContainer extends Sprite
{
public function MyContainer()
{
}
private var _content:*;
public function get content():*
{
return _content;
}
public function set content(value:*):void
{
_content = value;
if (_content is Array)
{
for (var i:int = 0; i < _content.length; i++)
{
_content[i].initializeMe(this);
}
}
else
{
_content.initializeMe(this);
}
}
public function initializeMe(parent:DisplayObjectContainer):void
{
parent.addChild(this);
}
}
}
With the metadata, we defined MyContainer’s default property to "content." Then I just filled in some basic getters/setters for the property. Because content could be a single item or an Array of items, I special cased some logic in there. Basically all I did was loop through all the content and call initializeMe(this) on all the children. That’s just a method I made up, and I probably should create an interface for it. You can see I implemented initializeMe(parent:DisplayObjectContainer) in here as well, and all it does is call addChild. In the case of ArtBoard, it’s pretty similar:
public function initializeMe(parent:DisplayObjectContainer):void
{
parent.addChild(this);
renderShape();
}
So to complete the picture of how all this works, let’s take a look at the generated code for our MXML class (the first one which uses default properties):
package
{
// begin class def
public class FrameworklessChildren
extends components.MyContainer
{
// instance variables
// type-import dummies
// constructor (non-Flex display object)
/**
* @private
**/
public function FrameworklessChildren()
{
super();
// our style settings
// properties
this.content = [_FrameworklessChildren_Rect1_c(), _FrameworklessChildren_Ellipse1_c(), _FrameworklessChildren_MyContainer2_c()];
// events
}
// scripts
// end scripts
// supporting function definitions for properties, events, styles, effects
private function _FrameworklessChildren_Rect1_c() : components.shapes.Rect
{
var temp : components.shapes.Rect = new components.shapes.Rect();
temp.color = 16711680;
temp.startX = 20;
temp.startY = 20;
temp.endX = 100;
temp.endY = 30;
return temp;
}
private function _FrameworklessChildren_Ellipse1_c() : components.shapes.Ellipse
{
var temp : components.shapes.Ellipse = new components.shapes.Ellipse();
temp.color = 65280;
temp.startX = 20;
temp.startY = 120;
temp.endX = 100;
temp.endY = 130;
return temp;
}
private function _FrameworklessChildren_MyContainer2_c() : components.MyContainer
{
var temp : components.MyContainer = new components.MyContainer();
temp.x = 100;
temp.y = 100;
temp.content = [_FrameworklessChildren_Rect2_c(), _FrameworklessChildren_Ellipse2_c()];
return temp;
}
private function _FrameworklessChildren_Rect2_c() : components.shapes.Rect
{
var temp : components.shapes.Rect = new components.shapes.Rect();
temp.color = 16711680;
temp.startX = 20;
temp.startY = 20;
temp.endX = 100;
temp.endY = 30;
return temp;
}
private function _FrameworklessChildren_Ellipse2_c() : components.shapes.Ellipse
{
var temp : components.shapes.Ellipse = new components.shapes.Ellipse();
temp.color = 65280;
temp.startX = 20;
temp.startY = 120;
temp.endX = 100;
temp.endY = 130;
return temp;
}
// embed carrier vars
// end embed carrier vars
// end class def
}
// end package def
}
You can see it’s basically the same as the one using IMXMLObject; however, rather than calling the initialized() method for us, it just sets the content property. You can download the fill code for this example here.
One thing that might be worth checking out is how this class gets generated in the first place. In the compiler, we have a few velocity templates that we use (for style sheets, MXML classes, binding, etc...). The one for the MXML->ActionScript is called ClassDef.vm (a lot of the methods used in that class exist in ClassDefLib.vm). If you’re interested in a lot of this stuff, definitely download the compiler code and take a look at these files. They’re pretty simple to follow and easy to modify if you want to start mucking around with it.
So that’s basically it for my short trip into compiler-land. If you’ve got any questions, let me know!Thursday, July 10, 2008
Creating a Frameworkless MXML File
package
{
import flash.display.Sprite;
import flash.events.TimerEvent;
import flash.text.TextField;
import flash.utils.Timer;
public class SimpleDigitalClock extends Sprite
{
public function SimpleDigitalClock()
{
super();
time = new TextField();
addChild(time);
updateTime();
timer = new Timer(1000);
timer.addEventListener(TimerEvent.TIMER, updateTime);
timer.start();
}
private var time:TextField;
private var timer:Timer;
private function updateTime(event:TimerEvent = null):void
{
time.text = new Date().toString();
}
}
}
As mentioned before I extend Sprite. In the constructor I create a new TextField and add it as a child. Then I call updateTime, which grabs the current time and puts it into our TextField. Lastly, I just create a Timer which runs every second to update the time so it stays current.
So my FlexBuilder project looks like this (just two files):
Now for the cool part. Let’s say I wanted to make this my Application. Well it’s actually really easy, just change your main MXML file to look like this:
<?xml version="1.0" encoding="utf-8"?> <local:SimpleDigialClock xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:local="*"> </local:SimpleDigitalClock>We can run this file, and we get this beauty: Some points to note:
- We kept the mx namespace because the compiler needs this info, even though no components are instantiated from the mx namespace (hopefully I’ll talk about how this is done in another blog).
- xmlns:local=”*” creates a new namespace, called local, which maps to actionscript files in my current directory.
- All we need to do is tell the MXML compiler to instantiate our Sprite object, SimpleDigitalClock, as the root object for this SWF.
/**
* Generated by mxmlc 4.0
*
* Package:
* Class: FrameworkLessClock
* Source: C:\Documents and Settings\rfrishbe\My Documents\Gumbo-FP10-MyBranch\FrameworkLessClock\src\FrameworkLessClock.mxml
* Template: flex2/compiler/mxml/gen/ClassDef.vm
* Time: 2008.07.10 18:37:50 PDT
*/
package
{
import SimpleDigitalClock;
import flash.accessibility.*;
import flash.debugger.*;
import flash.display.*;
import flash.errors.*;
import flash.events.*;
import flash.external.*;
import flash.filters.*;
import flash.geom.*;
import flash.media.*;
import flash.net.*;
import flash.printing.*;
import flash.profiler.*;
import flash.system.*;
import flash.text.*;
import flash.ui.*;
import flash.utils.*;
import flash.xml.*;
import mx.binding.*;
import mx.core.ClassFactory;
import mx.core.DeferredInstanceFromClass;
import mx.core.DeferredInstanceFromFunction;
import mx.core.IDeferredInstance;
import mx.core.IFactory;
import mx.core.IPropertyChangeNotifier;
import mx.core.mx_internal;
import mx.styles.*;
// begin class def
public class FrameworkLessClock
extends SimpleDigitalClock
{
// instance variables
// type-import dummies
// constructor (non-Flex display object)
/**
* @private
**/
public function FrameworkLessClock()
{
super();
// our style settings
// properties
// events
}
// scripts
// end scripts
// supporting function definitions for properties, events, styles, effects
// embed carrier vars
// end embed carrier vars
// end class def
}
// end package def
}
So you see not much happens. There’s a lot of comments in there for some framework stuff, but not much happens in our case because we don’t need that stuff. Let’s say you add event-listeners or properties to your SimpleDigitalClock. Those will get genned in here. So for a simple example, let’s create a property called timeZoneOffset, which will be the timezone offset (in minutes) that we want to change our clock to. I won’t go into the details of the math, but I think it works out… Also, let’s create a clockUpdated event, which dispatches anytime we change the text on the clock. The important pieces are highlighted in red:
package
{
import flash.display.Sprite;
import flash.events.Event;
import flash.events.TimerEvent;
import flash.text.TextField;
import flash.utils.Timer;
[Event("clockUpdated")]
public class SimpleDigitalClock extends Sprite
{
public function SimpleDigitalClock()
{
super();
time = new TextField();
addChild(time);
timer = new Timer(1000);
timer.addEventListener(TimerEvent.TIMER, updateTime);
timer.start();
}
private var time:TextField;
private var timer:Timer;
public var timeZoneOffset:int = new Date().getTimezoneOffset();
private function updateTime(event:TimerEvent = null):void
{
var date:Date = new Date();
// converts the Date to UTC by adding or subtracting the time zone offset
var currentOffsetMilliseconds:Number = date.getTimezoneOffset() * 60 * 1000;
var newOffsetMilliseconds:Number = timeZoneOffset * 60 * 1000;
date.setTime(date.getTime() + currentOffsetMilliseconds);
date.setTime(date.getTime() - newOffsetMilliseconds);
time.text = date.toString();
dispatchEvent(new Event("clockUpdated"));
}
}
}
The two things that really matter here are adding the public property and the event metadata. We also dispatch the event in updateTime, otherwise it’d never get fired.
So let’s change these variables in MXML:
<?xml version="1.0" encoding="utf-8"?>
<local:SimpleDigitalClock xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:local="*"
timeZoneOffset="480" clockUpdated="trace('blah')">
</local:SimpleDigitalClock>
Now, let’s take a look at our genned code from this example:
// constructor (non-Flex display object)
/**
* @private
**/
public function FrameworkLessClock()
{
super();
// our style settings
// properties
this.timeZoneOffset = 480;
// events
this.addEventListener("clockUpdated", ___FrameworkLessClock_SimpleDigitalClock1_clockUpdated);
}
// scripts
// end scripts
// supporting function definitions for properties, events, styles, effects
/**
* @private
**/
public function ___FrameworkLessClock_SimpleDigitalClock1_clockUpdated(event:flash.events.Event):void
{
trace('blah')
}
You can see from the genned code it added the property we set as well as the event and a function for the event handler.
So that’s it for now. I plan on blogging some more on this stuff, covering how to add children in MXML without the framework as well as some other cool topics.
Post a comment if you have any questions or future blog posts that might be useful.Monday, February 25, 2008
Sunday, December 02, 2007
Making Flex Menus Easier
The Flex framework makes your life a lot easier when creating an application. However, not everything is necessarily as easy as it should be right out of the box; however, there are extensibility points within the framework to make this job easier. One such particular example is Menus. So let's look at a quick example:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:MenuBar showRoot="false" menuShow="menuShowHandler(event)" itemClick="menuClickHandler(event)">
<mx:XML>
<menuitem id="rootMenu">
<menuitem label="File">
<menuitem id="menuNew" label="New"/>
<menuitem label="Open"/>
</menuitem>
<menuitem label="Edit">
<menuitem id="cut" label="Cut"/>
<menuitem id="copy" label="Copy"/>
<menuitem id="paste" name="paste" label="Paste" enabled="false"/>
<menuitem type="separator" />
<menuitem label="Other menu item"/>
</menuitem>
<menuitem id="windowMenu" label="Window">
<menuitem id="d1" label="Document 1" type="radio" group="docs" toggled="false"/>
<menuitem label="Document 2" type="radio" group="docs" toggled="true"/>
<menuitem id="d3" name="d3" label="Document 3" type="radio" group="docs" toggled="false"/>
<menuitem label="Document 4" type="radio" group="docs" toggled="false"/>
</menuitem>
</menuitem>
</mx:XML>
</mx:MenuBar>
<mx:Script>
<![CDATA[
import mx.events.MenuEvent;
private function menuClickHandler(event:MenuEvent):void {
switch (event.label) {
case "Cut":
cutHandler(event);
break;
case "Copy":
copyHandler(event);
break;
// many more cases if needed....
}
}
private function menuShowHandler(event:MenuEvent):void {
switch (event.label) {
case "Edit":
editHandler(event);
break;
// many more cases if needed....
}
}
private function cutHandler(event:MenuEvent):void {
trace("cut event");
}
private function copyHandler(event:MenuEvent):void {
trace("copy event");
}
private function editHandler(event:MenuEvent):void {
trace("edit event");
}
]]>
</mx:Script>
</mx:Application>
So you look at that, and it's pretty damn easy to create a menu. You create a menu based off of a simple dataProvider that's written in an easy-to-understand XML format. So what's wrong with it? Well, I think there are a few problems:
- The XML is untyped, so it's easy to make a mistake or forget what the properties are (type, not typed and toggle, not toggled, etc...). It's also easy to forget the values the properties take (for example: type takes check, radio, separator and not checked, checkbox, or something similar)
- Databinding is not supported, so to disable the cut, copy, and paste menu items, we'd have to access the dataProvider for all three menu items and change it manually. We'd have to do this in a special method and remember to do it everytime, whereas if data binding was supported, it'd be done for us automatically when one property changes (for example, when a textarea has focus or something)
- Events are global for the whole menu. It's much better to attach events at the most specific point possible because we can avoid large, monolithic functions that essentially just dispatch the event somewhere else based on a large switch-case statement. Let the "framework" handle that for us.
Ok, so those are atleast some of the problems that I have with the current state of menus and specifying the data provider. However, we can fix these problems pretty easily. Since our data provider can be an object (not just XML), we can take advantage of this because MXML really just compiles down into an ActionScript object. Because MXML is essentially a typed object, we solve problem #1 (it can even tell us that type accepts check, radio, or seperator as inputs). Since MXML supports databinding, we solve problem #2, and with some functions of our own, we can define events on our new MXML object and get it routed to these more specific events and solve problem #3.
Here's what the example above will look like when done:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:local="*">
<mx:MenuBar showRoot="false" menuShow="menuItemEventHandler(event)" menuHide="menuItemEventHandler(event)"
itemClick="menuItemEventHandler(event)" itemRollOut="menuItemEventHandler(event)"
itemRollOver="menuItemEventHandler(event)" change="menuItemEventHandler(event)">
<local:MenuItem id="rootMenu">
<local:MenuItem label="File">
<local:MenuItem id="menuNew" label="New"/>
<local:MenuItem label="Open"/>
</local:MenuItem>
<local:MenuItem label="Edit" menuShow="editHandler(event)">
<local:MenuItem id="cut" label="Cut" itemClick="cutHandler(event)"/>
<local:MenuItem id="copy" label="Copy" itemClick="copyHandler(event)"/>
<local:MenuItem id="paste" name="paste" label="Paste" enabled="false"/>
<local:MenuItem type="separator" />
<local:MenuItem label="Other menu item"/>
</local:MenuItem>
<local:MenuItem id="windowMenu" label="Window">
<local:MenuItem id="d1" label="Document 1" type="radio" group="docs" toggled="false"/>
<local:MenuItem label="Document 2" type="radio" group="docs" toggled="true"/>
<local:MenuItem id="d3" name="d3" label="Document 3" type="radio" group="docs" toggled="false"/>
<local:MenuItem label="Document 4" type="radio" group="docs" toggled="false"/>
</local:MenuItem>
</local:MenuItem>
</mx:MenuBar>
<mx:Script>
<![CDATA[
import mx.events.MenuEvent;
private function menuItemEventHandler(event:MenuEvent):void
{
if (event.item is MenuItem)
{
EventDispatcher(event.item).dispatchEvent(event);
}
else if (event.menu && event.menu.dataProvider &&
event.menu.dataProvider[0] is MenuItem &&
event.menu.dataProvider[0].parent is MenuItem)
{
EventDispatcher(event.menu.dataProvider[0].parent).dispatchEvent(event);
}
}
]]>
</mx:Script>
<mx:Script>
<![CDATA[
private function cutHandler(event:MenuEvent):void
{
trace("cut event");
}
private function copyHandler(event:MenuEvent):void
{
trace("copy event");
}
private function editHandler(event:MenuEvent):void
{
trace("edit event");
}
]]>
</mx:Script>
</mx:Application>
You can see the code above looks basically the same, but it does solve all of our problems. We can even reference MenuItems by id's now, treating them as first-class objects. The only tricky part is the menuItemEventHandler defined that you'll also need. This grabs the appropriate MenuItem object and then dispatches the event to the event handler defined for that MenuItem. This is different then the switch-case statement above because it doesn't actually know anything about our menu data -- it just dispatched it through to the event defined on the menu item, so it's the same for all menus.
So how was this so easily accomplished? Well it's with a new MenuItem class. It's basically just an object with some properites, like type, enabled, name, etc... and one important property, called children that's an array of MenuItems. In its simplest form, MenuItem looks like:
package
{
[Event(name="change", type="mx.events.MenuEvent")]
[Event(name="itemClick", type="mx.events.MenuEvent")]
[Event(name="menuHide", type="mx.events.MenuEvent")]
[Event(name="menuShow", type="mx.events.MenuEvent")]
[Event(name="itemRollOut", type="mx.events.MenuEvent")]
[Event(name="itemRollOver", type="mx.events.MenuEvent")]
[DefaultProperty("children")]
public class MenuItem extends EventDispatcher
{
public function MenuItem() {
}
[Bindable]
public var enabled:Boolean = true;
[Bindable]
public var toggled:Boolean;
public var name:String = null;
public var group:String = null;
public var parent:MenuItem = null;
public var label:String = null;
[Inspectable(category="General", enumeration="check,radio,separator")]
public var type:String = null;
public var icon:Object = null;
private var _children:Array = null;
[Inspectable(category="General", arrayType="MenuItem")]
[ArrayElementType("MenuItem")]
public function set children(c:Array):void
{
children = c;
if (c)
for (var i:int = 0; i < c.length; i++)
c[i].parent = this;
}
public function get children():Array
{
return _children;
}
// functions for manipulating children:
}
}
Two cool things to note here are with the meta-data. The first is with [DefaultProperty("children")] defined above the class. This tells the compiler that when children are added in MXML, what property it actually correlates to. Another example of this is in the Flex framework for List and Menu where dataProvider is the default property. Another metadata trick is with [Inspectable(category="General", enumeration="check,radio,separator")] where we tell Flex Builder what the type property accepts so that code-hinting works quite nicely.
So the code above actually work perfectly fine, but I added some extra methods for dealing with children (adding, find, remove, etc...), which I will disclose aren't tested very well. I've uploaded the full MenuItem.as code so you can download it. Mike Schiff, another Flex SDK engineer, was the first one to point out this technique, and his code was the basis for everything here (in fact, it was probably most of it).
This is a really simple technique but it helps with a few pits of creating menus in Flex. Not only that, but it can be applied to all components based on data providers. If you want to define a similar MXML object but want to have non-standard properties, like myType instead of type, you can tell the Flex framework this by using either dataDescriptor or something like labelField/labelFunction.
Wednesday, November 28, 2007
Flex Tutorial Videos
Saturday, October 20, 2007
Validation/Invalidation Mechanism
It's been quite a while since I blogged last--for that I apologize.
When you create a UI component in Flex, there are 4 main methods we ask that you implement: createChildren, commitProperties, validateSize, and updateDisplayList. The Flex framework then calls these methods at the appropriate times, but if you get into component development, you need to understand not only how these mechanisms work, but why we have these mechanisms in the first place. As a new member to the Flex SDK team, this was one of the first questions I had. To help answer this, let's take a look at something I'm currently implementing. Note that whatever I show may or may not end up being the final implementation, but it's a good example to look at.
In AIR (Adobe Integrated Runtime), they added NativeMenu's, which are menus native to each operating system and sit at the top of the screen for a window. The API for this is centered around instantiating Menus, MenuItems, and adding them all together. A typical example would look like:
var mainMenu:NativeMenu = new NativeMenu();
var fileMenu:NativeMenu = new NativeMenu();
var printItem:NativeMenuItem = new NativeMenuItem("Print");
printItem.keyEquivalent = "P";
printItem.keyEquivalentModifiers = [Keyboard.CONTROL];
printItem.mnemonicIndex = 0;
...
var saveItem:NativeMenuItem = new NativeMenuItem("Save");
...
var newItem:NativeMenuItem = new NativeMenuItem("New");
...
fileMenu.addItem(printItem);
fileMenu.addItem(saveItem);
fileMenu.addItem(newItem);
var fileMenuItem:NativeMenuItem = new NativeMenuItem("File");
fileMenuItem.subMenu = fileMenu;
mainMenu.addItem(fileMenuItem);
// repeat for Edit, View, etc...
[Bindable]
private var myMenuData:XMLList =
<>
<menuitem label="_File">yy
<menuitem label="_Save" keyEquivalent="F10" />
<menuitem label="P_rint" mnemonicIndex="4" mnemonicIndexOverride="true" />
<menuitem type="separator" />
<menuitem label="E_xit" />
</menuitem>
<menuitem label="_Edit">
<menuitem label="_Copy" toggled="true" type="check" />
<menuitem label="C_ut"/>
<menuitem label="_Paste" />
</menuitem>
</>;
<mx:Desktopmenu id="desktopMenu" dataProvider="{myMenuData}" labelField="@label" showRoot="false"/>
public function set dataProvider(value:Object):void
{
_dataProvider = value;
createNativeMenu();
}public function set dataProvider(value:Object):void
{
_dataProvider = value;
invalidateProperties();
}
private invalidatePropertiesFlag:Boolean = false;
private function invalidateProperties()
{
if (!invalidatePropertiesFlag)
{
invalidatePropertiesFlag = true;
var myTimer:Timer = new Timer(100, 1);
myTimer.addEventListener(TimerEvent.TIMER, validateProperties);
myTimer.start();
}
}
private function validateProperties(event:TimerEvent)
{
if (invalidatePropertiesFlag)
{
invalidatePropertiesFlag = false;
createNativeMenu();
}
} <?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" applicationComplete="init()" layout="absolute" >
<mx:Script>
<![CDATA[
private function init():void
{
stage.frameRate = 1;
}
private function width50():void
{
myCanvas.width = 50;
var myTimer:Timer = new Timer(500, 1);
myTimer.addEventListener(TimerEvent.TIMER, width200);
myTimer.start();
}
private function width200(event:TimerEvent):void
{
myCanvas.width = 200;
}
]]>
</mx:Script>
<mx:Canvas id="myCanvas" backgroundColor="red" width="100" height="100" />
<mx:Button label="Click Me" click="width50()" x="200" y="200" />
</mx:Application>
We change the framerate to 1 per second. Now, when you click the button, sometimes you'll see it shrink to a width of 50 before growing to 200, or sometimes you'll see it just go to 200. It depends on exactly when you click on the button with respect to where in the current frame we are. There are a few reasons the flash player does this as it helps with performance, but the important point is that Flex ties into this and does validation/re-measuring only when it'll actually affects the screen by tying in to the ENTER_FRAME event. So, in this DesktopMenu component, we should do the same thing and validate ourselves when all other components do (to be honest, I'm not sure if the native menu, since it's using the operating system API underneath can update itself in between frames or not). To do this, we need to implement ILayoutManagerClient, like UIComponent does, and then attach ourselves to the LayoutManager's queue by calling UIComponentGlobals.mx_internal::layoutManager.invalidateProperties(this);. Since we're not a display object, we don't need to do anything in validateSize or validateDisplayList. One thing to note is that despite it's name, LayoutManger should really be though of as a ValidationManager because it doesn't just deal with layouts. One other reason we do this is because sometimes the order of properties matters, and if it's represented as MXML, this order can be lost. So for example, in a List component, we might do something like:
myList.dataProvider = ["a", "b", "c"]; // gets promoted to an ArrayCollection myList.selectedIndex = 1;
But in MXML, you have:
<mx:List id="myList" dataProvider="{['a', 'b', 'c']}" selectedIndex="1" />
<mx:List id="myList" selectedIndex="1" dataProvider="{['a', 'b', 'c'}" />
- Properties tend to change at the same time, and we don't want to re-do the same work because of this.
- Property changes often cascade and affect many other properties (changing my position, would affect lots of other elements). This exacerbates the point made above.
- The Flash Player uses delayed rendering, so changing a UI property doesn't actually display until the next frame
- MXML representations of objects don't take into account order of operations.
Sunday, September 09, 2007
AutoScrolling for Flex Tree
package{
import flash.events.Event;
import mx.controls.Tree;
import mx.core.mx_internal;
import mx.core.ScrollPolicy;
import mx.events.TreeEvent;
public class AutoSizeTree extends Tree
{
public function AutoSizeTree(){
super();
horizontalScrollPolicy = ScrollPolicy.AUTO;
}
// we need to override maxHorizontalScrollPosition because setting
// Tree's maxHorizontalScrollPosition adds an indent value to it,
// which we don't need as measureWidthOfItems seems to return exactly
// what we need. Not only that, but getIndent() seems to be broken
// anyways (SDK-12578).
// I hate using mx_internal stuff, but we can't do
// super.super.maxHorizontalScrollPosition in AS 3, so we have to
// emulate it.
override public function get maxHorizontalScrollPosition():Number
{
if (isNaN(mx_internal::_maxHorizontalScrollPosition))
return 0;
return mx_internal::_maxHorizontalScrollPosition;
}
override public function set maxHorizontalScrollPosition(value:Number):void
{
mx_internal::_maxHorizontalScrollPosition = value;
dispatchEvent(new Event("maxHorizontalScrollPositionChanged"));
scrollAreaChanged = true;
invalidateDisplayList();
}
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
// we call measureWidthOfItems to get the max width of the item renderers.
// then we see how much space we need to scroll, setting maxHorizontalScrollPosition appropriately
var diffWidth:Number = measureWidthOfItems(0,0) - (unscaledWidth - viewMetrics.left - viewMetrics.right);
if (diffWidth <= 0) maxHorizontalScrollPosition = NaN; else maxHorizontalScrollPosition = diffWidth; super.updateDisplayList(unscaledWidth, unscaledHeight); } } }