I have an AIR application running on a Mac and I want to have the behavior of hiding the window when someone "closes" the app (e.g. hits the red "x" button or cmd-w). However, if someone hits cmd-q or chooses "Quit" from the dock context menu or top level menu, I want the app to actually close.
I can preventDefault on the "closing" event sent by the application, however, this causes all "close" methods to just hide the window. The only way for someone to close the application at that point is ForceQuit (or through a separate interface I provide, like a context menu option on the dock icon).
I have also tried capturing the cmd-q keyDown event manually, but it doesn't get sent. Also, this wouldn't help for the case when people try to quit the app using the menu options.
Furthermore, if I preventDefault on the closing method, it causes my application to cancel a shut down process immediately (which is a terrible user experience).
Is there a way to detect different methods of closing an AIR application? I want to be able to tell the difference between these closing methods and react to the appropriately.
Try this for the closing, from what I understand there was / is a bug in the framework so that if you include the AIR updater it breaks cmd-q support, the thread used to be here: http://www.adobe.com/cfusion/webforums/forum/messageview.cfm?forumid=72&catid=670&threadid=1373568
This may or may not be applicable to your situation.
NativeApplication.nativeApplication.addEventListener(Event.EXITING,
function(e:Event):void {
var opened:Array = NativeApplication.nativeApplication.openedWindows;
for (var i:int = 0; i < opened.length; i ++) {
opened[i].close();
}
});
Try this, I am sure there must be a better way of handling this but this has worked for me.
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="onCreationComplete()">
<mx:Script>
<![CDATA[
import mx.core.Application;
import mx.events.AIREvent;
import mx.core.Window;
private function onCreationComplete():void {
addMacSupport();
}
private var macsupport_allowExit:Boolean = false;
private function addMacSupport():void {
if ( Capabilities.os.indexOf("Mac") == 0 ) {
//open a hidden window that will prevent the application from
//exiting when the user presses Cmd+W
var win:Window = new Window();
win.visible = false;
win.open(false);
//add a closing listener on the hidden window, this event will only
//be fired when the user pressed Cmd+Q or selects quit from the menu
//then set macsupport_allowExit to true
win.addEventListener(Event.CLOSING, function(e:Event):void {
macsupport_allowExit = true;
});
//add an event listener to this window on closing
addEventListener(Event.CLOSING, function(e:Event):void {
//always preventDefault
e.preventDefault();
//wait one frame, then check the macsupport_allowExit variable
//if it is true, we nedd to exit the app, otherwise just hide
//the app window
callLater(function():void {
if ( macsupport_allowExit ) {
nativeApplication.exit();
}
else {
nativeWindow.visible = false;
}
});
});
//add an event listener for INVOKE to show our main app window
//when the dock icon is clicked.
addEventListener(InvokeEvent.INVOKE, function(e:InvokeEvent):void {
if ( nativeWindow && !nativeWindow.visible ) {
nativeWindow.visible = true;
nativeWindow.activate();
}
});
}
}
]]>
</mx:Script>
</mx:WindowedApplication>
Related
In Flex (Flash Builder 4) I am opening a new window via PopUpManager.addPopUp. I have timer code that runs in my component and I need to stop my timer when that window opens and start the timer again when the window closes.
I figure it's easy enough to stop the timer in the function that opens the window, but how can I start the timer again when the window closes?
Is there a way to tell if there is a pop-up window in front of my component, or if a specific pop-up window is still open via PopUpManager?
Maybe events are a better approach?
Thanks!
Events! is the way to go.
Fire events during launch/close. Add your logic in the event Handlers!
You can use following code to check whether opened popup window is getting closed or not.
if it is closed you can stop the timer.
//set the flag to find your popup window is exist or not.
private var isPopupExist:Boolean = false;
private function closePopUpWindow():void
{
var systemManager:SystemManager = FlexGlobals.topLevelApplication.systemManager;
//Returns a list of all children.
var childList:IChildList = systemManager.rawChildren;
for(var i:int=childList.numChildren-1;i>=0;i--)
{
var childObject:* = childList.getChildAt(i);
//If child object is Uicomponent.
if (childObject is UIComponent)
{
var uiComponent:UIComponent = childObject as UIComponent;
//If uicomponent is popup and class name is equal to **your popup component name** here i am using "ChatComp".
if (uiComponent.isPopUp && uiComponent.className == "ChatComp")
{
isPopupExist = true;
}
}
}
}
in your Timer,
private function checkPopUpExistance():void
{
call closePopUpWindow() function for every 1 sec or any seconds(your wish) to check whether popup is exist or not.
if(isPopupExist)
{
here you stop the timer.
}
}
Now you can start the Timer, when you opened the Popup window.
The popupmanager is a singleton class, so you can easily know how many popups have been created with his ChildList
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/mx/managers/PopUpManagerChildList.html
Am working on a flex project
I am looking to provide some UI functionality using the mouse- I have two distinct UI events to be achieved via mouse
a) change value
b) delete object
I don't seem to have sufficient mouseclick events for both. I am avoiding using the right click as it has some default options(whose signing off will affect the whole project- not just this). I have mouse click used for change value- how do I use the doubleclick as the single-click events seems to get invoked prior?
Any thoughts?
private var doubleClickOccurred:Boolean = false;
private var timer:Timer = new Timer(100, 1);
protected function application1_creationCompleteHandler(event:FlexEvent):void
{
myLabel.addEventListener(MouseEvent.CLICK, checkSingleOrDoubleClick);
myLabel.addEventListener(MouseEvent.DOUBLE_CLICK, checkSingleOrDoubleClick);
timer.addEventListener(TimerEvent.TIMER_COMPLETE, handleClick);
}
private function checkSingleOrDoubleClick(event:MouseEvent):void
{
if(event.target == myLabel && event.type == MouseEvent.DOUBLE_CLICK)
{
// set the flag and let the timer complete event
// take care of the click handling
doubleClickOccurred = true;
trace(" double clicked");
}
else if( event.type == MouseEvent.CLICK)
{
// start timer to wait till the double click event
// gets called
timer.start();
trace("Starting timer");
}
}
private function handleClick(event:Event):void
{
if(doubleClickOccurred)
{
// handle double click event
trace("Yes");
}
else
{
// handle single click
trace("No");
}
// reset flag for capturing future events
doubleClickOccurred = false;
}
]]>
</fx:Script>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<s:Label id="myLabel" text="Click Me" doubleClickEnabled="true" />
Output:
1) If a there is a single mouse click on the Label, the Single click login i.e trace("No") is invoked
2) In case of double click on the Label, the trace("yes") is invoked.
I hope this piece of code answers your question about handling single and double click on Flex components.
Can't say too much without knowing what you're editing, what the values are, etc.
A common idiom is to show an "X" icon, say, on the edge of a selected item, with clicks only on that icon triggering delete.
In Flex Air app, how do you open a window behind an active one?
I tried following and i can't seem to get it to work
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
creationComplete="onCreationComplete(event)">
<fx:Script>
<![CDATA[
import mx.events.FlexEvent;
import spark.components.Window;
private var window1:Window = new Window();
private var window2:Window = new Window();
private var timer:Timer = new Timer(3000,1);
private function onCreationComplete(event:FlexEvent):void
{
window1 = new Window();
window1.title = "Window 1";
window1.width = 200;
window1.height = 200;
window1.open(false);
window1.orderInBackOf(this);
window2 = new Window();
window2.title = "Window 2";
window2.width = 200;
window2.height = 200;
timer.addEventListener(TimerEvent.TIMER_COMPLETE, openWindow2, false, 0, true);
timer.start();
}
private function openWindow2(event:TimerEvent):void
{
window2.open(false);
window2.orderInBackOf(window1);
}
]]>
</fx:Script>
</s:WindowedApplication>
With this code, I would expect window1 to open behind the main app window and, in 3 seconds, window2 would open behind window1. But if you execute this, window1 will open on top of the main window and window2 will open on top of window1 and the main app will retain focus. This seems like a bug in the Flex. If so is there any workaround for this problem?
So it seems like there isn't a good way to solve this problem. A workaround would be to add event listener for AIREvent.WINDOW_COMPLETE and move the window in the event handler. This seems like "correct" workaround and it works for a simple case like my original example. Problem is that this approach did not work on my code.
If you look at the spark.components.Window code from the sdk, you will see that an event listener for Event.ENTER_FRAME is added when opening the window (in commitProperties()). Then in the event handler, enterFrameHandler(), it keeps a counter, and on second frame, dispatch AIREvent.WINDOW_COMPLETE event. This makes me think that AIREvent.WINDOW_COMPLETE event will fire on second frame regardless of the status of the window even though the livedoc says:
Dispatched when the Window completes its initial layout and opens the underlying NativeWindow.
I'm guessing my window wasn't full created by the second frame and therefore orderInBackOf failed.
So here's my workaround:
First, in my window, I override open(openWindowActive). If openWindowActive is false, I add an event listener for Event.ENTER_FRAME. In the event handler, I keep a counter and, when the frame count reaches a certain number (10 right now), I move the current window behind the active one. This is a VERY hacky way to get around this problem. I've been increasing the limit and now it succeeds about 90% of the time. I could continue to increase the limit until I get close to 100% but I hate having to check this condition every time I make any changes to the window. Also this condition could be different depending on the machine.
I would LOVE it if someone can tell me how wrong my workaround is and tell me a much much better way to solve my problem... but till then this will have to do...
Is there a particular reason you're setting the useWeakReference parameter to true? Otherwise, you should be able to just call:
timer.addEventListener(TimerEvent.TIMER_COMPLETE, openWindow2);
However, there's a bug in that line of code. The TIMER_COMPLETE event is fired when the timer is finished all its cycles. You have set your timer to "fire" infinitely. So it will never complete it's cycles.
You need to modify that line of code to the following and you should get your expected results:
timer.addEventListener(TimerEvent.TIMER, openWindow2);
To address your second question (why window1 doesn't appear behind the application). Judging by the return value of the orderInBackOf function:
Boolean — true if the window was succesfully sent behind;
false if the window is invisible or minimized.
It seems that ordering fails if the the window is invisible. It may be the case that, by having the code in the creationComplete handler, you're calling this function before the Application window has a chance to display itself. Try moving that code within your openWindow2 function, as well. Yeilding:
private function openWindow2(event:TimerEvent):void
{
window1.orderInBackOf(this);
window2.open(false);
window2.orderInBackOf(window1);
}
I hope this helps in some way,
gMale
EDIT: per my last comment try this,
private function openWindow2(event:TimerEvent):void
{
window1.depth = 5; //arbitrary number
window2.depth = 4;
window1.open(false); //assumes window1 isn't opened before
window2.open(false);
}
I've done a lot of C# programming with both Winforms and WPF. I'm working on a Flex/Air app now for cross platform support. But this is my first flex project, so I'm learning as I go.
I've got a window that I want to popup, that the user will fill out a form, then hit OK or CANCEL. I set it up the same way I would've in C#, but it doesn't work, and I can't really see a way to make it do what I want.
EDIT:
So I'm trying events now, the events just don't seem to be handled...
EDIT again:
Oh, It's because the popup manager seems to create a new instance of the Form object, rather than using the one I created already.
so in the showWindow method, I put in this code rather than the popup manager:
parent.addChild(this);
then I remove it when I close it. The only problem is, it doesn't disable the rest of the parent like the popup manager does. Any suggestions on that?
PARENT:
private function btnAdd_Clicked():void
{
var form:Form = new Form();
form.addEventListener(CloseEvent.CLOSE, onFormClosed, false, 0, true);
recipeForm.showWindow(this);
}
private function onFormClosed(e:CloseEvent):void
{
//none of these Alerts are ever shown. I also tried breakpoints in debug to try an follow the code, with no luck
Alert.show("Closed");
if(e.detail == Alert.OK)
{
Alert.show("OK");
}
else if(e.detail == Alert.CANCEL)
{
Alert.show("Cancel");
}
}
CHILD:
private function btnCancel_Clicked():void
{
okClicked = false;
closeWindow();
}
public function closeWindow():void
{
var e:CloseEvent = new CloseEvent(CloseEvent.CLOSE);
e.detail = okClicked ? Alert.OK : Alert.CANCEL;
dispatchEvent(e);
PopUpManager.removePopUp(this);
}
public function showWindow(parent:WindowedApplication):void
{
var window:IFlexDisplayObject = PopUpManager.createPopUp(parent, RecipeForm, true);
PopUpManager.centerPopUp(window);
}
You can do this at least two different ways:
FIRST WAY: Using events
Let your Form class dispatch an event when either of the buttons is clicked. After Form is instantiated from the parent view, add an eventListener for the event(s) it's known to dispatch. When the Form dispatches the event, the eventListener will be invoked. You can even reuse Flex's CloseEvent and set the "detail" property to either Alert.OK or Alert.CANCEL before dispatching it.
In Form:
var e:CloseEvent = new CloseEvent(CloseEvent.CLOSE);
e.detail = okClicked ? Alert.OK : Alert.CANCEL;
dispatchEvent(e);
In parent:
var f:Form = new Form();
f.addEventListener(CloseEvent.CLOSE, onClose, false, 0, true);
...
private function onClose(e:CloseEvent):void
{
if (e.detail == Alert.OK)
// do something
else if (e.detail == Alert.CANCEL)
// do something else
}
SECOND WAY: Using callbacks
Add a public var of type "Function" to your Form class and supply a callback function from the parent. This does basically the same thing as #1 except with little less abstraction / indirection.
I would recommend #1 since the event model in Flex is pretty well-conceived and more flexible than the callback.
In Form:
var e:CloseEvent = new CloseEvent(CloseEvent.CLOSE);
e.detail = okClicked ? Alert.OK : Alert.CANCEL;
dispatchEvent(e);
In parent:
var f:Form = new Form();
f.addEventListener(CloseEvent.CLOSE, onClose, false, 0, true);
...
private function onClose(e:CloseEvent):void
{
if (e.detail == Alert.OK)
// do something
else if (e.detail == Alert.CANCEL)
// do something else
}
Not sure if this is still an open issue. I ran into this very same problem and I think I figured out what is wrong. At least I did for my problem.
I implemented things exactly as you did. I also have the close attribute set to closeWindow (I'm using a TitleWindow for my dialog).
So when the window is closed via the X at the top, it will call closeWindow, also if you click on the Cancel button, it will also call closeWindow.
The problem for me was that clicking cancel, dispatches a CloseEvent which seems to be caught by a Listener which calls closeWindow again (possibly via the close attribute which probably creates its own internal listener). I'm not sure if its an infinite loop but Flex does not like this.
My solution was to create two functions, one for the X close window to call and one for the Cancel button to dispatch a CloseEvent of its own. This seemed to work for me. Hope it helps you.
Goal:
Allow the user to delete a record by dragging a row from an AdvancedDataGrid, dropping it onto a trash-can icon and verify the user meant to do that via a popup alert with "OK" and "Cancel" buttons.
What is working:
Dragging/Dropping a row onto the trash icon.
If the user clicks the "OK" button, the record is deleted.
If the user clicks the "Cancel" button, the operation is canceled.
Problem:
After the user clicks the "Cancel" button and the popup alert closes, no rows in the ADG can be dragged. I've discovered that after sorting the ADG, by clicking on a column header, the user can begin dragging rows again.
Code: (changed from original post)
<mx:Image source="{trashImage}" buttonMode="true"
toolTip="drag a participant here to delete them from the project"
dragDrop="deleteParticipantDrop(event)" dragEnter="deleteParticipantEnter(event)"
dragExit="deleteParticipantDragExit(event)" top="4" right="122" id="image2" />
// trashImage Event Handlers:
private function deleteParticipantEnter(event:DragEvent):void
{
var component:IUIComponent = IUIComponent(event.currentTarget);
dragComponent = component;
DragManager.acceptDragDrop(component);
DragManager.showFeedback(DragManager.MOVE);
deleteParticipantDragEvent = event;
}
private function deleteParticipantDrop(event:DragEvent):void
{
var selectedKitNum:String = memberRpt.selectedItem.KitNum;
var selectedName:String = memberRpt.selectedItem.ParticipantName;
var component:IUIComponent = IUIComponent(event.currentTarget);
dragComponent = component;
DragManager.acceptDragDrop(component);
isEditingParticipantInfo = false;
isDeletingParticipant = true;
deleteParticipantDropEvent = event;
event.stopImmediatePropagation(); // Added as per mrm
alert.confirm("Are you sure you want to delete this participant, Kit #" + memberRpt.selectedItem.KitNum + " (" +
memberRpt.selectedItem.ParticipantName + ") from the project? This cannot be reversed!! An email will be " +
"sent to notify this participant and you will receive a copy of it for your records.", confirmRemoveParticipant);
}
private function deleteParticipantDragExit(event:DragEvent):void
{
var component:IUIComponent = IUIComponent(event.currentTarget);
dragComponent = component;
DragManager.acceptDragDrop(component);
DragManager.showFeedback(DragManager.NONE);
}
private function confirmRemoveParticipant(event:CloseEvent):void
{
if (event.detail == Alert.YES)
{
deleteReason = DeleteParticipantTitleWindow(PopUpManager.createPopUp( this, DeleteParticipantTitleWindow , true));
dispatchEvent(deleteParticipantDropEvent); // Added as per mrm
PopUpManager.centerPopUp(deleteReason);
deleteReason.showCloseButton = true;
deleteReason.title = "Reason for removal from project";
deleteReason.addEventListener("close", cleanupRemoveParticipant);
deleteReason["cancelButton"].addEventListener("click", cleanupRemoveParticipant);
deleteReason["okButton"].addEventListener("click", finalizeDeleteParticipant);
isDeletingParticipant = false;
}
else
{
cleanupRemoveParticipant();
}
}
private function cleanupRemoveParticipant(event:Event = null):void
{
memberRpt.invalidateDisplayList();
memberRpt.executeBindings();
if (deleteReason != null)
{
PopUpManager.removePopUp(deleteReason);
deleteReason = null;
}
}
public function finalizeDeleteParticipant(event:Event):void
{
if (deleteReason.reason.text != null)
{
selectedReportItem = memberRpt.selectedItem;
selectedReportItemIndex = memberRpt.selectedIndex;
memberReportData.removeItemAt(selectedReportItemIndex);
}
else
{
alert.info("You must provide a reason for removing a participant from your project!!");
}
cleanupRemoveParticipant();
}
Thanks in advance for all helpful suggestions.
Have you tried running the validateNow() method on the ADG after the cancel event?
Here is some more information on the validateNow() method.
Why you need to know about validateNow...
I really do think this is what you're looking for! Please let us know if that is the case...
Try refreshing the data bindings on the datagrid using executeBindings and/or invalidateDisplayList in the enclosing control.
To be honest this sounds a bit like a bug. Have you posted this on flexcoders? The Adobe guys hang out on there (probably here too, but definitely there)
Hang on... just spotted that between the drop event and the cancel button of the popup there is an asynchronous web service call which appears to be handled by GetParticipantOrderInformation. Is that correct?
If yes, then have you tried offering a simpler dialog for Cancel before you do that? I wonder whether the combination of layers of events is causing a problem.
I didn't have any success with refreshing the data bindings on the datagrid via the executeBindings and invalidateDisplayList methods. I also didn't have any luck by showing the confirmation alert before making the webservice call. In fact, I discovered that making the webservice call was completely unnecessary and removed it. So now the code flows like this:
Drag/drop ADG row onto trash icon.
Display confirmation Alert box.
If user clicked "Cancel" button, redisplay the ADG.
But the same problem persists. I'll update the Code section with the latest code.
Here's an idea:
- Just before you create the alert window, stop the DragEvent
event.stopImmediatePropagation();
store the event so we can resume if the user clicks the Yes button
queuedEvent = event as DragEvent;
show the alert window
if the user clicks the yes button, resume the queued event
dispatchEvent(queuedEvent);
DragManager.showFeedback(DragManager.NONE);