Reputation: 248
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
Upvotes: 1
Views: 6521
Reputation: 159291
Sarcan has an excellent answer.
This answer just provides sample code (base on zonski's forum post) for a Timeline approach to modifying a css style. The code can be used as an alternative to the code you post in your question.
An advantage of this approach is that the JavaFX libraries handle all of the threading issues, ensuring all of your code is executed on the JavaFX thread and eliminating any thread safety concerns you may have.
import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.beans.value.*;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.TilePane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class CssFlash extends Application {
private Label flashOnClick(final Label label) {
label.setStyle(String.format("-fx-padding: 5px; -fx-background-radius: 5px; -fx-background-color: lightblue;"));
DoubleProperty opacity = new SimpleDoubleProperty();
opacity.addListener(new ChangeListener<Number>() {
@Override public void changed(ObservableValue<? extends Number> source, Number oldValue, Number newValue) {
label.setStyle(String.format("-fx-padding: 5px; -fx-background-radius: 5px; -fx-background-color: rgba(255, 0, 0, %f)", newValue.doubleValue()));
}
});
final Timeline flashAnimation = new Timeline(
new KeyFrame(Duration.ZERO, new KeyValue(opacity, 1)),
new KeyFrame(Duration.millis(500), new KeyValue(opacity, 0)));
flashAnimation.setCycleCount(Animation.INDEFINITE);
flashAnimation.setAutoReverse(true);
label.setOnMouseClicked(new EventHandler<MouseEvent>() {
@Override public void handle(MouseEvent t) {
if (Animation.Status.STOPPED.equals(flashAnimation.getStatus())) {
flashAnimation.play();
} else {
flashAnimation.stop();
label.setStyle(String.format("-fx-padding: 5px; -fx-background-radius: 5px; -fx-background-color: lightblue;"));
}
}
});
return label;
}
@Override public void start(Stage stage) throws Exception {
TilePane layout = new TilePane(5, 5);
layout.setStyle("-fx-background-color: whitesmoke; -fx-padding: 10;");
for (int i = 0; i < NUM_LABELS; i++) {
layout.getChildren().add(flashOnClick(new Label("Click to flash")));
}
Scene scene = new Scene(layout);
stage.setScene(scene);
stage.show();
}
private static final int NUM_LABELS = 20;
public static void main(String[] args) { Application.launch(args); }
}
JavaFX 8 will allow you to set the background of a region using a Java API, so that, if you wanted, you could accomplish the same thing without css.
Sample program output with a few of the labels clicked and in various states of flashing:
Upvotes: 3
Reputation: 3165
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.
Upvotes: 4