I have made a hierarchy in which there is a main page, using add element i have attached a component mxml of type group. There is a single button on main page when clicked it should add children of type group in that group type mxml component along with two buttons. Now using one of buttons i am attaching another component mxml type group. the problem is even they overlap i can still excess the children groups of first group component mxml. how can i stop this mouse events to happen.
I think those kind of events usually bubble up to parent components.
You can try using the following code in your mouse click event listener to stop further propagation:
private function onMouseClicked(event: MouseEvent): void {
event.stopPropagation();
... do whatever you wanted when smth was clicked ...
}
By setting enabled, mouseChildren, mouseEnabled to false, you will disable the entire component and it's children. example below
private var myPreviousGroupComponent:Group = null;
function addNewGroup():void
{
if(myPreviousGroupComponent != null)
{
myPreviousGroupComponent.enabled = false;
myPreviousGroupComponent.mouseChildren = false;
myPreviousGroupComponent.mouseEnabled = false;
}
var newGroup:Group = new Group();
addElement(newGroup);
myPreviousGroupComponent = newGroup;
}
Related
I have implemented a custom clickable label class in Xamarin.Forms along with a custom renderer, that adds a RippleDrawable as the controls Foreground. I am creating the RippleDrawable with the following code:
public static Drawable CreateRippleDrawable(Context context)
{
var typedValue = new TypedValue();
context.Theme.ResolveAttribute(Resource.Attribute.SelectableItemBackground, typedValue, true);
var rippleDrawable = context.Resources.GetDrawable(typedValue.ResourceId, context.Theme);
return rippleDrawable;
}
In my custom renderer I assign the drawable
this.Control.Foreground = DrawableHelper.CreateRippleDrawable(this.Context);
and update the ripple when the user touches the control
private void LinkLabelRenderer_Touch(object sender, TouchEventArgs e)
{
if (e.Event.Action == MotionEventActions.Down)
{
this.Pressed = true;
}
if (e.Event.Action == MotionEventActions.Cancel)
{
this.Pressed = false;
}
if (e.Event.Action == MotionEventActions.Up)
{
this.Ripple.SetHotspot(e.Event.GetX(), e.Event.GetY());
this.Pressed = false;
// raise the event of the Xamarin.Forms control
}
}
Now, whenever I click the control, the ripple will be shown, which is the expected behavior, but if I touch (tap or long-press) the parents of the control (e.g. the StackLayout, Grid or whatever layout contains the label, including their parent Layout, Page or View) the ripple animation will be triggered. Anyway, the event handler LinkLabelRenderer_Touch in not called in this case, only when the actual control is touched.
I can work around this behavior by adding an empty GestureRecognizer to the respective parent(s), but I really dislike this solution, because this is but a hack. And to make things worse it is a hack I'll always have to remember whenever I use the control.
How can I prevent the RippleDrawable being shown when the parent is touched?
Turned out I got things fundamentally wrong. Subscribing the Touch event is not the way to go. I had to make the control clickable and subscribe the Click event
this.Control.Clickable = true;
this.Click += LinkLabelRenderer_OnClick;
There is no need to handle all that RippleTouch stuff the way I did (via the Touch event) but could let android handle things for me.
I've created a custom component with several inline item renderers that I use as a tooltip. The height of the component is unknown, as the data contents of the component are not known until runtime.
However, when displaying the tooltip, occasionally it extends beyond the boundaries of the flash application, thus, I'd like to be able to detect this occurrence and reposition the tip.
The problem is that the height and width of the component are, apparently, not available until after being rendered by the popup manager. (i.e. they are always 0)
But, I do not know any way of finding out when the popup is actually rendered and, therefore, the height/width values available.
I tried adding a resize event listener to the component, but it doesn't appear to work, though I most certainly could be doing something wrong since it seems to me that the resize event only gives you the "oldWidth" and "oldHeight" of the object, which, at first display, would be 0...and useless to me.
Any ideas about how to proceed?
-----Edit-----
I have a base class like this:
public class TTComponent extends Canvas
{
var _parentC:UIComponent;
var popped:Boolean = false;
var timer:Timer;
var _comp:UIComponent;
public function set parentComponent(pC:UIComponent):void
{
_parentC = pc;
_parentC.addEventListener(MouseEvent.MOUSE_OUT, mouseOut);
_parentC.addEventListener(MouseEvent.MOUSE_OVER, mouseOver);
}
public function mouseOver(evt:MouseEvent):void
{
if (_parentC != null)
{
timer = new Timer(150,1);
_comp = this;
timer.addEventListener(TimerEvent.TIMER_COMPLETE, function( tevt:TimerEvent ):void
{
this.move( somex, somey);
if (popped != true)
{
PopUpManager.addPopUp(_comp, parentComponent );
popped = true;
});
timer.start();
}
}
public function mouseOut(evt:MouseEvent ):void
{
if ( timer )
{
timer.stop();
timer = null;
}
//If we popped up, remove the popup
if ( popped )
{
PopUpManager.removePopUp( _comp );
popped = false;
parentC .removeEventListener(MouseEvent.MOUSE_OUT, mouseOut);
parentC .removeEventListener(MouseEvent.MOUSE_OVER, mouseOver);
}
}
}
Then, an extended renderer like this:
<c:TTComponent name="T" xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:c="components.*">
<s:BorderContainer>
...about 30 labels grouped in various manners
...2 lists with inline item renderers
</s:BorderContainer>
</c:TTComponent>
Now, the code is called like this:
var w = new TTComponent();
w.data = data;
win.parentComponent = this;
This will add listeners to the mouse over and mouse out events on the parent, whatever it is, and then show or hide the tooltip accordingly.
------Edit------
Using a portion of what a commenter below suggested, this is the solution I came up with:
Inside the TTComponent class:
import flash.events.Event;
import mx.binding.utils.ChangeWatcher;
private var heightWatcher:ChangeWatcher;
public function set parentComponent
{
...
heightWatcher = ChangeWatcher.watch(this,'height',onSizeChange);
}
public function onSizeChange(evt:Event):void
{
if (this.height != 0)
{
....calculate the new component coords
this.move(newx, newy);
}
}
Note that this additional code doesn't bind to any component variable, it just adds a watcher on the component property.
You could also try binding your width and height. If these are made bindable in your class, flex will automatically adjust your popup's width and height.
When using mxml for your binding, you can just do something like this
<mx:YourComponent height="{HeightOfYourTooltip}" width="{WidthOfYourTooltip}"></mx:YourComponent>
You can also add a eventListener that listens to the change event if you want to reposition you component, like so
<mx:YourComponent height="{HeightOfYourTooltip}" width="{WidthOfYourTooltip}" change="yourComponentResizeHandler()"></mx:YourComponent>
If you are using a programmed approach, you should should use the changewatcher. Below is shown how you can use that.
ChangeWatcher.watch(YourComponent, "width", repositionHandler);
ChangeWatcher.watch(YourComponent, "height", repositionHandler);
If you want to watch for other variables or properties to change, be sure to add the [Bindable]-tag above your variables in your class, like this
[Bindable]
var myVariable:SomeVariable;
I hope this helps.
For displaying toolTip which controls you are using in itemRenderer? Text or Label?
Try to Listen update complete Event of that component. May this Help you. :)
This might be messy, but on the pop up component, you could add an event listener after complete is fired, if the height or width == 0 then you setTimeout() to a function after say 100ms until you get valid data.
Yes, I know it is a bit of a hack, but those will eventually report correctly measured values so it's not going to call that many times.
Just an idea if you are against a deadline or something like this isn't critical. :)
I've a parent (Canvas) with many children (LinkButtons)
The linkButtons trigger an event to communicate between them:
dispatchEvent(new SameBookmarkEvent("SameBookmarkEvent", bookmark.name));
and all linkButtons have a listener
this.addEventListener("SameBookmarkEvent", highlightMe);
...
private function highlightMe(e:SameBookmarkEvent):void {
//do something
}
Now, the issue is that the event is only listened by the dispatcher child. In other words, only the child triggering the event, is receiving it. I was wondering what's wrong with it, and if I should add a listener to the parent (Canvas)...
I basically need the children (LinkButton) communicate between them
Yes, when you add the button's listener to a function within that object, of course only that object will receive the event. When you call addEventListener with the highlightMe function, you pass a reference to the highlightMe function within the current scope. That means that the private function within that class is referenced. But that function is different for each new instance of the class.
In OOP each object works for itself and doesn't know anything about the parents, so one link button should not be able to know that there are other link buttons beside itself and what those buttons do. Instead the parent knows that there are X different link buttons, each working alone, but managed by the parent to work together. In that sense, when working with events, one should always leave the event handling in the parent object – except your custom class has default handlers that keep a standard procedure working (like changing appearance on mouse over etc), or when you have custom events that encapsulate other events.
As such the correct way to deal with it, is to have a single event handler in the parent class (where you instantiate the buttons) which then also is able to identify which button the event is related to.
Example
public class SomeParent extends Sprite
{
public function SomeParent ()
{
var btn:Button;
for ( var i:uint = 0; i < 100; i++ )
{
btn = new Button();
btn.label = 'Button ' + i;
btn.addEventListener( MouseEvent.CLICK, clickHandler );
this.addChild( btn );
}
}
private function clickHandler ( event:MouseEvent ):void
{
var btn:Button = event.eventTarget as Button;
trace( 'Button with id ' + this.getChildIndex( btn ) + ' and label "' + btn.label + '" was pressed.' );
}
}
for (iss = 0; iss < listOfProductIds2.length; iss++)
{
// Alert.show(listOfProductIds2[iss]);
var productMain:VBox=new VBox();
var p1:HBox=new HBox();
var l1:Label=new Label();
var b1:Button=new Button();
var spacer:Spacer=new Spacer();
spacer.width=300;
b1.label="Remove";
b1.setConstraintValue("id","");
b1.addEventListener(MouseEvent.CLICK,removeProduct);
l1.text="Product "+iss;
p1.setActualSize(500,500);
p1.addChild(l1);
p1.addChild(spacer);
p1.addChild(b1);
productMain.addChild(p1);
}
function removeProduct(event:MouseEvent):void
{
// How do i know which button is clicked
}
Use event.currentTarget (instead of event.target) because event.target might be the Label component or some styling component within the button, but currentTarget is assured to be the object with which the listener was registered.
To get a handle to the button that was clicked you can just cast the currentTarget to a button.
function removeProduct(event:MouseEvent):void
{
var b1:Button = Button(event.currentTarget);
}
The method setConstraintValue is for setting layout constraints, not setting id. The id property is used by mxml for creating variable names for objects. You can get/set id as you would get/set any other property (say width) - but neither have I seen anyone doing that nor do I see any need to do that in the first place.
event.target should point to the Button you clicked on, shouldn't it ? However you should probably give ids to the buttons to be able to differenciate them (since you create them dynamically.)
Look at event.target.
If ids are assigned dynamically as in the example given b1.id = "button_" + listOfProductIds2[iss]
Then the function that processes the click event would look at the currenttarget, and what I usually do is do a string replace on the part of the id that you know is not dynamic like "button_" with "", which leaves you with the name of the product.
Flex has built in drag-n-drop for list controls, and allows you to override this. But they don't cover this in examples. The built-in functionality automatically drags the list-item, if you want to override this you find the handlers are being set up on the list itself.
What I specifically want to do, is my TileList shows small thumbnails of items I can drag onto a large Canvas. As I drag an item from the list, the drag proxy should be a different image.
So, I followed the technique suggested and it only works if I explicitly set the width/height on the proxy Image. Why?
It's not obvious until you've tried it =) I struggled with the same thing just a few weeks ago. This was my solution:
The list:
<List>
<mouseDown>onListMouseDown(event)</mouseDown>
</Tree>
The mouse down handler:
private function onMouseDown( event : MouseEvent ) : void {
var list : List = List(event.currentTarget);
// the data of the clicked row, change the name of the class to your own
var item : MyDataType = MyDataType(list.selectedItem);
var source : DragSource = new DragSource();
// MyAwsomeDragFormat is the key that you will retrieve the data by in the
// component that handles the drop
source.addData(item, "MyAwsomeDragFormat");
// this is the component that will be shown as the drag proxy image
var dragView : UIComponent = new Image();
// set the source of the image to a bigger version here
dragView.source = getABiggerImage(item);
// get hold of the renderer of the clicked row, to use as the drag initiator
var rowRenderer : UIComponent = UIComponent(list.indexToItemRenderer(list.selectedIndex));
DragManager.doDrag(
rowRenderer,
source,
event,
dragView
);
}
That will start the drag when the user clicks an item in the list. Notice that I don't set dragEnabled and the other drag-related properties on the list since I handle all that myself.
It can be useful to add this to the beginning of the event handler:
if ( event.target is ScrollThumb || event.target is Button ) {
return;
}
Just to short circuit if the user clicks somewhere in the scrollbar. It's not very elegant but it does the job.
I found a simpler answer here. That example extends a DataGrid control, but you can do the same with a List control. In my case, I use an image source instead of Class:
public class CustomDragList extends List {
[Bindable]
public var dragProxyImageSource:Object;
override protected function get dragImage():IUIComponent {
var image:Image = new Image();
image.width = 50;
image.height = 50;
image.source = dragProxyImageSource;
image.owner = this;
return image;
}
}
Then use that custom list like this:
<control:CustomDragList
allowMultipleSelection="true"
dragEnabled="true"
dragProxyImageSource="{someImageSource}"
dragStart="onDragStart(event)"/>
Where 'someImageSource' can be anything you'd normally use for an image source (embedded, linked, etc.)