tags:

views:

4071

answers:

5

This may be a bit of a beginners question, but I can't for the life of me figure it out.

I'm using flex to develop a GUI for a large project, specifically a status bar along the bottom. Within my StatusBar class is a ProgressBar, which other classes doing work can tell to update(change bar completion and label) as they progress. The problem I'm encountering is that flex won't update whats shown on the screen until it's too late, for example

ProgressBar initialized, 0% done
some class sets the ProgressBar to be 12% done
some class does some work
some class sets the ProgressBar to be 56% done

Whats happening is the 12% done is never displaying, it just hangs at 0% during the work, then skips right to 56% done. I've tried to understand the lifecycle of a flex component (invalidation and validation), and I think I understand it and am applying it correctly, but it's not working at all. I need to tell flex to redraw my StatusBar (or at least the ProgressBar within) after some class sets it to be 12% done, but before some class starts doing its work. How do I do this?

A: 

Try calling invalidateDisplayList() after each changes to your progress bar. Something like :

Class StatusBar
{

    public function set progress(value:uint):void
    {
        progressBar.value = value;
        progressBar.invalidateDisplayList();
    }
}

Flex has an invalidation cycle that avoid screen redrawing everytime a property changes. As an example, if a property's value changes 3 times in a single frame, it will render only with the last value set. You can force a component to be redrawn by calling invidateDisplayList() which means updateDisplayList will be immediatly executed instead of waiting the next frame.

PeZ
Actually it won't invoke updateDisplayList() "immediately" but it will invoke it at the next screen update / frame. If it did it immediately it would kind of defeat the point of the invalidation / validation model.
cliff.meyers
My bad, you're absolutely right. Instead of calling invalidateDisplayList(), you have to call directly updateDisplayList()
PeZ
A: 

Actionscript in Flash player, like Javascript in the browser, is pseudo-multithreaded. That is, they're single threaded, but they have multiple execution stacks. This means you can't "sleep" in a particular thread, but you can spawn a new execution stack that gets deferred until a later time. The flex way of doing this is the "callLater" function. You can also use the setTimeout/setInterval functions. Or you can use a timer object built into the flash player. Or even "ENTER_FRAME" event listener. All of these will essentially allow you to do what you need, if I'm correct about the cause of your problems.

It sounds like you have one "thread" doing most of your work, never stopping to allow other execution stacks (threads*) to run.

The problem could be what PeZ is saying, but if that doesn't help, you might want to try some deferred calls for worker classes. So your process might look like this now:

  1. Progress initialized.
  2. Do some work.
  3. Update progress bar to 12. (invalidate display list)
  4. setTimeout(doMoreWork, 100);
  5. Update progress bar to 52.

(if your worker is a UIcomponent, you can use uicomp.callLater(...), otherwise, you need to use setTimeout/timers/enter_frame for pure AS3 classes).

Glenn
+2  A: 

As mentioned in other answers, the flash player is single threaded, if you don't break up your work into discrete chunks that can executed in separate "frames", you're going to see jumps and stutters in the ui, which is effectively what you're seeing.

If you really must see that 12% message, then it's not enough to invalidate the display list, as the display list isn't getting a chance to update until after the 56% work has completed, you must explicitly interrupt the natural event cycle with a call to validateNow() after your message has been set.

This however is not the best way to do things if performance is of concern. You might get by with judicial usage of callLater() to schedule each chunk of work in turn, as this will allow the player to potentially complete a frame cycle (and update the display list) before attempting the next step in your process.

ianmjones
I still can't get it, I call invalidateDisplayList() followed immediately by validateNow() and it doesn't appear to update. My StatusBar class as well as the ProgressBar and ProgressBarSkin are being invalidated and then validated, it goes all the way though and even does the drawRect() to actually redraw the filled in part of the ProgressBar, but it still doesn't actually update on the screen.
ABBarton
Did you try callLater, setTimeout, or similar, as I suggested? It sounds like your processing is blocking the renderer from updating the screen. You need to pause, and let it breath.
Glenn
Aha! Talk about bizarre, I can get it to update when I want using setTimeout, as long as the time to execute the rest of the function after the setTimeout(doWork,X) takes less than X. In other words I have to let flash go idle (for more then 25ms). This however breaks if I put breakpoints in the wrong place or if I try and step through the code.
ABBarton
A: 

What is the point of the ProgressBar component then if it won't show progress? Shouldn't the documentation for the component cover this? Would seem to be a big thing to leave out.

I'm trying to use one to reflect the filling of a video player buffer. Are you saying I'd have to pause the buffer filling work each time I want to update the progress bar?

It just seems odd when you consider that there will "always" be work going on while trying to update the progress bar. That's what a progress bar is - shows the progress of work. Did anyone consider this when they implemented it? If so, where is the documentation for how to deal with the problem? In all of the "how to use the progress bar" articles out there, this is never mentioned. What am I missing?

Robert
+1  A: 

Glenn,

That is not at all how the threading in Flex works whatsoever. Like many UIs it has a message pump on the main UI thread (they do it in frames). When you call callLater() it places the passed in function pointer at the end of the message pump queue (on the next frame) and returns immediately. The function then gets called when the message pump has finished processing all of the messages prior (like mouse clicks).

The issue is that as the property change causes UI events to be triggered, they then place their own messages on the pump which now comes after your method call that you placed there from callLater().

Flex does have multiple threads but they are there for Adobe's own reasons and therefore are not user accessible. I don't know if there is a way to guarantee that a UI update will occur at a specific point, but an option is to call callLater a number of times until the operation occurs. Start off with a small number and increase until the number of iterations produces the result you want. Example:

// Change this to a number that works... it will probably be over 1, depending on what you're doing.
private const TOTAL_CALL_COUNT:int = 5;

private var _timesCalled:int = 0;

//----------------------------------------------------------------
private function set Progress( progress:int ):void
{
    progressBar.value = progress;
    DoNextFunction();
}

//----------------------------------------------------------------
private function DoNextFunction():void
{
    if( _timesCalled >= TOTAL_CALL_COUNT )
    {
        _timesCalled = 0;
        Function();
    }
    else
    {
        _timesCalled++;
        callLater( DoNextFunction );
    }
}