Drag area in Flex HGroup is smaller than the group itself - actionscript-3

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).

Related

Defining hitArea on Spark ToggleButton

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>

How To Control Icons in TabbedViewNavigator tabs for Flex Mobile

As currently developing an mobile application with Flex Mobile, got to the point of finalising things and make some fine visual layout adjustments. As my Application is built in sections using TabbedViewNavigatorApplication i wanted to add and fine tune icons that appear in the TabbedViewNavigator #tabBar tabs. Well, that was one of those tasks that seems to take minutes, ending in searching Adobe api docs and googling around for hours, eventually to find the solution in days. Said so i want to post my solution, hoping that someone will need it and use it in their own specific situations.
The first thing to mention here is that Adobe per default, has an extensive help library for skinning spark components, but eventually and on the end - those examples often only scratch the surface of what is needed in specific situation and / or implementation.
At first i want to say that I avoid using mxml skins and always do my spark skinning job in AS3 classes using ClassReference. Seems to me that this approach is much more flexible, elegant and cleaner than over-spamming things in mxml. Although sometimes more harder to implement.
So your typical minimal TabbedViewNavigator Application looks like this:
<?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" applicationComplete="onApplicationComplete(event)">
<fx:Style source="MyStyles.css" />
<fx:Script>
<![CDATA[
import spark.components.ViewNavigator;
private function onApplicationComplete(e:Event):void {
for each(var vn : ViewNavigator in this.navigators){
/// do something with your VN's ...
vn.icon = "./assets/myFaboulousIcon.png";
}
}
]]>
</fx:Script>
<s:ViewNavigator label="one" width="100%" height="100%" firstView="views.oneView"/>
<s:ViewNavigator label="two" width="100%" height="100%" firstView="views.twoView"/>
<s:ViewNavigator label="three" width="100%" height="100%" firstView="views.threeView"/>
</s:TabbedViewNavigatorApplication>
In this typical configuration you can access the ViewNavigator icon property and set it either in your mxml or from AS3 (eg. in the for loop above) eventually adding a switch statement to select different icons for each tab ... But that is where your story ends. ViewNavigator icon property is used when certain ViewNavigator is represented by a visual component. Which means that in your #tabBar the ViewNavigator is represented visually as a part of navigators stack, and is logical - in this situation it uses the icon specified.
Now let's suppose now you want to change the alpha or colorise your icons programatically, meaning you always use one set of icons but in different situations - you give them different visual properties, and one of those situations can be - their visual appeal in the application #tabBar.
The icon property is a path to the icon image file, and as a type is an generic Object. So you can't change alpha or do anything else with it. Neither icon property will give you any reference to the display object containing the icon itself or anything else you want for your juicy AS3 jonglery pokery ...
For such task we must do some nice spark skinning (in AS3) ;) So read the answer bellow
The first step is to add your css file from example above MyStyles.css (i always do skin class references from within css, found it easy to manage and change if needed) ...
/* MyStyle.css example */
.
.
s|TabbedViewNavigator #tabBar {
skinClass: ClassReference("com.myapp.skins.TabBarSkin");
}
.
.
You must now define your custom TabBarSkin class that can look something like this:
/* TabBarSkin Class */
package com.myapp.skins
{
import spark.skins.mobile.TabbedViewNavigatorTabBarSkin;
import spark.skins.mobile.supportClasses.ButtonBarButtonClassFactory;
import spark.components.ButtonBarButton;
public class TabBarSkin extends TabbedViewNavigatorTabBarSkin
{
public function TabBarSkin() {
super();
}
override protected function createChildren():void
{
if (!firstButton) {
firstButton = new ButtonBarButtonClassFactory(ButtonBarButton);
firstButton.skinClass = TabBarFirstTabSkin;
}
if (!lastButton) {
lastButton = new ButtonBarButtonClassFactory(ButtonBarButton);
lastButton.skinClass = TabBarLastTabSkin;
}
if (!middleButton) {
middleButton = new ButtonBarButtonClassFactory(ButtonBarButton);
middleButton.skinClass = TabBarLastTabSkin;
}
super.createChildren();
}
}
}
Not getting so much in details, you must know that this custom class TabBarSkin inherits from TabbedViewNavigatorTabBarSkin which has 3 skin classes for each of the significant tab positions / first / mid / last / in your #tabBar. In the most simple situation we must implement (extend) two of them / first / and / last -> as the / mid / position also uses the / last / skin, and in this case we don't need it separately implemented.
/* TabBarFirstTabSkin Class */
package com.myapp.skins
{
import spark.components.Group;
import spark.skins.mobile.TabbedViewNavigatorTabBarFirstTabSkin;
public class TabBarFirstTabSkin extends TabbedViewNavigatorTabBarFirstTabSkin
{
private var __iconGroup : Group = null;
public function TabBarFirstTabSkin() {
super();
}
override protected function layoutContents(unscaledWidth : Number, unscaledHeight : Number) : void {
super.layoutContents(unscaledWidth, unscaledHeight);
if(!__iconGroup) {
__iconGroup = getIconDisplay() as Group;
}
}
}
}
Same as for the / last / one:
/* TabBarLastTabSkin Class */
package com.myapp.skins
{
import spark.components.Group;
import spark.skins.mobile.TabbedViewNavigatorTabBarLastTabSkin;
public class TabBarLastTabSkin extends TabbedViewNavigatorTabBarLastTabSkin
{
private var __iconGroup : Group = null;
public function TabBarLastTabSkin() {
super();
}
override protected function layoutContents(unscaledWidth : Number, unscaledHeight : Number) : void {
super.layoutContents(unscaledWidth, unscaledHeight);
if(!__iconGroup) {
__iconGroup = getIconDisplay() as Group;
}
}
}
}
Finally, the __iconGroup member will now have reference to the Group visual component containing your icon! -> and which you defined in your ViewNavigator instance through mxml / or in AS3 code. Now we can go dirty ;) and do things like this for example:
.
.
.
var colorTransform : ColorTransform = new ColorTransform();
colorTransform.color = 0xFF3300;
colorTransform.alphaMultiplier = 0.85;
__iconGroup.transform.colorTransform = colorTransform;
.
.
.
Which will colorise your icon in red and give alpha 0.85. etc ... This is really the basics you can do with spark #tabBar skinning in Flex Mobile. Hope will help someone. Cheers.

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 to create dynamic variable vbox in actionscript

I have to create several vbox-es in a for each loop.
Now I want to do something like this.
formsArray["vb"+counter] = new VBox;
formsArray["vb"+counter].visible = true;
add labels etc.
I can't get this thing to work. Anybody any idea how to create dynamic variable names for my vbox-es?
Thanks
First off, to use an associative array, you need to use an Object and not an Array (perhaps you already are, then never mind).
You can achieve what you want to do the following way:
var vbox:VBox;
var formsArray:Object = new Object();
var counter:int = 0;
for each(<statement>)
{
vbox = new VBox();
formsArray[("vb" + counter.toString())] = vbox;
counter++;
}
The VBox's visible property is true by default, so no need to explicitly set it.
Answer to additional question in comments:
You don't really need to make use of dynamic references to do what you want to do. You'd be best of creating a custom component for this, extending the VBox class, by creating a new MXML class with VBox as the root tag. Something along these lines:
<mx:VBox ... >
<mx:Button ... click="btnClickHandler()"/>
<mx:Script>
<![CDATA[
// Toggles visibility of the VBox
private function btnClickHandler():void
{
visible = !visible;
}
]]>
</mx:Script>
</mx:VBox>
Then you can just instantiate as many of these custom VBox:es as you need. However, making the VBox invisible will make the contained button invisible as well, making it difficult to click it again. :) You probably want to address that. Anyways, I hope this will point you in the right direction.

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.