Java FX Thread Safe Pause/Sleep


Question

I have a JavaFX application, within it are about 20 labels. When a user clicks a label, I need the label to flash red. To make the label flash red, I am using a thread I actually developed for swing but converted to JavaFX. It worked for awhile, but I've recently traced the application lock-ups to the animation of the label. The way I did the animation was simple:

new Thread(new AnimateLabel(tl,idx)).start();

tl points to an ArrayList of labels, and idx to the index of it. Another label has an on click event attached to it, and when you click, it creates the thread that animates the label (makes it flash).

For some reason, this will cause the application to lock up if there's a lot of labels being pressed.

I'm pretty sure it's a thread safety issue with JavaFX. I have another thread that shares the JavaFX application thread as so:

TimerUpdater tu = new TimerUpdater(mDS);
Timeline incHandler = new Timeline(new KeyFrame(Duration.millis(130),tu)); 
incHandler.setCycleCount(Timeline.INDEFINITE); 
incHandler.play(); 

TimerUpdater will constantly update the text on labels, even the ones that are flashing.

Here's the label animator:

private class AnimateLabel implements Runnable
{
    private Label lbl;
    public AnimateLabel(Label lbl, int myIndex)
    {
        // if inAnimation.get(myIndex) changes from myAnim's value, stop,
        // another animation is taking over
        this.lbl = lbl;
    }

    @Override
    public void run() {
        int r, b, g;
        r=255;
        b=0;
        g=0;

        int i = 0;
        while(b <= 255 && g <= 255)
        {
            RGB rgb = getBackgroundStyle(lbl.getStyle());
            if(rgb != null)
            {
                if(rgb.g < g-16) { return; }
            }
            lbl.setStyle("-fx-color: #000; -fx-background-color: rgb("+r+","+g+","+b+");");
            try { Thread.sleep(6); }
            catch (Exception e){}
            b += 4;
            g += 4;
            ++i;
        }
        lbl.setStyle("-fx-color: #000; -fx-background-color: fff;");
    }
}

I would run this as such:

javafx.application.Platform.runLater(new AnimateLabel(tl, idx));

However, Thread.sleep(6) will be ignored. Is there a way to pause in a run later to control the speed of the animation while sharing a thread with javafx.application?

Regards, Dustin

1
1
12/9/2012 11:27:01 AM

I think there's a slight misunderstanding of how the JavaFX event queue works.

1) Running your AnimateLabel code on a normal thread will cause the Label#setStyle(...) code to execute on that thread - this is illegal and likely to cause your issues.

2) Running the AnimateLabel code entirely on the JavaFX event queue, as per your second example, means that the event thread would be blocked until the animation is complete. Meanwhile, the application will not update, will not process user events or repaint, for that matter. Basically, you're changing the label style within a loop, but you're not giving the event queue time to actually redraw the label, which is why you won't see anything on screen.

The semi-correct approach is a mixture of both. Run AnimateLabel in a separate thread, but wrap the calls to Label#setStyle(...) in a Platform#runLater(...). This way you'll only bother the event thread with the relevant work, and leave it free to do other work in between (such as updating the UI).

As I said, this is the semi-correct approach since there's a build-in facility to do what you want in an easier fashion. You might want to check out the Transition class. It offers a simple approach for custom animations and even offers a bunch of prebuilt subclasses for animating the most common properties of a Node.

4
12/9/2012 12:21:36 PM

Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon