Defining hitArea on Spark ToggleButton - actionscript-3

I've been able to skin a ToggleButton with custom images (loading Skin images dynamically) and it is working well.
The last functionality I would like to add is to limit interactivity (mouseEvents) to the opaque pixels. I've been searching around and came across InteractivePNG and Creating a hitarea for PNG Image with transparent (alpha) regions in Flex. I had erratic results with InteractivePNGin initial tests so I thought I would try to define the ToggleButton hitArea property using the code from the second link.
I haven't been able to get it working. My code for generating the Sprite to define the hit area is below. (I've also tried just a simple Sprite with a rect drawn within it.) When I set the togglebutton.hitArea I don't get any errors but it is no longer interactive.
Does the Sprite defining the hitArea need to be added to the DisplayList of the object it is being applied to? If so, how to I do that with a subclass of ToggleButton?
hitSprite = createHitArea(upImageData, 1);
hitSprite.mouseEnabled = false;
this.hitArea = hitSprite;
Of course calling `addChild() on a ToggleButton throws an error.
this.addChild(hitSprite);
I've tried:
wrapping my custom ToggleButton in a Spark Group, and then an MX UIComponent so that I could have a use a SpriteDisplayElement to add things to – but that didn't work.
adding a Sprite within my custom ToggleButton definition – and get an error doing that.
Am I out of luck here using the ToggleButton class for this type of functionality? What I don't understand is why ToggleButton has a hitArea property if there isn't a way to set it – but that can't be the case so I must be doing something wrong.
Update
I just tried this approach( Flex 4 Custom Component - How to notify skin of property changes?) binding to a SpriteVisualElement that gets created in to ToggleButton – but still no success in setting the hitArea – either of the ToggleButton or the Skin it is using.
public function createHitArea(bitmapData:BitmapData, grainSize:uint=1):SpriteVisualElement
{
var _hitarea:SpriteVisualElement = new SpriteVisualElement();
_hitarea.graphics.beginFill(0x000000, 1.0);
for(var x:uint=0;x<bitmapData.width;x+=grainSize) {
for(var y:uint=grainSize;y<bitmapData.height;y+=grainSize) {
if(x<=bitmapData.width && y<=bitmapData.height && bitmapData.getPixel(x,y)!=0) {
_hitarea.graphics.drawRect(x,y,grainSize,grainSize);
}
}
}
_hitarea.graphics.endFill();
_hitarea.cacheAsBitmap = true;
return _hitarea;
}
]]>

I don't know much about the hitArea property, but I have dealt with buttons that have transparencies. I solve that by adding something like this to my button skin:
<s:Group id="clickTarget" top="0" left="0" bottom="0" right="0">
<s:Rect top="0" left="0" bottom="0" right="0">
<s:fill>
<s:SolidColor color="0xFFFFFF" alpha="0.01" />
</s:fill>
</s:Rect>
</s:Group>

Related

How can I make Flex Busy Indicator load early?

I'm building a mobile app with Flash Builder 4.6 and AIR.
My Main.mxml class -- a ViewNavigatorApplication -- references a splash screen set to a minimum time of 6 seconds. After the splashscreen disappears, there's a period of a couple of seconds during which the content area of the screen is just blank white -- and that's when I need the Busy Indicator to appear.
EDIT: Additional Code, as requested:
In the Main.mxml class:
protected function viewnavigatorAppInitializeHandler(event:FlexEvent):void
{
//checks various criteria to determine which data to load, ie which service call
// to make. When that is determined, code calls setUpModel().
}
private function setUpModel():void {
Model.Instance.initialize(); //adds listeners in the model,
//makes service call. Data is returned and parsed in the event handler.
//APPLICATION_MODEL_LOADED event is then dispatched, and handled here
Model.Instance.addEventListener( Model.APPLICATION_MODEL_LOADED, modelReadyHandler );
}
private function modelReadyHandler(e:Event):void
{
//Busy Indicator only appears *after* the HomeListView is loaded.
//I need it to appear *while* these calls are being made.
navigator.pushView(HomeListView); //this is my First View
}
So I think the question is: what is it that is being displayed before HomeListView? If it's Main.mxml -- the ViewNavigatorApplication -- then how do I avoid the 'not assignable' error?
In HomeListView:
[Bindable] private var categoryList:ArrayCollection = new ArrayCollection();
private function viewActivateHandler():void
{
//the service call has been made, and data is ready to be loaded into the control
categories = Model.Instance.Categories;
I've tried this inside HomeListView:
<s:Group width="100%" height="100%">
<s:BusyIndicator
id="busy"
visible="true" symbolColor="blue"
horizontalCenter="0" verticalCenter="0"
rotationInterval="100" />
</s:Group>
It works. But the problem is it only appears once HomeListView has loaded. I thought that the blank white was HomeListView, just before the list appeared. But it isn't.
So then I tried putting the above code into the Main.mxml file. But I got the following error:
'spark.components.Group' is not assignable to the default property, 'navigationStack', of type 'spark.components.supportClasses.NavigationStack'.
So where can I put this BusyIndicator so that it covers the blank space before HomeListView appears?
I've figured out a way to do this.
From the documentation:
"Unlike Application, ViewNavigatorApplication is not meant to accept UIComponent objects as children. Instead, all visual components should be children of the views managed by the application."
That is why I was getting the error. So for my first View, I created a simple View called HolderView which contains the BusyIndicator:
<?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="HolderView">
<s:Group width="100%" height="100%">
<s:BusyIndicator
id="busy"
visible="true" symbolColor="blue"
horizontalCenter="0" verticalCenter="0"
rotationInterval="100" />
</s:Group>
Then, from my Main.mxml file, I first load that HolderView.
private function setUpModel():void {
Model.Instance.initialize();
navigator.pushView(HolderView);
Model.Instance.addEventListener(Model.APPLICATION_MODEL_LOADED, modelReadyHandler );
}
Once the data has come back -- on the Model.APPLICATION_MODEL_LOADED event listener -- that is when I will load HomeListView:
private function modelReadyHandler(e:Event):void
{
navigator.pushView(HomeListView);
}
This works. Hope it helps someone with same problem!

Drag area in Flex HGroup is smaller than the group itself

I have an HGroup that I want to drag elements onto. In order to style the HGroup, it's really a Group with a Rect and an HGroup within it.
The drag area in this code is only as large as the elements that are currently in the group (that's why I put a button in there to test it). As you can see, I've manually set the width of the Group and HGroup. Everything reports itself to be 300 wide, as it should be, but the drag area remains only as large as the number of elements in the HGroup. This makes me think the HGroup isn't actually what it's reporting to be (it reports 300 from accessing the .width property after its been set). The Rect is properly 300 wide, so I can see what the drop zone should be.
I feel like I must be doing something very obviously wrong, but I can't see it. Any ideas would be very appreciated!
EDIT: I wonder if there is some sort of masking issue. Adding a drag enter event on the rectangle (which I know is the right size, as I can see it) also doesn't result in the enter event firing. How could I find this out?
package components
{
import mx.core.IUIComponent;
import mx.events.DragEvent;
import mx.graphics.SolidColor;
import mx.managers.DragManager;
import spark.components.Group;
import spark.components.HGroup;
import spark.components.VGroup;
import spark.primitives.Rect;
public class TestVerticalConstraintExpression extends Group
{
private var expressions:VGroup;
private var topExpression:Group;
private var locked:Boolean = false;
public function TestVerticalConstraintExpression()
{
super();
this.width = 300;
expressions = new VGroup();
expressions.width = 300;
this.addElement(getBackground());
topExpression = new HGroup();
expressions.addElement(topExpression);
this.addElement(getBackground());
this.addElement(expressions);
topExpression.width = 300;
topExpression.height = 50;
topExpression.addElement(new ConstraintButton());
// Setup listeners for when things are dropped onto the expression group
topExpression.addEventListener(DragEvent.DRAG_ENTER,
dragEnterHandler);
}
protected function getBackground():Rect
{
var bg:Rect = new Rect();
bg.fill = new SolidColor(Math.round(Math.random()*0xFFFFFF));
bg.left = 0;
bg.right = 0;
bg.top = 0;
bg.bottom = 0;
bg.percentWidth = 100;
bg.percentHeight = 100;
return bg;
}
protected function dragEnterHandler(event:DragEvent):void {
DragManager.acceptDragDrop(event.currentTarget as IUIComponent);
}
}
}
A Group is essentially just a layout container. It has no graphic elements whatsoever, hence it is completely transparent except where you've put your ConstraintButton, which is why you can only drop onto that ConstraintButton and nowhere else in the HGroup.
So you must create a "hitzone" with a graphic element that has the same size as your HGroup. It may have its alpha property set to 0 so that you don't see it, but still can interact with it. The two simplest ways I can think of:
convert your HGroup to a BorderContainer with an Horizontallayout
put the HGroup in a Group together with a background Rect; something like this:
.
<s:Group id="topExpression">
<s:Rect left="0" right="0" top="0" bottom="0">
<s:fill>
<s:SolidColor alpha="0" />
</s:fill>
</s:Rect>
<s:HGroup id="yourOldTopExpression" left="0" right="0" top="0" bottom="0" />
</s:Group>
That said, why do you want to write a "view" in ActionScript? It makes the code so much more verbose (i.e. more work) and harder to read. Not to mention you make mistakes, like adding the background Rect twice to the main Group, which you would have spotted immediatley if your code were written in MXML.
And perhaps more importantly: because you put the subcomponent creation code in the constructor of your main class, your code will actually result in performance loss. You have to know and understand the Flex component lifecycle if you want to write components in pure ActionScript. MXML handles all this nitty-gritty for you.
If you're worried about mixing up MXML and ActionScript, you should read up on the Spark skinning architecture. (For an example of that, you could check out this answer which uses Spark skinning: Custom Composite Control not rendering correctly for only 0.5-1 sec after being added back into a VGROUP).

Question on bindable variables in AS3

I'm probably misusing bindable variables here, so please bear with me here while I try and explain what I'm trying to to.
I have a simple spark list where I allow people to select a background by clicking on one of the items. When a background is selected, I then save it to the SharedObject in order to use it when the user loads the application again later.
This list is populated by an ArrayCollection (binded variable) created as follows:
[Bindable] private var arrBG:ArrayCollection = new ArrayCollection();
This then gets populated the following way:
var objImage:Object;
var strSharedObjImage:String = sharedObj.sharedBackground.data.backgroundIndex;
// Background
objImage = new Object();
objImage.icon = ICONS_PATH + objImage.label;
objImage.label = "Titanium";
objImage.selected = (strSharedObjImage == objImage.fileName) ? true : false;
arrBG.addItem(objImage);
objImage = new Object();
objImage.icon = ICONS_PATH + objImage.fileName;
objImage.label = "Iron";
objImage.selected = (strSharedObjImage == objImage.label) ? true : false;
arrBG.addItem(objImage);
I then use it as the dataProvider on my spark list.
If you notice above, on my object I have a property called selected, which will get set to true, if the value of my shared object is the same as the value on the "label" property.
On the item renderer for my spark list, I have the following:
<s:ItemRenderer name="HorizontalListSkin"
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
autoDrawBackground="false"
creationComplete="initMenuSkin(event)"
>
<fx:Script>
<![CDATA[
protected function initMenuSkin(event:Event):void
{
iconImage.source = data.icon;
iconText.text = data.label;
// Check to see if the item we're displying is selected. If it is make it stand out
if(data.selected){
iconText.setStyle("color", "Green")
}
}
]]>
</fx:Script>
<s:VGroup x="10" y="10" width="50" height="50" buttonMode="true" horizontalAlign="center">
<s:Image id="iconImage" horizontalCenter="0"/>
<s:Label id="iconText" fontFamily="Verdana" fontSize="11" fontWeight="bold" horizontalCenter="0" showTruncationTip="false"/>
</s:VGroup>
</s:ItemRenderer>
So as you can see, I'm simply changing the colour of the font on my selected item.
When I load it up, I can see that the item I have previously selected is marked in green, and if I select a new item, I would like it to now be marked as green instead.
Obviously there's a big gap in here, since nowhere in my explanation above I mention updating my bindable variable so in theory it wold propagate to my spark list (being it a bindable variable I would of thought it would simultaneously update the item on my list(?)).
Well, I have tried doing it in a few different ways, and the debugger does "say" my array has been updated, however, my list isn't being updated at all, and will only bring another item marked in green if I close the screen and open again (when it all gets reloaded)
The whole logic described above to create a new background is contained within a function, so whenever I select an item from my list of backgrounds I was triggering my "loadBackgrounds" method again, which would apply all the logic to know which is the selected background, and because the variable is binded with my spark list I'd have hoped would update the list. Thing is, it doesn't.
What am I doing wrong here? Am I going totally bonkers and there's a much easier way of doing this but only I can't see it?
Any help here would be appreciated.
Thanks in advance
After you set the data ion the collection you need to refresh it.
arrBG.refresh();
[EDIT]
Ok I re-read your question.
I think I misunderstood what you were asking.
You want to know how to update the list so the item renderer will re-render the new list after you made changes to the data provider?
function newSelection( val:String ):void{
for each( var item:Object in arrBG ){
if( item.label == val ){
item.selected = true;
}else{
item.selected = false;
}
}
arrBG.refresh();
}
// use the commit properties on your renderer not the init
// commit properties will trigger whenever there is a dataprovider update/change
<s:ItemRenderer name="HorizontalListSkin"
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
autoDrawBackground="false"
>
<fx:Script>
<![CDATA[
override protected function commitProperties():void{
super.commitProperties();
iconImage.source = data.icon;
iconText.text = data.label;
// Check to see if the item we're displying is selected. If it is make it stand out
if(data.selected){
iconText.setStyle("color", "Green")
}
}
]]>
</fx:Script>
<s:VGroup x="10" y="10" width="50" height="50" buttonMode="true" horizontalAlign="center">
<s:Image id="iconImage" horizontalCenter="0"/>
<s:Label id="iconText" fontFamily="Verdana" fontSize="11" fontWeight="bold" horizontalCenter="0" showTruncationTip="false"/>
</s:VGroup>
</s:ItemRenderer>

How can I edit the objects within custom AS3 components using MXML?

I'm writing a Flex application using Flash Builder 4 and I'm having a bit of trouble with an AS3 object. Essentially, it is a BorderContainer, with a few buttons and images, and programming logic that determines how these interact with eachother and a database.
What I want to be able to do is configure the layout/style of the inner components using MXML and CSS. I can configure the inherited objects, but not ones that I have defined...
For example, in my MXML. I can modify the (inherited) borderstroke variable of myContainer like so;
<IE:MyContainer>
<IE:borderStroke>
<s:LinearGradientStroke weight="10" rotation="270">
<s:GradientEntry color="0xF655E5"/>
<s:GradientEntry color="0x6600CC"/>
</s:LinearGradientStroke>
</IE:borderStroke>
</IE:MyContainer>
However, I can't edit the nextButton variable (which is of type Button) like this;
<IE:MyContainer>
<IE:nextButton width="100" height="30" left="10%" bottom="10%"/>
</IE:MyContainer>
If I try, I get the compile error "Could not resolve to a component implementation".
What do I need to do to make this work?!
Thanks in advance,
Aidan
EDIT:
Here's the main method of MyContainer (actually named InvestigativeEnvironment).
The call to defineTestInvestigativeEnvironment() is what takes care of setting up the objects and action listeners and such. What I want to do is change the layout and appearance of these visual components in MXML (nextButton, prevButton, toolbox, displayArea). I want to be able to set their height, width, background, x, y, horizontalCenter, etc like I can to a button that I add to a container via MXML.
public class InvestigativeEnvironment extends BorderContainer
{
private var toolbox:Toolbox;
private var bodySystem:BodySystem;
public var nextButton:Button;
public var prevButton:Button;
private var displayArea:Group;
private var image:Image;
private var toolDisplayArea:Group;
public function InvestigativeEnvironment()
{
super();
//create 'Next' button and event listener
nextButton = new Button();
nextButton.addEventListener(MouseEvent.CLICK, nextViewAngle);
nextButton.label = "Next";
this.addElement(nextButton);
//create 'Prev' button and event listener
prevButton = new Button();
prevButton.addEventListener(MouseEvent.CLICK, prevViewAngle);
prevButton.label = "Prev";
this.addElement(prevButton);
//define investigative environment by creating models.
defineTestInvestigativeEnvironment();
//Instantiate the Group that contains the model image and tool overlays
displayArea=new Group();
//Instantiate the image that is used to display the model
image = new Image();
image.source=bodySystem.getImage();
image.horizontalCenter=0;
image.verticalCenter=0;
displayArea.addElement(image);
//add toolOverlayContainer to the display area ABOVE the model image
toolDisplayArea = new Group();
toolDisplayArea.verticalCenter=0;
toolDisplayArea.horizontalCenter=0;
displayArea.addElement(toolDisplayArea);
this.addElement(displayArea);
//add toolbox to display
toolbox = new Toolbox(toolDisplayArea);
toolbox.replaceTools(bodySystem.getToolGroup());
this.addElement(toolbox);
}
I can't understand what is your problem with editing button in particular, sorry for that. But I have a lot of notices about your InvestigativeEnvironment which code you've attached.
First, you haven't follow Flex components livecycle (see here or here). So in your code you should add and configure children in createChildren() method.
But anyway you can't use your container to add children both with MXML and from code. If your adding custom components code will be executed first MXML (in your implementation with adding them in constructor it is so) all the MXML tags just remove all your added content (anyway result will be unpredictable). In other case it will be very hard to control instance's order.
I can suggest you to declare your nextButton etc as skin parts and perform their positioning in skin. This way these internal controls will be a part of border and you can add MXML children without any problem. And you can configure them within partAdded() method.
It turns out that I wasn't quite asking the right question. I wanted to edit the components, but specifically the layout and color type attributes of them.
What I need to do is set the id of the components, and then target them using CSS.
For my nextButton, I add the ID like this;
nextButton.id="nextButton";
Then I can lay it out in the MXML file (or external stylesheet) like this;
<fx:Style>
#namespace s "library://ns.adobe.com/flex/spark";
#namespace IE "InvestigativeEnvironment.*";
IE|InvestigativeEnvironment s|Button {
chromeColor: #336666;
}
#nextButton {
bottom: 100;
right: 5;
}
</fx:Style>

How can I "unaccept" a drag in Flex?

Once I've called DragManager.acceptDrag is there any way to "unaccept" the drag? Say that I have a view which can accept drag and drop, but only in certain areas. Once the user drags over one of these areas I call DragManager.acceptDrag(this) (from a DragEvent.DRAG_OVER handler), but if the user then moves out of this area I'd like to change the status of the drag to not accepted and show the DragManager.NONE feedback. However, neither calling DragManager.acceptDrag(null) nor DragManager.showFeedback(DragManager.NONE) seems to have any effect. Once I've accepted the drag an set the feedback type I can't seem to change it.
Just to make it clear: the areas where the user should be able to drop are not components or even display objects, in fact they are just ranges in the text of a text field (like the selection). Had they been components of their own I could have solved it by making each of them accept drag events individually. I guess I could create proxy components that float over the text to emulate it, but I'd rather not if it isn't necessary.
I've managed to get it working in both AIR and the browser now, but only by putting proxy components on top of the ranges of text where you should be able to drop things. That way I get the right feedback and drops are automatically unaccepted on drag exit.
This is the oddest thing about D&D in AIR:
DragManager.doDrag(initiator, source, event, dragImage, offsetX, offsetY);
In browser-based Flex, offsetX and offsetY should be negative (so says the documentation, and it works fine). However, when running exactly the same code in AIR you have to make the offsets positive. The same numbers, but positive. That is very, very weird.
I've tested some more and what #maclema works, but not if you run in AIR. It seems like drag and drop in AIR is different. It's really, really weird because not only is the feedback not showing correctly, and it's not possible to unaccept, but the coordinates are also completely off. I just tried my application in a browser instead of AIR and dragging and dropping is completely broken.
Also, skipping the dragEnter handler works fine in AIR, but breaks everything when running in a browser.
Are you using only the dragEnter method? If you are trying to reject the drag while still dragging over the same component you need to use both the dragEnter and dragOver methods.
Check out this example:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:Script>
<![CDATA[
import mx.core.DragSource;
import mx.managers.DragManager;
import mx.events.DragEvent;
private function onDragEnter(e:DragEvent):void {
if ( e.target == lbl ) {
if ( e.localX < lbl.width/2 ) {
trace("accept");
DragManager.acceptDragDrop(this);
}
else {
DragManager.acceptDragDrop(null);
}
}
}
private function doStartDrag(e:MouseEvent):void {
if ( e.buttonDown ) {
var ds:DragSource = new DragSource();
ds.addData("test", "text");
DragManager.doDrag(btn, ds, e);
}
}
]]>
</mx:Script>
<mx:Label id="lbl" text="hello world!" left="10" top="10" dragEnter="onDragEnter(event)" dragOver="onDragEnter(event)" />
<mx:Button id="btn" x="47" y="255" label="Button" mouseMove="doStartDrag(event)"/>
</mx:Application>
If you don't need native drag and drop in AIR, you can get the Flex drag and drop behavior by subclassing WindowedApplication and setting the DragManager. See this post on the Adobe Jira for more info: https://bugs.adobe.com/jira/browse/SDK-13983
You are misunderstanding the concept. Your "unaccept" is achieved by implementing the dragOverHandler and signaling that the data is not wanted.
Here is the basic concept:
register the dragEnterHandler or override the already registered method.
function dragEnterHandler(event: DragEvent):void {
if (data suites at least one location in this component)
DragManager.acceptDragDrop(this);
}
This enables your container to receive further messages (dragOver/dragExit). But this is NOT the location to decide which kind of mouse cursor should be displayed.
Without DragManager.acceptDragDrop(this); the other handlers aren't called.
register the dragOverHandler or override the already registered method.
function dragOverHandler(event: DragEvent):void {
if (data suites at least no location in this component) {
DragManager.showFeedback(DragManager.NONE);
return;
}
... // handle other cases and show the cursor / icon you want
}
Calling DragManager.showFeedback(DragManager.NONE); does the trick to display the "unaccept".
register the dragExitHandler or override the already registered method.
function dragOverHandler(event: DragEvent):void {
// handle the recieved data as you like.
}
ok, I see the problem now. Rather than null, try setting it to the dragInitiator.
Check this out.
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:Script>
<![CDATA[
import mx.controls.Alert;
import mx.events.DragEvent;
import mx.managers.DragManager;
import mx.core.DragSource;
private function doStartDrag(e:MouseEvent):void {
if ( e.buttonDown && !DragManager.isDragging ) {
var ds:DragSource = new DragSource();
ds.addData("test", "test");
DragManager.doDrag(btn, ds, e);
}
}
private function handleDragOver(e:DragEvent):void {
if ( e.localX < cvs.width/2 ) {
//since null does nothing, lets just set to accept the drag
//operation, but accept it to the dragInitiator
DragManager.acceptDragDrop(e.dragInitiator);
}
else {
//accept drag
DragManager.acceptDragDrop(cvs);
DragManager.showFeedback( DragManager.COPY );
}
}
private function handleDragDrop(e:DragEvent):void {
if ( e.dragSource.hasFormat("test") ) {
Alert.show("Got a drag drop!");
}
}
]]>
</mx:Script>
<mx:Canvas x="265" y="66" width="321" height="245" backgroundColor="#FF0000" id="cvs" dragOver="handleDragOver(event)" dragDrop="handleDragDrop(event)">
</mx:Canvas>
<mx:Button id="btn" x="82" y="140" label="Drag Me" mouseDown="doStartDrag(event)"/>
</mx:WindowedApplication>
Yes, drag and drop is different in AIR. I HATE that! It takes a lot of playing around to figure out how to get things to work the same as custom dnd that was built in flex.
As for the coordinates, maybe play around with localToContent, and localToGlobal methods. They may help in translating the coordinates to something useful.
Good luck. I will let you know if I think of anything else.