My Sprite class keeps losing focus when I click with the mouse - specifically after the MOUSE_DOWN event (before the click is complete).
I have set mouseEnabled to false on the children, no change. I added a listener for FOCUS_OUT and noticed that the FocusEvent.relatedObject property is NULL, which is confusing me - doesn't that mean there is no new focus target, the focus is just getting lost?
The exact sequence of events I get, by tracing them, as I click:
[FocusEvent type="focusOut" bubbles=true cancelable=false eventPhase=2 relatedObject=null shiftKey=false keyCode=0]
[MouseEvent type="mouseDown" bubbles=true cancelable=false eventPhase=2 localX=355 localY=362 stageX=360 stageY=367 relatedObject=null ctrlKey=false altKey=false shiftKey=false buttonDown=true delta=0])
[MouseEvent type="click" bubbles=true cancelable=false eventPhase=2 localX=355 localY=362 stageX=360 stageY=367 relatedObject=null ctrlKey=false altKey=false shiftKey=false buttonDown=false delta=0]
Try setting mouseChildren = false; on the sprite instead of mouseEnabled = false; on the children. If the sprite's children have mouseEnabled set to false, none of the visible elements contained in your sprite are clickable, except for the shapes drawn directly in its own graphics. You would then actually click "through" the children and on the stage.
[EDIT]:
I've created a test to reproduce your problem. You're right - the focus is mysteriously lost when a sprite is clicked on, even though one would expect this to actually set the focus on it. The same is true for MovieClips, but not for TextFields. Unpractical though that may be, you can work around it by adding a custom mouseDown handler:
private function onMouseDown (ev:Event) : void {
if (stage.focus != sprite) stage.focus = sprite;
}
If you extend any InteractiveObject you have to set tabEnabled = true; . This will make your clicked upon object gain focus.
Be careful though:
If tabEnabled is false, but mouseChildren is true, then the stage.focus will be set to null.
Related
I'm new to JavaFX and am trying to monitor the resize events on a window so that I can trigger a recalculation of the layout.
If I create a stage and set the scene like in the example below I only get each resize event to be fired once. No matter how many times I resize the window.
Stage stage = new Stage();
stage.setScene(someScene);
stage.setTitle("Some Title");
stage.initModality(Modality.APPLICATION_MODAL);
stage.show();
stage.widthProperty().addListener(observable -> {
System.out.println("Width changed");
});
stage.heightProperty().addListener(observable -> {
System.out.println("Height changed");
});
First note:
I'm trying to monitor the resize events on a window so that I can
trigger a recalculation of the layout.
This is almost certainly the wrong approach. Layout recalculations will be triggered automatically when the stage and scene change size. If you use standard layout panes, there is no need to register listeners. If you really need a custom layout (which is highly unlikely), you should subclass Pane and override layoutChildren() (and other appropriate methods) to hook into the same layout system.
However, in the interests of explaining what you're observing:
You're registering an InvalidationListener with each property, which gets notified when the property goes from a valid state to an invalid state.
The invalid state only becomes valid again if you actually request the value of the property (e.g. stage.getWidth()). Since you never do that, the property never becomes valid, and hence never goes from valid to invalid again.
Instead, register a ChangeListener with each property:
stage.widthProperty().addListener((observable, oldWidth, newWidth) -> {
System.out.println("Width changed");
});
stage.heightProperty().addListener((observable, oldHeight, newHeight) -> {
System.out.println("Height changed");
});
Alternatively, you can force validation by requesting the value (though I think the change listener above actually gets to the point of what you are trying to do):
stage.widthProperty().addListener(observable -> {
System.out.println("Width changed: "+stage.getWidth());
});
etc.
In QML only a single object can have keyboard focus (per window). In my application, I need the option of having multiple objects with keyboard focus, thus I use a custom event dispatcher in combination with a custom multiple selection implementation.
The problem is however that every time any of the stock Control elements are clicked, they automatically steal the focus, breaking the custom event dispatcher.
In addition to that, it still needs to be possible to explicitly set another focus item, in the case of overlay popups and such.
I'm not sure how it fits in with your custom event stuff, but this answer might also help others who have found your question but are simply looking to prevent a control from getting focus.
You can prevent controls from getting focus with the focusPolicy enum:
Button {
focusPolicy: Qt.NoFocus
// Other options:
// focusPolicy: Qt.TabFocus - The control accepts focus by tabbing.
// focusPolicy: Qt.ClickFocus - The control accepts focus by clicking.
// focusPolicy: Qt.StrongFocus - The control accepts focus by both tabbing and clicking.
// focusPolicy: Qt.WheelFocus - The control accepts focus by tabbing, clicking, and using the mouse wheel.
}
I ended up with this interface, applied to all focus-able items:
Item {
onFocusChanged: if (keepFocus) focus = true
property bool keepFocus: false
property Item prevFocus: null
function getFocus() {
if (prevFocus) {
prevFocus.keepFocus = false
keepFocus = true
focus = true
}
}
function restoreFocus() {
if (prevFocus) {
keepFocus = false
prevFocus.keepFocus = true
prevFocus.focus = true
}
}
}
Since only overlay dialogs are supposed to take focus from the event dispatcher, the dialog base type automatically handles the acquiring and restoring of focus on dialog show and hide respectively.
So from "one item may have focus" I move to a "one item may have explicit focus", causing the focus to be re-enabled for that item whenever a Control element might steal it.
I'd like to avoid that mouse events triggered by the user don't get dispatched to their target objects, effectively "freezing" the GUI for the user.
In a sample application featuring just a single mx.controls.Button I called addEventListener on the button to get notified of mouse events. In the event handler, I called Event::stopImmediatePropagation on the event, assuming that this would "discard" the event. Clicking the button would call my event handler, but yet the button was "clicked" (it animated and triggered an event).
How could I do this?
button.mouseEnabled = false;
button.mouseChildren = false;
should work
Depending on how advanced your interface is, you could just throw an object (s:Rect in an s:Group would work) on top of everything, set width and height to 100%, and disable mouseChildren
USE removeEventListener()
var b:Button = new Button();
function init():void
{
b.addEventListener(MouseEvent.CLICK, onButtonClick);
}
function onButtonClick(event:MourseEvent):void
{
b.removeEventListener(MouseEvent.CLICK, onButtonClick);
}
I have a datagrid that I want the user to sort the rows on. To make it obvious that it's sortable I am implementing some custom cursors. But I'm having a problem when I actually drag an item.
here's a pseudo demonstration of the problem
Application = normal cursor // fine
Rollover datagrid = open hand cursor // good so far
mousedown on datagrid = closed hand cursor // good
dragging item around = closed hand cursor // switches back to normal cursor (if I move it around real fast I can see my custom curser for an instant)
mouse up on datadrid = open hand cursor // not sure, after I drop it goes back to open hand but if I mouse down, dont move and mouse up I have a closed hand
rollout of datagrid = normal cursor //good
datagrid code:
<mx:DataGrid id="sectQuestionsDG" x="10" y="204" width="558" height="277" headerHeight="0" selectable="{editMode}"
dragMoveEnabled="{editMode}" dragEnabled="{editMode}" dropEnabled="{editMode}"
dragDrop="sectQuestReOrder(event);" rollOver="over();" mouseDown="down();" mouseUp="up();" rollOut="out();"/>
functions:
public function over():void{
CursorManager.setCursor(grabCursor,CursorManagerPriority.LOW,0,0);
}
public function down():void{
CursorManager.setCursor(grabbingCursor,CursorManagerPriority.HIGH,0,0);
}
public function up():void{
CursorManager.setCursor(grabCursor,CursorManagerPriority.LOW,0,0);
}
public function out():void{
CursorManager.removeAllCursors();
}
Edit 12/17/09:
I've made a little bit of progress, I'm now doing this on rollOver
var styleSheet:CSSStyleDeclaration = StyleManager.getStyleDeclaration("DragManager");
styleSheet.setStyle("moveCursor", grabbingCursor);
CursorManager.setCursor(grabCursor,CursorManagerPriority.LOW);
This is giving me the correct rollover and correct drag, but if I try to add any
function to rollOut it screws up again, so now I'm stuck with the grabCursor. It
seems like when I set a rollOut on the dataGrid it's firing for each row, same
with mouseOut, is there any way to avoid that?
Edit 12/21/09:
It is a confirmed thing that roll/mouse out/over fire for every item in the datagrid. The solution I need is how to prevent that and only fire it when the user mouses out of the datagrid as a whole. I need flex to see the forest, not the trees.
PS. the rollout only fires on every item when I am dragging. mouseout fires on every item regardless
EDIT 12/21/09, End of the day:
I have managed to answer my own question so my bounty rep is lost to me :-( Anyway since my answer solves my problem I will award the bounty to anyone that can answer this. My solution uses AS to remove the the rollOut/rollOver while a user is dragging. In a dataGrid. How can you get the same result without removing the rollOut/rollOver (so that rollOut is not firing for each item as you drag another item over it)?
Why not use the property isDragging of DragManager if you are doig a drag you dont need to change the cursor. And dont forget to check for the dragExit event in case you drop outside the datagrid.
N.B
sometimes the cursor keep with the dragging shape after the drop so you can in your sectQuestReOrder remove the cursor and set it back to over state.
sample:
public function over(evt:Event):void{ //on mouse over, added with AS
if (DragManager.isDragging) // you are dragging so no cursor changed
return;
CursorManager.removeAllCursors();
CursorManager.setCursor(grabCursor,CursorManagerPriority.LOW,-7,-7);
var styleSheet:CSSStyleDeclaration = StyleManager.getStyleDeclaration("DragManager");
styleSheet.setStyle("moveCursor",grabbingCursor); //style set for the drag cursor
}
public function down(evt:Event):void{ // on mouse down
CursorManager.removeAllCursors();
CursorManager.setCursor(grabbingCursor,CursorManagerPriority.LOW,-7,-7);
}
public function up(evt:Event):void{
CursorManager.removeAllCursors();
CursorManager.setCursor(grabCursor,CursorManagerPriority.LOW,-7,-7);
}
public function out(evt:Event):void{
if (DragManager.isDragging) // you are dragging so no cursor changed
return;
CursorManager.removeAllCursors();
}
public function sectQuestReOrder(e:Event):void{
// sometime you will be stuck with the moving cursor
// so after the drop done reset cursor to what you want
CursorManager.removeAllCursors();
CursorManager.setCursor(grabCursor,CursorManagerPriority.LOW,-7,-7);
...
}
public function onDragExit(e:Event):void {
// in case you go out of the datagrid reset the cursor
// so when you do a drop outside you ll not get one of your dragging cursor
CursorManager.removeAllCursors();
}
And in your grid add dragExit
<mx:DataGrid
id="sectQuestionsDG"
x="10" y="204" width="558" height="277" headerHeight="0"
selectable="{editMode}"
dragExit="onDragExit(event)"
dragMoveEnabled="{editMode}"
dragEnabled="{editMode}"
dropEnabled="{editMode}"
dragDrop="sectQuestReOrder(event);"
rollOver="over(event);"
mouseDown="down(event);"
mouseUp="up(event);"
rollOut="out(event);"/>
I would look at the mouseOut event and determine if its firing when you're moving the mouse during a drag. I have seen cases where the dragged object doesn't move exactly with the mouse, and for a short while, the mouse is actually hovering over another object (causing the mouseOut event to fire, thus changing the cursor).
OK some props to Gabriel there for getting my mind out of a rut and back into this problem in full mode. I had to go through a few steps to get to my answer
1)remove the listeners for rollOver, rollOut, and mouseUp from the mxml and add rollOver and rollOut through the addEventListener method in AS
2) add the listener dragComplete to the mxml and assign the function previously assigned to mouseUP to it
3) change the main function to this:
public function over(evt:Event):void{ //on mouse over, added with AS
CursorManager.removeAllCursors();
CursorManager.setCursor(grabCursor,CursorManagerPriority.LOW,-7,-7);
var styleSheet:CSSStyleDeclaration = StyleManager.getStyleDeclaration("DragManager");
styleSheet.setStyle("moveCursor",grabbingCursor); //style set for the drag cursor
}
public function down(evt:Event):void{ // on mouse down
CursorManager.removeAllCursors();
CursorManager.setCursor(grabbingCursor,CursorManagerPriority.LOW,-7,-7);
sectQuestionsDG.removeEventListener(MouseEvent.ROLL_OVER,over);
sectQuestionsDG.removeEventListener(MouseEvent.ROLL_OUT,out);
//this is why I had to take it off the mxml, can only remove listeners
//added with the addEventListener, I don't remember where I read that.
}
public function up(evt:Event):void{
CursorManager.removeAllCursors();
CursorManager.setCursor(grabCursor,CursorManagerPriority.LOW,-7,-7);
sectQuestionsDG.addEventListener(MouseEvent.ROLL_OVER,over);
sectQuestionsDG.addEventListener(MouseEvent.ROLL_OUT,out);
}
public function out(evt:Event):void{
CursorManager.removeAllCursors();
}
Is there a way to write a custom event that gets triggered when the user clicks outside of that custom component instance? Basically anywhere else in the main flex app.
Thanks.
You can use the FlexMouseEvent.MOUSE_DOWN_OUTSIDE event. For example:
myPopup.addEventListener(
FlexMouseEvent.MOUSE_DOWN_OUTSIDE,
function(mouseEvt:FlexMouseEvent):void
{
PopUpManager.removePopUp(myPopup);
}
);
stage.addEventListener( MouseEvent.CLICK, stgMouseListener, false, 0, true );
...
private function stgMouseListener( evt:MouseEvent ):void
{
trace("click on stage");
}
private function yourComponentListener( evt:MouseEvent ):void
{
trace("do your thing");
evt.stopPropagation();
}
Got this from Senocular. I think it applies to this subject, at least it did the trick for me. What jedierikb suggested seems to be the same, but a little incomplete.
Preventing Event Propagation
If you want to prevent an event from propagating further, you can stop it from doing so within an event listener using stopPropagation() (flash.events.Event.stopPropagation()) or stopImmediatePropagation() (flash.events.Event.stopImmediatePropagation()). These methods are called from the Event objects passed into event listeners and essentially stop the event from happening - at least past that point.
stopPropagation prevents any objects beyond the current from recieving the event, and this can be within any phase of the event. stopImmediatePropagation does the same but also takes the extra step of preventing additional events within the current target receiving the event from happening too. So where as stopPropagation would prevent sprite A's parent from receiving the event, stopImmediatePropagation would prevent sprite A's parent as well as any other listeners listening to sprite A from receiving the event.
Example: toggle between using stopPropagation and stopImmediatePropagation
ActionScript Code:
var circle:Sprite = new Sprite();
circle.graphics.beginFill(0x4080A0);
circle.graphics.drawCircle(50, 50, 25);
addChild(circle);
circle.addEventListener(MouseEvent.CLICK, clickCircle1);
circle.addEventListener(MouseEvent.CLICK, clickCircle2);
stage.addEventListener(MouseEvent.CLICK, clickStage);
function clickCircle1(evt:MouseEvent):void {
evt.stopPropagation();
// evt.stopImmediatePropagation();
trace("clickCircle1");
}
function clickCircle2(evt:MouseEvent):void {
trace("clickCircle2");
}
function clickStage(evt:MouseEvent):void {
trace("clickStage");
}
Click the circle and see how the event is stopped with each method. stopPropagation prevented the stage from receiving the event while stopImmediatePropagation also prevented clickCircle2 from recognizing the event
normal output
clickCircle1
clickCircle2
clickStage
stopPropagation output
clickCircle1
clickCircle2
stopImmediatePropagation output
clickCircle1
Flex/Actionscript 3 - close popupanchor on mouse clicked anywhere outside popup anchor
for 4.6 SDK try this..
frmPUA.popUp.addEventListener(FlexMouseEvent.MOUSE_DOWN_OUTSIDE, menuPopOutside, false, 0, true);
Full code is avaiable at
http://saravanakumargn.wordpress.com/2013/12/14/flexactionscript-3-close-popupanchor-on-mouse-clicked-anywhere-outside-popup-anchor-2/