FLEX: dialog not display immediately

2019-04-09 21:03发布

问题:

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

回答1:

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.



回答2:

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



回答3:

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();
}


标签: flex air