I was laying out my Flex components using mxml and had them working correctly. But then I wanted to switch them over to Actionscript because I wanted them to extend a base component that provides default functionality.
I've go the code working except that my components that used to fill the entire space using width="100%" and height="100%" appear to display just using the default sizes. Do you know How I can get them to take up the entire space again?
Here is a test component I am playing with that exhibits the problem.
Main.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:bbq="components.*"
backgroundGradientColors="[#000000, #3333dd]"
minWidth="480" minHeight="320"
layout="vertical" verticalAlign="top" horizontalAlign="center"
paddingLeft="0" paddingRight="0" paddingTop="30" paddingBottom="0"
width="100%" height="100%" >
<mx:VBox backgroundColor="0xcccc66">
<mx:ViewStack id="mainViewStack" width="100%" height="100%" color="0x323232">
<bbq:TestComp id="testComp" height="100%" width="100%" />
<bbq:ResultsBox />
</mx:ViewStack>
</mx:VBox>
TestComp.as
package components {
import mx.containers.VBox;
import mx.containers.Panel;
import flash.events.*;
public class TestComp extends VBox {
private var p:Panel;
function TestComp(){
super();
percentHeight = 100;
percentWidth = 100;
}
override protected function createChildren():void {
super.createChildren();
p = new Panel();
p.percentHeight = 100;
p.percentWidth = 100;
p.title = "Test Comp";
addChild(p);
}
}
}
Add inherited method calls; calls to super() methods;
import mx.containers.VBox;
import mx.containers.Panel;
import flash.events.*;
public class TestComp extends VBox {
private var p:Panel;
function TestComp(){
super(); // add this line
percentHeight = 100;
percentWidth = 100;
}
override protected function createChildren():void {
super.createChildren(); // add this line
p = new Panel();
p.percentHeight = 100;
p.percentWidth = 100;
p.title = "Test Comp";
addChild(p);
}
}
In you mxml:
<local:TestComp width="100%" height="100%" />
I think I might know what it is. I think I explicitly set the width and height of one of the other components in the viewstack, and I think that affected the viewstack itself so that all of the other components that were added to it also go those dimensions.
resizeToContent=true
Related
I'm having a sizing issue with a canvases located inside an HBox. It seems "_graphic", "_border" and "_fill" canvases (in com.example.ThingRenderer.mxml) do not get measured at the same time as all the other measurements inside the renderer. However, this problem is only observed on the first pass-through. Refer to the images for a visual... 1st image shows the state of the app after it finished loading. 2nd image represents what the screen looks like after the Populate button is clicked. 3rd image shows what happens when the stepper is incremented. The question is how come the drawing in the 3rd image doesn't get rendered once the data is populated into the table?
RendererTest.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute"
creationComplete="handleCreationComplete(event)"
>
<mx:Script>
<![CDATA[
import com.example.Thing;
import mx.collections.ArrayCollection;
import mx.events.FlexEvent;
import mx.events.NumericStepperEvent;
private const _thingProvider:ArrayCollection = new ArrayCollection();
private var _thing1:Thing;
protected function handleCreationComplete(event:FlexEvent):void {
_thing1 = new Thing("thingy", 0xff0000, 0.3);
_stepper.value = _thing1.ratio;
}
protected function handlePopulateClick(event:MouseEvent):void {
_thingProvider.addItem(_thing1);
}
protected function handleStepperChange(event:NumericStepperEvent):void {
_thing1.ratio = event.value;
}
]]>
</mx:Script>
<mx:VBox>
<mx:Button label="Populate" click="handlePopulateClick(event)" />
<mx:NumericStepper id="_stepper" minimum="0" maximum="1" stepSize="0.01" change="handleStepperChange(event)" />
<mx:AdvancedDataGrid dataProvider="{_thingProvider}" variableRowHeight="true" width="100%" height="100%">
<mx:columns>
<mx:AdvancedDataGridColumn headerText="Name" dataField="name" />
<mx:AdvancedDataGridColumn headerText="Display"
width="150" sortable="false"
itemRenderer="com.example.ThingRenderer"
/>
</mx:columns>
</mx:AdvancedDataGrid>
</mx:VBox>
</mx:Application>
com.exampleThingRenderer.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas
xmlns:mx="http://www.adobe.com/2006/mxml"
width="100%"
horizontalScrollPolicy="off" verticalScrollPolicy="off"
>
<mx:Script>
<![CDATA[
import mx.binding.utils.ChangeWatcher;
private var _thing:Thing;
private var _ratioWatcher:ChangeWatcher;
private var _doClearContent:Boolean;
private var _doDrawBorder:Boolean;
private var _doUpdateFill:Boolean;
override public function set data(value:Object):void {
if(value && value is Thing) {
_thing = Thing(value);
if(_ratioWatcher) {
_ratioWatcher.unwatch();
}
_ratioWatcher = ChangeWatcher.watch(_thing, "ratio", handleRatioChanged);
_doClearContent = false;
_doDrawBorder = true;
_doUpdateFill = true;
_graphic.invalidateSize();
_border.invalidateSize();
}
else {
_doClearContent = true;
_doDrawBorder = false;
_doUpdateFill = false;
}
super.data = value;
}
private function handleRatioChanged(event:Event):void {
_doUpdateFill = true;
invalidateDisplayList();
}
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
if(_doClearContent) {
_container.visible = false;
_container.includeInLayout = false;
_doClearContent = false;
}
super.updateDisplayList(unscaledWidth, unscaledHeight);
if(_doDrawBorder) {
trace("_thingContainer.width="+_container.width, "_thingGraphic.width="+_graphic.width, "_thingBorder.width="+_border.width);
_border.graphics.clear();
_border.graphics.moveTo(0, 0);
_border.graphics.lineStyle(1, _thing.color);
_border.graphics.lineTo(_border.width, 0);
_border.graphics.lineTo(_border.width, _border.height);
_border.graphics.lineTo(0, _border.height);
_border.graphics.lineTo(0, 0);
_doDrawBorder = false;
}
if(_doUpdateFill) {
_percentage.text = Math.round(_thing.ratio * 100.0) + "%";
_fill.graphics.clear();
_fill.graphics.beginFill(_thing.color);
_fill.graphics.drawRect(0, 0, _fill.width * _thing.ratio, _fill.height);
_doUpdateFill = false;
}
}
]]>
</mx:Script>
<mx:HBox id="_container" width="100%" paddingLeft="5" paddingTop="5" paddingRight="5" paddingBottom="5">
<mx:Label id="_percentage" width="45" />
<mx:Canvas id="_graphic" width="100%" height="15">
<mx:Canvas id="_border" x="0" y="0" width="100%" height="100%" />
<mx:Canvas id="_fill" x="0" y="0" width="100%" height="100%" />
</mx:Canvas>
</mx:HBox>
</mx:Canvas>
com.example.Thing.as
package com.example {
public class Thing {
[Bindable] public var name:String;
[Bindable] public var color:uint;
[Bindable] public var ratio:Number;
public function Thing(name:String, color:uint, ratio:Number) {
this.name = name;
this.color = color;
this.ratio = ratio;
}
}
}
All this happens because you can't use width and height properties in updateDisplayList, they are not updated yet. Make separate component (e.g. ThingProgressBar) and put drawing logick inside it, that will solve everything:
package
{
import mx.core.UIComponent;
public class ThingProgressBar extends UIComponent
{
private var _ratio:Number;
public function get ratio():Number
{
return _ratio;
}
public function set ratio(value:Number):void
{
_ratio = value;
invalidateDisplayList();
}
override protected function updateDisplayList(
unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
graphics.clear();
if (unscaledWidth > 0 && unscaledHeight > 0)
{
graphics.lineStyle(1, 0xFF0000);
graphics.drawRect(0, 0, unscaledWidth, unscaledHeight);
graphics.beginFill(0xFF0000);
graphics.drawRect(0, 0, unscaledWidth * ratio, unscaledHeight);
graphics.endFill();
}
}
}
}
So your renderer might look like this:
<mx:HBox
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:mx="library://ns.adobe.com/flex/mx"
horizontalScrollPolicy="off" verticalScrollPolicy="off" xmlns:local="*"
>
<fx:Script>
<![CDATA[
[Bindable] private var _thing:Thing;
override public function set data(value:Object):void
{
_thing = value as Thing;
super.data = value;
}
]]>
</fx:Script>
<mx:HBox width="100%"
paddingLeft="5" paddingTop="5"
paddingRight="5" paddingBottom="5">
<mx:Label text="{_thing.name}" width="45" />
<local:ThingProgressBar width="100%" height="15"
ratio="{_thing.ratio}"/>
</mx:HBox>
</mx:HBox>
I removed watcher. Binding by watcher is considered a bad practice, use mxml binding or events instead.
I removed two Canvases with separated border and fill - they can be cobined together.
I used UIComponent instead of Canvas. Don't use containers unless you need layout, they are heavy.
I used HBox instead of Canvas in renderer because I like boxes more :) But you can't avoid using second container in renderer if you need custom styles since List overwrites renderer's stylesheet.
MENU - RightNavigation
<fx:Metadata>
[Event(name="interval", type="flash.events.Event")]
<fx:Metadata>
[Bindable]
public var sInterval:String;
[Bindable]
public var sIntervalId:String;
protected function intervalSelected(event:MouseEvent):void
{
sInterval = intervalMenu.selectedItem.intervals_miles;
sIntervalId = intervalMenu.selectedItem.interval_id;
dispatchEvent(new Event("interval"));
}
MENU - RightNavigation = This are the buttons in the menu
<s:VGroup includeIn="iMenu" width="100%" height="100%" horizontalAlign="center" paddingTop="10">
<s:List id="intervalMenu" styleName="leftNavContent" creationComplete="miles_handler(event)"
itemRenderer="renderers.MilesItemRenderer" click="intervalSelected(event)" >
<s:AsyncListView list="{intervalsResult.lastResult}"/>
</s:List>
</s:VGroup>
MODULE - mcIntervals
initialize="init()"
import containers.RightNavigation;
import mx.binding.utils.ChangeWatcher;
import flash.events.*;
[Bindable]
public var interval:RightNavigation;
public function init():void
{
//addEventListener("interval", intervalServices);
ChangeWatcher.watch(interval, "sIntervalId", intervalServices);
}
protected function intervalServices(e:Event):void
{
Alert.show("test");
}
Application Setup
MainApp has two containers RightNavigation and MainContent
MainContent has a module called mcIntervals
So I'm trying to send value from RightNavigation to mcIntervals
This is a desktop application if this make any difference
This is not working I can see that is sending the value in debug mode but ChangeWatcher or evenListener are not detecting anything
Thanks, Robert
I don't even know how to explain this behavior but I'll try. I am loading images from an external url that requires basic auth so I am using URLLoader to load the image from a unique ID. The ID gets passed to the itemrenderer which then proceeds to load the image. But the images switch around on their own when I scroll. If I load more than 7 images or so it starts repeating images....
Youtube video of error:
http://www.youtube.com/watch?v=ZYoqlS14gWQ
Relevant code:
<s:ItemRenderer name="RandomItemRenderer" creationComplete="init();"
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
autoDrawBackground="false">
<s:states>
<s:State name="normal" />
<s:State name="hovered" />
<s:State name="selected" />
</s:states>
<fx:Script>
<![CDATA[
import flash.net.URLLoader;
import flash.net.URLLoaderDataFormat;
import flash.net.URLRequest;
import flash.net.URLRequestHeader;
import flash.net.URLRequestMethod;
import mx.utils.ObjectProxy;
import customclasses.Settings;
[Bindable] private var coverArtImage:Image;
private var myCoverArtLoader:URLLoader;
[Bindable] private var coverArtSource:String;
private function init():void {
get_coverArt();
}
private function get_coverArt(): void {
if (!data.coverArt) {
set_nullCoverArt();
} else {
var requestString:String = "/rest/getCoverArt.view?v=1.5.0&c=AirSub&id=" + data.coverArt;
var requestURL:String = Settings.ServerURL + requestString;
myCoverArtLoader = new URLLoader();
var myRequest:URLRequest = new URLRequest();
var authHeader:URLRequestHeader = new URLRequestHeader();
authHeader.name = 'Authorization';
authHeader.value = 'Basic ' + Settings.EncryptedCreds();
myRequest.requestHeaders.push(authHeader);
myRequest.url = requestURL;
myRequest.method = URLRequestMethod.GET;
myCoverArtLoader.dataFormat = URLLoaderDataFormat.BINARY;
myCoverArtLoader.addEventListener(Event.COMPLETE, set_coverArt);
myCoverArtLoader.addEventListener(IOErrorEvent.IO_ERROR, set_failedCoverArt);
myCoverArtLoader.load(myRequest);
}
}
private function set_coverArt(evt:Event) : void {
coverArtImage = new Image();
coverArtImage.source = myCoverArtLoader.data;
myCoverArtLoader.removeEventListener(Event.COMPLETE, set_coverArt);
}
private function set_nullCoverArt() : void {
coverArtImage = new Image();
coverArtImage.source = "assets/nullCoverArt.jpg";
}
private function set_failedCoverArt() : void {
coverArtImage = new Image();
coverArtImage.source = "assets/nullCoverArt.jpg";
myCoverArtLoader.addEventListener(IOErrorEvent.IO_ERROR, set_nullCoverArt);
}
]]>
</fx:Script>
<s:Image source.normal="assets/coverOutline.png" source.selected="assets/coverOutlineYellow.png" source.hovered="assets/coverOutlineYellow.png"
height="226" width="226" />
<s:VGroup top="4.5" bottom="5" width="200" horizontalAlign="center" letterSpacing="10"
paddingBottom="5" paddingTop="9" verticalAlign="middle" x="13.5">
<s:Image id="ui_imgCoverArt" width="200" height="200" source="{coverArtImage.source}"/>
<s:Label text="{data.title}" width="160" styleName="RandomList" />
</s:VGroup>
ItemRenderers are reusable and cached, i.e. there are only limited count created in List to fill its area (rowCount +- couple). And when you scroll, new renderers are not instantiated, instead the one renderer that was scrolled out goes up or down and is filled with new data.
That's why you can not rely on creationComplete event, it will be fired only once for each instance of renderer.
The solution is to override data setter and build there the behaviour needed:
override public function set data(value:Object):void
{
super.data = value;
get_coverArt();
}
Useful link: How flex itemRenderer works ? (their life cycle)
I'm having trouble to resize my custom UIComponent that wrap flash.media.Video object (The reason I choose this way is because mx.control.VideoDisplay doesn't support streaming playback that available in flash.media.Video that is attachNetStream()). Once I create a 320x240 Video size and remove it from its parent, I can't replace it with another one, bigger or smaller.
Here's my code (this one only capture Camera not NetStream).
package media
{
import flash.media.Camera;
import flash.media.Video;
import mx.controls.VideoDisplay;
import mx.core.UIComponent;
public class VideoUI extends UIComponent
{
private var video:Video;
public function VideoUI(width:int, height:int)
{
super();
video = new Video(width, height);
var cam:Camera = Camera.getCamera();
video.attachCamera(cam);
addChild(video);
}
}
}
The other part,
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:Script>
<![CDATA[
import media.VideoUI;
private function addVideoOutput():void
{
// initial video size
var video:VideoUI = new VideoUI(160,120);
HBoxVideo.addChild(video);
}
protected function resizeVideo(event:MouseEvent):void
{
var videoList:Array = HBoxVideo.getChildren();
for (var i:int = 0; i < videoList.length; i++)
{
var video:VideoUI = videoList.pop();
HBoxVideo.removeChild(video);
// new size that produce the previous size :(
video = new VideoUI(320, 240);
HBoxVideo.addChild(video);
}
}
]]>
</mx:Script>
<mx:Button click="addVideoOutput()" x="10" y="265" label="add"/>
<mx:HBox x="10" y="10" width="100%" id="HBoxVideo">
</mx:HBox>
<mx:Button x="58" y="265" label="resize" click="resizeVideo(event)" id="resizeButton"/>
</mx:Application>
Thank you very much.
By default, new instances of the Video class are 320 pixels wide by 240 pixels high. You will need access to your video in the VideoUI Class so that you can change the width and height.
As follows:
Change all appearances of your video variable in VideoUI.as to
_video
and apply a getter.
New Video UI Class
package media
{
import flash.media.Camera;
import flash.media.Video;
import mx.core.UIComponent;
public class VideoUI extends UIComponent
{
private var _video:Video;
public function VideoUI(width:int, height:int)
{
super();
_video = new Video(width, height);
var cam:Camera = Camera.getCamera();
_video.attachCamera(cam);
addChild(_video);
}
public function get video():Video{
return _video;
}
}
}
Replace in your main mxml file
video = new VideoUI(320, 240);
with
video.video.width=320;
video.video.height=240;
Note: You should rename your VideoUI instance to videoui or the sorts. It is a little confusing. You can also move this to your VideoUI Class or make a method. The choice is yours.
I am trying my first flex application. And have a problems adding data from xml http service to datagid.
My xml file looks like this:
<players>
<player>
<name>test</name>
<status>F</status>
<claimed>1</claimed>
</player>
<player>
<name>meta</name>
<status>F</status>
<claimed>1</claimed>
</player>
</players>
First I tried to fill the data in a raw way, so created mxml tag for HTTP service, and added handlers.
But very soon I realized that main application file became unreadable (because of huge amount of code), so I decided to organize it some way.
So decided to replace services with a separate as classes.
My new code looks like this:
MXML:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute" creationComplete="main()" height="756" borderColor="#FFFFFF" width="950" top="10" left="10" horizontalAlign="left" verticalAlign="top" backgroundGradientAlphas="[1.0, 1.0]" backgroundGradientColors="[#FCFCFC, #FCFCFC]">
<mx:Panel width="900" height="727" layout="absolute" title="Игра ГО" horizontalAlign="center" horizontalCenter="0" top="10">
<mx:Script>
<![CDATA[
import goclient.ListOfPlayers;
import goclient.usersList;
import goclient.Tester;
import mx.controls.Alert;
// And makes periodical requests to the server
[Bindable]
public var users:ListOfPlayers;
[Bindable]
public var test:Tester;
public function main():void{
test = new Tester();
users = new ListOfPlayers();
}
]]>
</mx:Script>
<mx:DataGrid doubleClickEnabled="true" dataProvider="{users.getPlayersList()}"
x="10" y="157" width="860" height="520" id="userList">
<mx:columns>
<mx:DataGridColumn dataField="claimed" headerText="Was claimed" width="25"/>
<mx:DataGridColumn dataField="name" headerText="Name of the player" />
<mx:DataGridColumn dataField="status" headerText="Status (Free or Busy)" />
</mx:columns>
</mx:DataGrid>
And the service class:
ListOfPlayers.as
package goclient
{
import flash.utils.Timer;
import mx.controls.Alert;
import mx.collections.ArrayCollection;
import mx.rpc.events.ResultEvent;
import mx.rpc.http.mxml.HTTPService;
public class ListOfPlayers
{
public var usersListService:HTTPService;
private var minTimer:Timer = new Timer(100000, 0);
private var playersData:ArrayCollection;
private var person:currentPerson;
public function ListOfPlayers()
{
usersListService = new HTTPService();
usersListService.url = "http://127.0.0.1:8000/go/active/";
usersListService.addEventListener(ResultEvent.RESULT, resultHandler);
//Alert.show("Here");
sendData();
//minTimer.addEventListener(TimerEvent.TIMER, sendData);
//minTimer.start();
}
public function getResp():String
{
return "Resr";
}
public function resultHandler(event:ResultEvent):void
{
//person = new currentPerson(event.result.current.username, event.result.current.img, event.result.current.rank);
playersData = event.result.players.player;
Alert.show("resh");
}
public function sendData():void
{
usersListService.send();
}
public function getPlayersList():ArrayCollection
{
Alert.show(playersData.toString());
return playersData;
}
}
}
The problem is that nothing is shown in the datagrid
I am just a beginner, so please advice what did I wrong with the class
The result function (in ListOfPlayers class) should give the list of players and not the function that is calling the webservice.
What you could do is add this in server class:
[Bindable]
public var playersData:ArrayCollection;
and in your view add also this variable with the bindable tag and set the value add this line in main():
playersData = users.playersData;
then the datagrid dataprovider is "{playersData}"
this should work. But with XML list it is always a bit difficult to know how deep you are in the tree ;)