In an AIR application I have the following code:
theDialog = PopUpManager.createPopUp( this, TheDialogClass, true ) as TheDialogClass;
theDialog.addEventListener(FlexEvent.CREATION_COMPLETE, cpuIntensiveCalc);
At the end of cpuIntensiveCalc the dialog is removed. The dialog informs the user that "something is going on, please stand by."
The problem is that cpuIntensiveCalc starts before the dialog draws. So the user experience is that the application freezes for 10 seconds with no indicator, then the modal dialog flashes quickly (less than a second) on screen.
The Adobe docs say this about creation_complete
Dispatched when the component has finished its construction,
property processing, measuring, layout, and drawing.
So this feels like the correct event.
In the name of completeness, I also tried
theDialog = PopUpManager.createPopUp( this, TheDialogClass, true ) as TheDialogClass;
cpuIntensiveCalc();
But had the same results.
TIA
The reason for this is that the Flash Player is single threaded, and so you are blocking the UI from reacting to the Dialog Popup until the maths chunk is finished.
Hacky fix time...
You have two options.
(This one should work, but is untested) Wrap the cpuIntensiveCalc() call in a callLater, so that the UI can finish rendering before you block the rendering.
Or
Use "Green Threads" to break up your processing so that you don't completely block the UI processing. Take a look.
(I just had the same issue => even if this thread is old, I just wanted to contribute my solution)
(disclaimer: this is a bit ugly, but they say that's ok in the UI layer... ;-) )
Flex is single threaded (at least from our developer's perspective, I think behind the scene threads are used by the VM)
=> you typically execute your code in the UI thread, after the user did some action on a widget. Any call to update a UI component (like setProgress or setLabel) will only be rendered on screen at the end of the render cycle (see again UiComponent life cycle).
=> In therory calling "cpuIntensiveCalc" in a callLater will let the framework display your popup before executing the method.
In practice though, I noticed you typically have to have for a couple of UI cylces before the popup be displayed, like this:
new MuchLaterRunner(popup, 7, cpuIntensiveCalc).runLater();
MuchLaterRunner being defined like this:
public class MuchLaterRunner
{
var uiComp:UIComponent;
var currentCounter = 0;
var cyclesToWaitBeforeExecution=0;
var command:Function;
public function MuchLaterRunner(uiComp:UIComponent, cyclesToWaitBeforeExecution:uint, command:Function)
{
this.uiComp = uiComp;
this.command = command;
this.cyclesToWaitBeforeExecution =cyclesToWaitBeforeExecution;
}
public function runLater() {
currentCounter ++;
if (currentCounter >= cyclesToWaitBeforeExecution) {
uiComp.callLater(command);
} else {
// wait one more cycle...
uiComp.callLater(runLater);
}
}
}
The issue is the same when calling setProgress afterward: we must divide cpuIntensiveCalc into small callable methods that can be ran at each UI cycle, otherwise the progressbar won't, err, progress.
Use enterFrame event on popup. Don't forget to remove the listener in the enterFrame event handler - otherwise the cpu intensive method will be called in each frame, crashing your app. If this doesn't work at first, use a private number as a counter and keep incrementing it in the enter frame handler - call cpu heavy method only when the counter reaches the appropriate value. Find the 'appropriate' value by trail and error.
theDialog = PopUpManager.createPopUp(this, TheDialogClass, true) as TheDialogClass;
theDialog.addEventListener(Event.ENTER_FRAME, onEnterFrame);
private function onEnterFrame(e:Event):void
{
//can use theDialog instead of e.currentTarget here.
(e.currentTarget).removeEventListener(Event.ENTER_FRAME, onEnterFrame);
cpuIntensiveCalc();
}
//in case the above method doesn't work, do it the following way:
theDialog.addEventListener(Event.ENTER_FRAME, onEnterFrame);
private var _frameCounter:Number = 0;
private function onEnterFrame(e:Event):void
{
_frameCounter++;
var desiredCount:Number = 1;//start with one - increment until it works.
if(_frameCounter < desiredCount)
return;
//can use theDialog instead of e.currentTarget here.
(e.currentTarget).removeEventListener(Event.ENTER_FRAME, onEnterFrame);
cpuIntensiveCalc();
}
Related
I have a problem with a refresh on a Label.
I have an function like this :
public void majMontantPaye(Double montantPaye) {
System.out.println("montant paye : "+montantPaye);
setMontantPaye(this.montantPaye+montantPaye);
Platform.runLater(() -> labelMontantPaye.setText(String.format("%.2f", this.montantPaye)+Messages.getMessage("0052")));
}
And My function is call by an API. This API communicate with a machine who allow to insert coin. And my function must to show the sum insert in the machine.
The problem is, when I insert a lot of coin in the same time in the machine, my function is correctly call every coin detected, so the System.out.println("montant paye : "+montantPaye); is correctly show every coin detected, BUT the Label "labelMontantPaye" is not refresh to every coin detected. Just on finish with the total sum.
I guess that the UI is not correctly refresh but I don't know how refresh correctly my Label.
Help please and sorry for mistake, i'm french.
You can follow the logic below:
As mentioned in the comments:
Using Platform.runLater(...) you queue the task into the JavaFXThread. But when you have many "events" you will only see the last result. (And maybe previous ones for a short time).
🍄Use a BlockingQueue to store each coin that is inserted.Use the method below(also have a look at the tutorial for the available methods,here i am using put which is blocking the current thread if a maximum of coins are inserted into the Queue,if you don't want this set the maximum to something like 500.000):
public void insertCoin(//maybe the kind of coin){
//add the coin into the BlockingQueue
blockingQueue.put(//coin);
}
Use a Thread which is running an infinity loop.The Thread is waking up
every time a new coin is inserted and when that is done , that Thread
waits JavaFXThread to refresh the Label Text:
new Thread(() -> {
//Run an infinity Thread
while (true) {
// Blocks until the queue has really any coins inserted
blockingQueue.get();
// Synchronize with javaFX thread
CountDownLatch latch = new CountDownLatch(1);
Platform.runLater(() -> {
label.setText(....);
latch.countDown();
});
// Block the Current Thread until the text is refreshed from
// JavaFX Thread
latch.await();
}
}).start();
Take MenuItem as an example, normally in QML, specifying the handler for the triggered signal is simple:
MenuItem {
onTriggered: {
console.log("Hey");
}
}
Now if I want to do the same thing, but instead to a dynamically created MenuItem, e.g. via Menu.addItem(), then what is the syntax like to connect and specify the signal handler?
I didn't expect this to work, but here is a working solution:
function onTriggered() {
console.log("Hey");
}
var newItem = myMenu.addItem("Item 1");
newItem.triggered.connect(onTriggered);
Nevertheless is there a better way? Above I defined a custom function that happened to be named onTriggered, but it can be named anything, right? So this code piece doesn't make use of the built-in handler, that's why I'm wondering if there's a neater solution?
More importantly, later on I've noticed further problems with this approach: in a for loop, if there is a temporary variable used by the handler, things don't work any more:
for (var i = 0; i < myArray.length; i ++) {
var info = myArray[i];
var newItem = myMenu.addItem("Item " + i);
newItem.triggered.connect(function() {
console.log(info);
});
}
Here you'll see that console prints the last info in myArray for all added menu items when triggered. How can I properly set up independent handlers for each individual menu item?
In addition to the comments, you can easily make it "easier":
Menu {
id: myMenu
function add(text, handler) {
var newItem = addItem(text)
newItem.triggered.connect(handler)
}
}
And there you have it, problem solved, now you can simply myMeny.add("Item 1", onTriggered)
As for the result you get in the loop and functor, that's because of JS's scoping rules. Check the linked answer for details how to work around that.
So this code piece doesn't make use of the built-in handler
Don't think of onSignal as a handler, it is just a hook to attach a handler. Think of it as the declarative connection syntax. Sure, you can also use the Connection element in declarative, but it only makes sense when the situation actually merits it.
I think this confusion stems from some other language / framework which does generate handler methods for you. A onSignal is different from function onSignal() { expression } - the latter is a handler function, the former is handler hook, which just connects the signal to the bound expression.eval(). The Qt documentation too refers to onSignal as a handler, which IMO is technically and conceptually wrong, since the handler is the code which gets executed, the handler is whatever you bind to onSignal.
So you can rest easy, the code you are worried about does not result in any sort of redundancy or inefficiency and doesn't leave anything unused and is in fact the correct way to do things in QML.
All that being said, you can have "built in handlers", but it is a very different thing:
// SomeItem.qml
Item {
signal someSignal
onSomeSignal: console.log("I am a built in handler")
}
// main.qml
SomeItem {
onSomeSignal: console.log("I am another handler")
Component.onCompleted: {
someSignal.connect(function(){console.log("Yet another handler")})
someSignal()
}
}
And the output in the console will say:
qml: I am a built in handler
qml: I am another handler
qml: Yet another handler
As you see, it not really a handler, but a connection hook. There is no shadowing, no "replacing / not using the built in handler", there is just a signal with 3 connections to the evaluation of three expressions.
Using signal.connect() with a named function does come with one advantage, you can later signal.disconnect(namedFunction) if you need to remove a built in or another handler. I am not sure if you can do this if you use onSignal: expr since you don't have a way to reference that anonymous expression. Note that if you use onSignal: namedFunction() this will not work, you will not be able to signal.disconnect(namedFunction) because the signal is not directly connected to that function, but to an anonymous expression invoking it.
I have a busy function taking several of seconds to be executed. At the top of the function I set my busy indicator to be visible and at the bottom I set it to be invisible. But the display does not change while the function is executed and the busy indicator does not appear. here is the code structure
public function busyFunction():void{
busyIndicator.visible = true;
doStuff .. // takes several seconds
busyIndicator.visible = false;
}
i just want the first line (busyIndicator.visible = true) to be executed while the doStuff is on progress.
Regards
The problem is because Flex doesn't redraw the screen while your method is executing. So you make the busy indicator visible, do stuff, and hide the indicator. But no update to the screen will have occurred yet, so the indicator never shows.
The Flex "elastic racetrack" paradigm basically says that your app consists of two cycles, one where your logic is executed, and another where the screen is updated. So you need to set the busy indicator to visible, and then make it go away after at least one update cycle later.
To make this sort of thing work, you need to execute doStuff() asynchronously. Some code that does this might look like this:
private function startDoingStuff():void
{
busyIndicator.visible = true;
// dispatch "doStuff" event
dispatchEvent(new Event("doStuff"));
return; // returning here is important don't do stuff here
}
private function onDoStuff(e:Event):void
{
// do stuff;
busyIndicator.visible = false;
}
Another idea is to use Flex's callLater() method to execute your doStuff() method. This should guarantee that doStuff() is executed on the next cycle:
private function startDoingStuff():void
{
busyIndicator.visible = true;
callLater(onDoStuff);
}
private function onDoStuff():void
{
// do stuff
busyIndicator.visible = false;
}
I have a NavigatorContent which is displayed when the user selects an item in a DataGrid.
This NavigatorContent contains a form and an accordion displaying the related objects.
When the user presses the Save button in the NavigatorContent the form and the children should be saved to the database by calling the server through BlazeDS:
saveObjectToDB()
{
//Map the form values to the object
object.field1 = object_field1.text;
object.field2 = object_field2.selectedDate as Date;
object.relatedobject3 = comboBox.selectedItem as RelatedObject3;
//etc.....
//Loop through accordion to save the child objects
for(var i:int= 0; i < accordion.numChildren; i++ )
{
if(accordion.getChild(i) is RelatedObject1Form)
{
var formRelated1:RelatedObject1Form = accordion.getChild(i) as RelatedObject1Form;
//Map the form values to the related object
object.relatedobject1.field1 = formRelated1.relatedobject1_field1.selectedDate;
//etc...
}
if(accordion.getChild(i) is RelatedObject2Grid)
{
var gridRelated2:RelatedObject2Grid = accordion.getChild(i) as RelatedObject2Grid;
//Get dataProvider for the datagrid of the relatedObject
object.relatedobject2 = gridRelated2.object.relatedobject2;
}
}
// Call the remoting object's saveObject method
var saveObjectOperation:Operation = new Operation();
saveObjectOperation.name = "saveObject";
saveObjectOperation.arguments=[object];
ro.operations = [saveObjectOperation];
saveObjectOperation.send();
if(isNewObject)
//dispatchEvent new object
else
//dispatchEvent object updated
}
My problem is as the question states that my application freezes for a few seconds when the user presses the save button that calls this method. I guess that is because Flex is single threaded, but still i dont quite get why this method would be so computational expensive? It doesnt seem to matter if i comment out the loop that loops over the accordion children.
I tried setting the objects related objects to null before calling the remote save method, and this seemed to speed up the save method, but it provided me with some troubles later.
My conclusion is that the remote call is whats freezing up the application, and if i set the related objects to null this seems to fix the issue. But is this really necessary? The related objects aren't really that big, so i don't quite get why the remote call should freeze the application for a few seconds.
This is how i create the accordion children when the NavaigatorContent is intialized:
var relatedObjectForm:RelatedObject1Form= new RelatedObject1Form();
accordion.addChild(relatedObjectForm);
relatedObjectForm.object= object;
relatedObjectForm.ro = this.ro;
The object that i pass to the accordion children is public and [Bindable] in the NavigatorContent and in the accordion children and is initially passed from the main DataGrid. May this be a problem relating to this issue?
Any help/comments is much appreciated. This issue is starting to affect my beauty sleep ;)
My guess would be that you're spending a lot of time in the serializer. Put a trace target in the app and watch the console when it runs to see what's being sent.
The most likely problems are from DisplayObjects - if they've been added to the application they will have a reference to the application itself, and will cause some serializers to start serializing the entire app. The bindable object might have some odd events attached that eventually attach to DisplayObjects - try copying the relevant values in it into your object instead of just taking a reference to the existing object.
I'm attempting to take a process that previously ran multiple remote object calls in parallel and make them run in serial.
I know it's a hackish approach (bandaid!), but so far my approach has been to take this for-loop that started each parallel request:
(original code)
for (i = 0; i < _processQ.length; i++){
fObj = _processQ.getItemAt(i);
f = fObj.Name;
fObj.Progress = "Processing...";
ro.process(gameid,f);
}
and change it to this:
(new code)
for (i = 0; i < _processQ.length; i++){
fObj = _processQ.getItemAt(i);
f = fObj.Name;
fObj.Progress = "Processing...";
ro.process(gameid,f);
//pause this loop until a result is returned from the request that was just made.
_pause_queue = true;
while(_pause_queue){
//do nothing
}
}
Where _pause_queue is a private global boolean for the class. In the fault-handler and response-handler functions, it is set back to false, so that the infinite where-loop is released and the for loop will continue.
This is functional, but the UI only updates after the while-loop ends, which causes the negative side effect that it looks like the application is hung while waiting for a response.
Is there something I can put inside the while loop to allow flash to update the display? In VB (ugh!) I would use DoEvents, but I don't know of an equivalent for Actionscript.
Alternately, is there a way to add a simple queueing system short of completely rewriting the application? This is legacy code and I'm on a tight deadline so I'd like to modify it as little as possible. (Otherwise I would rewrite it from the ground up and use Swiz to get the Event Chaining.)
Alternately, is there a way to add a
simple queueing system short of
completely rewriting the application?
Yes, you can use an array. It looks like you may already have one, with the _processQ variable. I might try something like this:
// A process queue item method
protected function processItemInQueue():Void{
if(_processQ.length > 0){
fObj = _processQ.pop();
f = fObj.Name;
fObj.Progress = "Processing...";
ro.process(gameid,f);
}
}
Then in your remote Object result handler:
protected function roResultHandler(event:ResultEvent):void{
// process results
// process next item in queue
processItemInQueue();
}
ActionScript is single threaded so a while (true) loop would indeed hang forever. What you might want to consider is using a Timer class to fire off events regularly or add an event listener to ENTER_FRAME (flash player thinks in the fps domain) which is fired at the start of each frame.
Looking at your code, unless ro.process() is a remote IO operation (fetching a web page or making a web service call), they would hang the UI while they do their work. You might want to split up the work and fire them every frame rather than all at once. You should also learn how to use EventDispatcher logic to listen to when they finish or faults instead of setting a global variable and loop.
Agree with clement -- why not make it all some event rather than trying to do everything in a single loop?
function outerFunction() {
var queue = new SomeAsyncProcessor();
for (i = 0; i < _processQ.length; i++){
queue.addProcess(_processQ.getItemAt(i));
}
queue.addEventListener('complete', handleProcess);
queue.start();
game.pause();
}
function handleProcess(event) {
var processor = event.currentTarget as SomeAsyncProcessor();
process.removeEventListener('complete', unpause);
game.unpause();
}
For a priorityqueue -- you could use this:
http://www.polygonal.de/legacy/as3ds/doc/de/polygonal/ds/PriorityQueue.html
Shouldn't be too much time to write one as well, mainly just wrapping what you have currently into one big event dispatcher (as long as your current processors already dispatch events).