I am trying to define the content area of a custom component that extends mx:VBox. The component has predefined headers and footers (visual children) and I want to set the area in the middle for adding children to the component. The component will be used like this:
<custom_component>
<mx:button/>
</custom_component>
How would I set this content area?
There's actually a few steps to it.
Your custom component needs to set its DefaultProperty metadata so the children don't collide with the ones within the custom component itself.
Then you need to stow them away in an instance var to add to your content area later, because the properties will be set before the components children are created.
Lastly, if multiple children are specified your DefaultProperty will be handed an Array object (rather than a single UIComponent instance.)
So your custom component would look something like this:
<?xml version="1.0" encoding="utf-8"?>
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml" width="400" height="300">
<mx:Metadata>
[DefaultProperty("content")]
</mx:Metadata>
<mx:HBox id="headerBox"/>
<mx:VBox id="contentBox"/>
<mx:HBox id="footerBox"/>
<mx:Script>
<![CDATA[
import mx.core.UIComponent;
private var _contentChildren:Array;
public function set content(c:*) : void {
// Allow 1 or more children to be specified
_contentChildren = (c as Array) || [c];
}
override protected function createChildren() : void {
// Call super so contentBox gets created first
super.createChildren();
for each (var child:UIComponent in _contentChildren) {
contentBox.addChild(child);
}
}
]]>
</mx:Script>
</mx:VBox>
In your custom component, add the DefaultProperty metadata tag:
[DefaultProperty("nameOfDefaultProperty")]
Then you would also define a setter for that property:
public function set nameOfDefaultProperty(value:UIComponent):void
{
if (value != null)
{
// add "value" to the display list here
}
}
Try using a canvas?
Related
I am trying to embed an image using an item renderer in my Flex project.
The image path however is a String passed in as a bound variable.
I am aware that
<s:BitmapImage source="#Embed('/../assets/image.png')" />
works because the image is embedded at runtime? (Could someone please clarify this)
How would i go about embedding my bound string, somewhat like this:
<s:BitmapImage source="#Embed('/../assets/{data.image}')" />
Many Thanks
I think a better choice if you'd like to embed the image but find it dynamically at runtime is: Embed all of the images it could be and then grab a reference to it dynamically. We generally use a pattern like this:
public class Icons {
[Embed(source="icons/icon1.png")]
public var icon1:Class;
[Embed(source="icons/icon2.png")]
public var icon2:Class;
}
Then you can dynamically grab the embedded images from your Icons instance at run-time.
Edit - self contained example - I'll use an item renderer since I think that's what you're doing.
Let's assume data.image can be 'plane' 'train' or 'automobile'
<s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx">
<fx:Script>
<![CDATA[
[Embed(source="/assets/icons/plane.png")]
public var plane : Class;
[Embed(source="/assets/icons/train.png")]
public var train : Class;
[Embed(source="/assets/icons/automobile.png")]
public var automobile : Class;
]]>
</fx:Script>
<s:Image source="{this[data.image]}"/>
</s:ItemRenderer>
This is a really simple example and not the BEST way to implement, but you get the idea.
I like embed icons with css files. Then in ItemRenderer you can set css class and get image which you want.
css file or mxml css block:
.icons
{
bender: Embed(source="/assets/bender.png");
/* other icons */
}
In renderer, when you override set data method:
override public function set data(value:Object):void
{
super.data = value;
var iconName:String = data.image;
if ( iconName )
{
var cssDecl2:CSSStyleDeclaration = styleManager.getStyleDeclaration(".icons");
var IconClass:Class = cssDecl2.getStyle( iconName );
bmImage.source = new IconClass();
}
}
and bmImage as id s:BitmapImage:
<s:BitmapImage id="bmImage" />
I'm trying to figure out the right approach for "Code behind" using flash builder for a mobile app:
I'm creating a flex mobile AIR project (Based on the "Tabbed view" template)
setting my UI in design mode
now I want all the logic to be in a separate class that will change the UI look accordingly
Sounds easy, however I can't really get the approach for doing it, any help is appreciated :)
Update:
main app:
<?xml version="1.0" encoding="utf-8"?>
<s:TabbedViewNavigatorApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark" applicationDPI="160">
<s:ViewNavigator label="a" width="100%" height="100%" firstView="views.aView"/>
<s:ViewNavigator label="b" width="100%" height="100%" firstView="views.bView"/>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
</s:TabbedViewNavigatorApplication>
view A:
<?xml version="1.0" encoding="utf-8"?>
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark" title="a">
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<s:Label id="txt" x="280" y="139" text="Label"/>
</s:View>
So now I want MyClass to change txt textField according to my logic, what is the right approach?
An elegant way would be implementing IMXMLObject. When implementing this interface, the IMXMLObject#initialize method will take the component (named document of type Object) and an optional id (of type String) as arguments and u can easily implement this pattern. The big advantage is, that you use composition over inheritance and when using interfaces, you can use it as some sort of type save mix-in as view behavior:
package net.icodeapp.examples.views
{
import flash.events.MouseEvent;
import mx.core.IMXMLObject;
import mx.events.FlexEvent;
public class ViewBaseModel implements IMXMLObject
{
//-------------------------------------------------------------------------
//
// Properties
//
//-------------------------------------------------------------------------
private var _id:String;
private var _viewBase:ViewBase;
protected function set viewBase(value:ViewBase):void
{
_viewBase = value;
if (!_viewBase)
throw new ArgumentError('View must be instance of ViewBase');
if (!_viewBase.initialized)
_viewBase.addEventListener(FlexEvent.CREATION_COMPLETE, viewBase_creationCompleteHandler, false, 0, true);
else
viewCreationCompleted();
}
//-------------------------------------------------------------------------
//
// Constructor
//
//-------------------------------------------------------------------------
public function ViewBaseModel()
{
}
//-------------------------------------------------------------------------
//
// Methods
//
//-------------------------------------------------------------------------
public function initialized(document:Object, id:String):void
{
viewBase = document as ViewBase;
_id = id;
}
private function viewCreationCompleted():void
{
_viewBase.addEventListener(MouseEvent.CLICK, viewBase_clickHandler);
}
//-------------------------------------------------------------------------
//
// Event Handler
//
//-------------------------------------------------------------------------
private function viewBase_creationCompleteHandler(event:FlexEvent):void
{
viewCreationCompleted();
}
private function viewBase_clickHandler(event:MouseEvent):void
{
// todo: do some action
}
}
}
The model is initialized and references are set by the framework. When taking a peek at the generated ActionScript code you'll see, that IMXMLObject#initialize it called in the constructor after the model is instantiated.
<?xml version="1.0"?>
<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:views="net.icodeapp.examples.views.*">
<fx:Declarations>
<views:ViewBaseModel/>
</fx:Declarations>
</s:Group>
The model would receive by events by the view and can call methods on it.
All you do is make an AS file that has the same base class as whatever your MXML object was initially set up as, for example if it's a VGroup make MyBaseClass extends VGroup, then change the VGroup to MyBaseClass.
Example
[Main.mxml]
<main:MainBase
xmlns:main="*"
...>
</main:MainBase>
[MainBase.as]
public class MainBase extends Application
Think of your Code Behind as a base class (or an Abstract Class). In an Abstract Class, it is really common for the actual implementation of methods or the "real objects" behind properties to be left to the extending class(es) to supply.
This is exactly like what you do when you set a base class in Flash to your custom Class, but the actual member objects (buttons, etc.) are provided on the stage of the MovieClip whose library instance is linked to your clip.
For more on code behind, check out my blog post here. If you'd like to check out the code for the template component described there, look here. Though template components are less useful in the Spark world (IMO).
I have a custom component ExpandCollapseMenu that extends SkinnableContainer. This component can have state "normal" or "expanded".
Inside this component I have buttons, with different skin based on ExpandCollapseMenu's state.
This works fine when defining the buttons inside ExpandCollapsMenu's skin class:
<s:Group id="contentGroup" top="20" left="10" right="10" bottom="10">
<s:layout>
<s:VerticalLayout/>
</s:layout>
<component:ExpandCollapseMenuButton label="Foo" skinClass.expanded="component.ExpandCollapseMenuButtonExpandedSkin" skinClass.normal="component.ExpandCollapseMenuButtonSkin" />
</s:Group>
But I don't want to define the buttons inside ExpandCollapsMenu's skin class, I want to define them where I use ExpandCollapseMenu. Like this:
<component:ExpandCollapseMenu skinClass="component.ExpandCollapseMenuSkin">
<component:ExpandCollapseMenuButton label="Foo" />
</component:ExpandCollapseMenu>
At this level, I can't reference skinclass.expanded, but I got it working by using CSS like this:
component|ExpandCollapseMenu:expanded component|ExpandCollapseMenuButton {
skinClass: ClassReference("component.ExpandCollapseMenuButtonExpandedSkin");
}
component|ExpandCollapseMenu:normal component|ExpandCollapseMenuButton {
skinClass: ClassReference("component.ExpandCollapseMenuButtonSkin");
}
Is this a good way to change skin based on parent containers state? Or is there a better way?
I recommend you to solve the problem on a ExpandCollapseMenu container level. When ExpandCollapseMenu's state is changed you should iterate over children and set some flag for each of them.
So I suggest to introduce expanded:Boolean flag for ExpandCollapseMenuButton.
The problem occurs if ExpandCollapseMenu can contain other instance's types. In this case we can solve it using the following two ways:
Check components in process of iterating if they are ExpandCollapseMenuButton instances (using is operator).
Introduce simple Expandable interface like the following:
public interface Expandable
{
function set expanded(value:Boolean);
}
and implement this interface by ExpandCollapseMenuButton. So you can use is operator in iterator body more flexible way without ExpandCollapseMenuButton dependency.
So the last part of puzzle is to implement setter in ExpandCollapseMenuButton class and switch skin:
private var expandedDirty:Boolean;
private var _expanded:Boolean;
public function set expanded(value:Boolean)
{
if (value == _expanded)
return;
_expanded = value;
expandedDirty = true;
invalidateProperties();
}
override protected function commitProperties():void
{
super.commitProperties();
if (expandedDirty)
{
if (_expanded)
setStyle("skinClass", ExpandCollapseMenuButtonExpandedSkin);
else
setStyle("skinClass", ExpandCollapseMenuButtonSkin);
expandedDirty = false;
}
}
In the following code, the call to myChild.bar() results in an exception because myChild is null. myParent is a valid object. What I don't understand is why myChild has not been created yet.
I have read the following document related to object creation sequence, but I am unsure how "new" is related:
http://livedocs.adobe.com/flex/3/html/help.html?content=layoutperformance_03.html
Any help is appreciated!
// Main.mxml
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="created()">
<mx:Script>
<![CDATA[
public var myParent:Parent = new Parent();
public function created():void {
myParent.foo();
}
]]>
</mx:Script>
</mx:Application>
// Parent.mxml
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" xmlns="*">
<mx:Script>
<![CDATA[
public function foo():void {
myChild.bar();
}
]]>
</mx:Script>
<Child id="myChild"/>
</mx:Canvas>
// Child.mxml
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
public function bar():void {
trace("Hello World");
}
]]>
</mx:Script>
</mx:Canvas>
creationComplete fires when an object and all its child elements are created and drawn to the screen. Since your parent object is created with
public var myParent:Parent = new Parent();
it is not a child of your main object and the creationComplete event fires before myParent is initialized.
In fact, myParent.myChild will remain null until something causes myParent to initialize. You can cause this by adding it to a component on screen or you could just call myParent.initialize();
Flex is a visual display / UI framework. It is designed to keep a display list of UI items and handle various updates the items in the display list.
The problem is that you have never added your Parent component to the display list. This is done with the AddChild method in the Flex 2/3 Halo architecture or AddElement if you're using the Flex 4 Spark architecture.
Once you add the Parent component to the stage using the AddChild method, that component will start stepping through the component lifeCycle which includes creating it's Children (via createChildren() method), and sizing and positioning the children (via updateDisplayList() ). When defining a component and child via MXML--as an example your Parent.mxml file defines the Child class as a child using XML--the addChild method call is done "automagically" in the background.
Keep in mind that the Flex Component LifeCycle is a process and may not be immediate. If you perform an addChild on the parent; you may not be able to immediately access that parent's children on the next line.
So, the new keywords creates a new instance of the component; but it does not put that component onto the displayList for processing by the Flex Framework layout managers.
One way to rectify the situation might be this change to your main application file:
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="created()">
<mx:Script>
<![CDATA[
public function created():void {
myParent.foo();
}
]]>
</mx:Script>
<parent:Parent id="myParent" />
</mx:Application>
Another might be this:
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="created()">
<mx:Script>
<![CDATA[
public var myParent:Parent = new Parent();
public function created():void {
myParent.foo();
}
override protected function createChildren():void{
super.createChildren();
this.addChild(myParent);
}
]]>
</mx:Script>
</mx:Application>
For some good reading about this stuff, read through the Flex Component LifeCycle docs. http://livedocs.adobe.com/flex/3/html/ascomponents_advanced_2.html#204762
I know AdvancedDataGrid has a styleFunction callback, but I don't want to change the style; I want the itemRenderer to get the global style that everything else (including normal columns) uses.
My in-line item renderers use the default style, but not the ones I created as separate MXML classes (they extend Canvas).
Any handle I need to jiggle to propagate the style into my item renderers?
Thanks
I don't think you can propagate. Seems like styleFunction is for something completly different. But you can access any CSS property
var styleDecl:CSSStyleDeclaration = StyleManager.getStyleDeclaration("YourTagOrClassName");
and then:
styleDecl.getStyle(property);
If you want to get style declaration directly from the AdvancedDataGrid you have to get renderers listData:
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml"
implements="mx.controls.listClasses.IDropInListItemRenderer">
<mx:Script>
import mx.controls.dataGridClasses.DataGridListData;
import mx.controls.listClasses.BaseListData;
[Bindable("dataChange")] private var _listData : BaseListData;
public function get listData():BaseListData
{
return _listData;
}
public function set listData( value : BaseListData ) : void
{
_listData = value;
}
override public function set data(value:Object):void
{
super.data = value;
if (this.listData)
((this.listData as DataGridListData).owner as AdvancedDataGrid).getStyle(...);
}
</mx:Script>
</mx:Canvas>
My Bad
It was picking up the style from the ADG; that component's style was not defaulted to the global style.