In JavaFX, it seems appropriate to me to use AnimationTimer for frequently updating Canvas Nodes; that is, for rasterized animations instead of vector animations. I'm running into an issue, which may be VSync related. I have a singular Canvas on my Scene which contains 2D data that changes frequently (the rest of the program is done with Nodes). I tend to get a tearing down the middle of the screen when I do this, which implies that the AnimationTimer may be falling out of sync with the monitor refresh rate.
This (simplified) code shows the error in detail. Note that I have no idea whether it's display hardware dependent, and the exact position of the tear may shift slightly.
import javafx.application.*;
import javafx.animation.*;
import javafx.scene.*;
import javafx.scene.canvas.*;
import javafx.stage.*;
import javafx.event.*;
import javafx.scene.input.*;
import javafx.scene.paint.*;
public class BugDemo extends Application {
public static void main(String...args) {
launch(args);
}
private Canvas background = new Canvas();
private Color bgColor = Color.rgb(0, 0, 0);
#Override
public void start(Stage primaryStage) throws Exception {
Group root = new Group();
startLoops(root);
Scene scene = new Scene(root);
initUserControls(scene);
background.widthProperty().bind(scene.widthProperty());
background.heightProperty().bind(scene.heightProperty());
root.getChildren().add(background);
primaryStage.setScene(scene);
primaryStage.setTitle("Bug Demo");
primaryStage.setMaximized(true);
primaryStage.show();
}
/**
* Initializes all running control managers for game field
* #param scene primary scene of game
*/
protected void initUserControls(Scene scene) {
EventHandler<MouseEvent> handler = new EventHandler<MouseEvent>(){
#Override
public void handle(MouseEvent event) {
bgColor = Color.rgb((int)((event.getX() / background.getWidth()) * 255),
(int)((event.getY() / background.getHeight()) * 255),
255);
}};
scene.getRoot().setOnMouseMoved(handler);
scene.getRoot().setOnMouseDragged(handler);
}
/**
* Starts all game loops, which will at the beginning of the simulation and often for the extent
* of it.
* #param parent Root node
*/
protected void startLoops(Parent parent) {
GraphicsContext gc = background.getGraphicsContext2D();
new AnimationTimer() {
#Override
public void handle(long now) {
drawBackground(gc);
}}.start();
}
protected void drawBackground(GraphicsContext gc) {
gc.setFill(bgColor);
gc.fillRect(0, 0, background.getWidth(), background.getHeight());
}
}
Moving the mouse cursor over the scene will cause the canvas to shift in background color. As you move the mouse, you will notice (presumably) a tearing in the frame.
It is quite possible that I'm simply being a N00B at JavaFX, and there's a perfectly friendly .sync()-ish method I'm supposed to call somewhere; but I haven't found it yet. I've found bugs submitted to Oracle that look similar to this. My answer may simply be dodging using Canvas and AnimationTimer for animations. All the same, even if there isn't a solution at this time, if someone has a workaround, I would love to hear about it!
Thanks!
Found the issue (thank you to #VGR for pointing out the lack of system behavioral congruity)! Seems that it wasn't Java at all; it was Marco. I switched Desktop Windowing Managers, to Compiz specifically; and the tearing is now gone.
Evidently this isn't a bug in Java at all; it's a trade-off for using Marco. This is a relief!
Related
Setup:
The following code, renders a 3D scene with a visible co-ordinate axis positioned at the origin, from a camera displaced by -156 units in the Z direction. Also, the camera's Z position is a function of mouse scroll, such that scrolling up/down will move the camera further/closer from the origin.
Problem:
upon initial startup of the program, the red and green axis are rendered at/near the origin, when in the physical world, it would be impossible to see them there from the current camera view. (blue axis blocking them). Also, when you scroll backwards and forwards, you can see glitches/flashes where the red/green axis are visible behind the blue axis, which should not occur.
Screenshot of result (with my manual adding of issue description):
initial_screenshot
Question:
1) Is this a problem with my setup? or JavaFX?
2) if this is a problem with my setup, then can someone please explain what I can do to remedy this issue?
Code:
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package testproblemjavafx01;
/**
*
* #author ad
*/
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.event.EventHandler;
import javafx.scene.Camera;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.input.ScrollEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.stage.Stage;
public class TestProblemJavaFX01 extends Application {
#Override
public void start(Stage primaryStage) {
Group root = new Group();
buildAxes(root);
Scene scene = new Scene(root, 600, 400, true, SceneAntialiasing.BALANCED);
PerspectiveCamera camera = new PerspectiveCamera(true);
scene.setFill(Color.WHITE);
camera.setNearClip(0);
camera.setFarClip(1000.0);
camera.setTranslateX(0);
camera.setTranslateY(0);
camera.setTranslateZ(-156);
scene.setCamera(camera);
setMouseEvents(scene);
primaryStage.setResizable(false);
primaryStage.setTitle("TestProblemJavaFX01");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
private void buildAxes(Group root) {
final PhongMaterial redMaterial = new PhongMaterial();
redMaterial.setDiffuseColor(Color.DARKRED);
redMaterial.setSpecularColor(Color.RED);
final PhongMaterial greenMaterial = new PhongMaterial();
greenMaterial.setDiffuseColor(Color.DARKGREEN);
greenMaterial.setSpecularColor(Color.GREEN);
final PhongMaterial blueMaterial = new PhongMaterial();
blueMaterial.setDiffuseColor(Color.DARKBLUE);
blueMaterial.setSpecularColor(Color.BLUE);
final Box xAxis = new Box(240.0, 1, 1);
final Box yAxis = new Box(1, 240.0, 1);
final Box zAxis = new Box(1, 1, 240.0);
xAxis.setMaterial(redMaterial);
yAxis.setMaterial(greenMaterial);
zAxis.setMaterial(blueMaterial);
root.getChildren().addAll(xAxis, yAxis, zAxis);
}
private void setMouseEvents(final Scene scene) {
scene.setOnScroll(
new EventHandler<ScrollEvent>() {
#Override
public void handle(ScrollEvent event) {
double deltaY = event.getDeltaY();
Camera camera = scene.getCamera();
camera.setTranslateZ(camera.getTranslateZ() + deltaY);
event.consume();
}
});
}
}
I think the problem is within the line camera.setNearClip(0);
From the documentation of setNearClip:
Specifies the distance from the eye of the near clipping plane of this
Camera in the eye coordinate space. Objects closer to the eye than
nearClip are not drawn. nearClip is specified as a value greater than
zero. A value less than or equal to zero is treated as a very small
positive number.
Try to set the value to its default value of 0.1. Or just remove the line.
I am developing a software which gets an image from a camera and displays it live in a JavaFX ImageView. I have a thread which gets the last image (in this case a BufferedImage), and an AnimationTimer that assigns it to the ImageView. The reason I went ahead with an AnimationTimer was it seemed better than to fill the Platform with Runnable each time a new image is obtained. The refresh works fine, and FPS are decent.
However, I noticed that when the AnimationTimer was running, the menu bar in my software was not displayed properly. Some menu items went missing when I moused over others. This picture explains it:
On the left side, you have what the menu normally looks like, and on the right side, what it looks like when the AnimationTimer is running. As you can see, the "Save" menu item is missing, and the background of my live image is displayed instead. Moreover, when I was opening a new window (on a new Scene), when I moused over any kind of Node (a button, a checkbox...), the background turned black. I was able to fix this problem by setting the depth-buffer boolean to true when initializing the Scene. I have however no clue how to fix this menu bar bug, and I think these bugs show what I am doing is probably not right.
I was thinking maybe the JavaFX application thread was saturated with new images to display, and it was basically taking too much time for other elements (such as a menu item) to be painted.
Questions:
Is that really where this bug comes from?
Is there a way to improve my code, using something different from an AnimationTimer for instance?
Here's a code snippet that reproduces the bug. Change the two strings in the start function to path to images. The images should be relatively large (several MB).
Click on the "Start" button to start the animation timer. Then try to open the "File" menu, and mouse over the menu items. The bug does not appear systematically, try repeating moving your mouse up and down over, it should appear at some point.
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
public class ImageRefresher extends Application {
#Override
public void start(Stage primaryStage) {
//Here change the 2 Strings to a path to an image on your HDD
//The bug appears more easily with large images (>3-4MB)
String pathToImage1 = "/path/to/your/first/image";
String pathToImage2 = "/path/to/your/second/image";
try {
//Image content (contains buffered image, see below)
ImageContent image = new ImageContent(pathToImage1);
//If this line is commented, the bug does not appear
image.setImage(ImageIO.read(new File(pathToImage2)));
//JavaFX class containing nodes (see below)
MainWindow window = new MainWindow(image);
Scene scene = new Scene(window.getPane(), 300, 250);
primaryStage.setTitle("Menu refresh");
primaryStage.setScene(scene);
primaryStage.show();
} catch (IOException ex) {
Logger.getLogger(ImageRefresher.class.getName()).log(Level.SEVERE, null, ex);
}
}
public static void main(String[] args) {
launch(args);
}
public class MainWindow {
private BorderPane pane;
private MenuBar menuBar;
private ImageView displayImage;
private Button startRefreshingButton;
private ImageContent imageContent;
private AnimationTimer animationTimer;
public MainWindow(ImageContent imageContent) {
this.imageContent = imageContent;
//Builds the window's components
buildGraphic();
//The image is reset at each frame
animationTimer = new AnimationTimer() {
#Override
public void handle(long now) {
displayImage.setImage(imageContent.getDisplayableImage());
}
};
}
private void buildGraphic() {
pane = new BorderPane();
menuBar = new MenuBar();
Menu menu = new Menu("File");
menu.getItems().addAll(new MenuItem("Save"),
new MenuItem("Open"),
new MenuItem("Close"));
menuBar.getMenus().add(menu);
displayImage = new ImageView();
startRefreshingButton = new Button("Start");
startRefreshingButton.setOnAction((event) -> {
animationTimer.start();
});
pane.setTop(menuBar);
pane.setCenter(displayImage);
pane.setBottom(startRefreshingButton);
}
public Pane getPane() {
return pane;
}
}
public class ImageContent {
private BufferedImage imageContent;
//Initializes bufferedimage with the path specified
public ImageContent(String pathToImage) throws IOException {
imageContent = ImageIO.read(new File(pathToImage));
}
public void setImage(BufferedImage newImage) {
imageContent = newImage;
}
//Function called by the animation timer to
//get a JavaFX image from a bufferedimage
public Image getDisplayableImage() {
return SwingFXUtils.toFXImage(imageContent, null);
}
}
}
I guess the issue is that since you're repainting the image every frame, you're overlaying the menu popup with the image. That seems like a bug, but you're also requesting way more work from the FX Application Thread than you need.
Ideally, you should find a way to check if there's really a new image, and only update the image if there's genuinely a new file. (Consider using java.nio.file.Path to represent the file and calling Files.getLastModifiedTime(path).)
For another way to avoid flooding the FX Application Thread with too many Platform.runLater(...) calls, see Throttling javafx gui updates
In the end I didn't file any issue on jira since I was able to solve my problem. The issue came from the way I called SwingFXUtils.toFXImage(imageContent, null). I returned this function's result on every frame, and I am not sure about the details but this probably created a new object every time. A simple way to avoid that is passing a WritableImage as parameter, and binding the ImageProperty value of the ImageView to it.
If I take the MCVE posted above this could something like this (not tested, probably cleaner solutions existing):
public class MainWindow {
private BorderPane pane;
private MenuBar menuBar;
private ImageView displayImage;
private Button startRefreshingButton;
private ImageContent imageContent;
private AnimationTimer animationTimer;
// Here's the value to bind
private ObservableValue<WritableImage> imageProperty;
public MainWindow(ImageContent imageContent) {
//initialization stuff
this.imageProperty = new ObservableValue<>(imageContent.getWritableImage());
displayImage.imageProperty().bind(imageProperty);
//The image is reset at each frame
animationTimer = new AnimationTimer() {
#Override
public void handle(long now) {
SwingFXUtils.toFXImage(imageContent.getBufferedImage(), image.getWritableImage());
}
};
}
}
public class ImageContent {
private BufferedImage imageContent;
private WritableImage writableImage;
//Initializes bufferedimage with the path specified
public ImageContent(String pathToImage) throws IOException {
imageContent = ImageIO.read(new File(pathToImage));
//Get the width and height values from your image
int width = imageContent.getWidth();
int height = imageContent.getHeight();
writableImage = new WritableImage(width, height);
}
public void setImage(BufferedImage newImage) {
imageContent = newImage;
}
public WritableImage getWritableImage() {
return writableImage;
}
public BufferedImage getBufferedImage() {
return imageContent;
}
}
However, this seems to be quite memory intensive now, I'll look into it.
I want an image to be resized automatically when the user drags the main window. Is that possible?
I have the following code that sets a window of a certain size. It also loads the image from an external URL.
#Override
public void start(Stage primaryStage) {
MenuBar menuBar=new MenuBar();
Menu menuGame = new Menu("Game");
MenuItem newGame = new MenuItem("New Game F1");
MenuItem exit = new MenuItem("Exit F2");
exit.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
primaryStage.close();
}
});
menuGame.getItems().addAll(newGame,new SeparatorMenuItem(),exit);
menuBar.getMenus().addAll(menuGame);
Image image = new Image("http://docs.oracle.com/javafx/"
+ "javafx/images/javafx-documentation.png");
ImageView imageView = new ImageView();
imageView.setImage(image);
VBox vbox=new VBox();
StackPane root=new StackPane();
root.getChildren().addAll(imageView);
vbox.getChildren().addAll(menuBar,root);
Scene scene= new Scene(vbox,400,400);
primaryStage.setScene(scene);
primaryStage.setMaxHeight(800);
primaryStage.setMinHeight(400);
primaryStage.setMaxWidth(1000);
primaryStage.setMinWidth(800);
primaryStage.setTitle("Minesweeper");
primaryStage.show();
}
Applying the solution to JavaFx image resizing to your sample code and resizing the window results in different image sizes for me.
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.beans.value.*;
import javafx.event.*;
import javafx.geometry.HPos;
import javafx.geometry.VPos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.image.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
public class ImageResizer extends Application {
#Override
public void start(Stage primaryStage) {
MenuBar menuBar=new MenuBar();
Menu menuGame = new Menu("Game");
MenuItem newGame = new MenuItem("New Game F1");
MenuItem exit = new MenuItem("Exit F2");
exit.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
primaryStage.close();
}
});
menuGame.getItems().addAll(newGame,new SeparatorMenuItem(),exit);
menuBar.getMenus().addAll(menuGame);
Image image = new Image("http://docs.oracle.com/javafx/"
+ "javafx/images/javafx-documentation.png");
ImageView imageView = new ImageView();
imageView.setImage(image);
ImageViewPane viewPane = new ImageViewPane(imageView);
VBox vbox=new VBox();
StackPane root=new StackPane();
root.getChildren().addAll(viewPane);
vbox.getChildren().addAll(menuBar,root);
VBox.setVgrow(root, Priority.ALWAYS);
Scene scene= new Scene(vbox,200,200);
primaryStage.setScene(scene);
primaryStage.setMaxHeight(400);
primaryStage.setMinHeight(200);
primaryStage.setMaxWidth(500);
primaryStage.setMinWidth(400);
primaryStage.setTitle("Minesweeper");
primaryStage.show();
}
public static void main(String[] args) { launch(); }
}
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*/
/**
*
* #author akouznet
*/
class ImageViewPane extends Region {
private ObjectProperty<ImageView> imageViewProperty = new SimpleObjectProperty<ImageView>();
public ObjectProperty<ImageView> imageViewProperty() {
return imageViewProperty;
}
public ImageView getImageView() {
return imageViewProperty.get();
}
public void setImageView(ImageView imageView) {
this.imageViewProperty.set(imageView);
}
public ImageViewPane() {
this(new ImageView());
}
#Override
protected void layoutChildren() {
ImageView imageView = imageViewProperty.get();
if (imageView != null) {
imageView.setFitWidth(getWidth());
imageView.setFitHeight(getHeight());
layoutInArea(imageView, 0, 0, getWidth(), getHeight(), 0, HPos.CENTER, VPos.CENTER);
}
super.layoutChildren();
}
public ImageViewPane(ImageView imageView) {
imageViewProperty.addListener(new ChangeListener<ImageView>() {
#Override
public void changed(ObservableValue<? extends ImageView> arg0, ImageView oldIV, ImageView newIV) {
if (oldIV != null) {
getChildren().remove(oldIV);
}
if (newIV != null) {
getChildren().add(newIV);
}
}
});
this.imageViewProperty.set(imageView);
}
}
Alternate approaches and extra information based upon comments
If you have to do all that, then it's a weakness in he JavaFX platform. Ideally I would expect there to have been a scale property on the image that one could set so that it uses the SceneGraph to determine it's size.
The solution presented above is just one answer, there are others possible. The scene graph can be used by binding various properties to the width and height of the parent node or by overriding layoutChildren in the parent node.
Related properties:
There is a scale property which can be applied to any node.
A node also has a transform list to which a Scale may be applied.
ImageView has a fitWidth and fitHeight properties (use of this is demonstrated in other answers).
I prefer the region subclass approach to a scale property or transform based approach because then the image is automatically sized based upon the layout manager. The ImageViewPane defined above is just a one-off definition class which can be reused as much as you want, the actual application code to use an ImageView or ImageViewPane is pretty much equivalent.
Another possible approach is to use a Region subclass (such as a Pane), which has a defined CSS style class or id and then to define the image in CSS as a background. The nice thing about a CSS defined image is that you can then use additional CSS based attributes to define things, like sizing, scaling, positioning and repeats for the image. This background can also be set programmatically if desired rather than via CSS.
Related question:
Resizing images to fit the parent node
To maintain proportional height and width of the image
The following code can be used in the ImageViewPane class provided above:
if (imageView.isPreserveRatio()) {
if (getHeight() > getWidth()) {
imageView.setFitWidth(getWidth());
imageView.setFitHeight(0);
} else {
imageView.setFitWidth(0);
imageView.setFitHeight(getHeight());
}
} else {
imageView.setFitWidth(getWidth());
imageView.setFitHeight(getHeight());
}
I know this is old but someone like me may stumble upon this so here is my solution. I am assuming that root is the root node of your scene. However, this should work if your parent node of the ImageView is any child of Pane.
imv = new ImageView();
root.getChildren().add(imv);
Image image = new Image(Main.class.getResourceAsStream("image.png"))
imv.setImage(image);
imv.fitWidthProperty().bind(center.widthProperty());
imv.fitHeightProperty().bind(center.heightProperty());
Just use a normal pane with a background image, here the css needed.
#titleImage{
-fx-background-image: url("../img/logo.png");
-fx-background-repeat: stretch;
-fx-background-position: center center;
}
Are you looking to resize an actual image, or just to center it?
You can try this: VBox.setVgrow(root, Priority.ALWAYS); and see if that's what you want.
Also, be careful with you image, because it's a png image. I'm a bit skeptic regarding what's resized there (the actual image or the transparent background).
I would like to draw some infinite object in Piccolo2D, like endless rectangular (cartesian) grid. It would be greate to have some geometric object on this grid, like in graphics editor.
Unfortunately, Piccolo somehow determines, whether it is required to call paint and does not do this appropriatedly for me.
The program below contains yellow object, which I want to make endless. It's type is PEndless. I am adding circle to it as a child.
package tests.endless;
import java.awt.Color;
import java.awt.geom.Rectangle2D;
import org.piccolo2d.PNode;
import org.piccolo2d.extras.PFrame;
import org.piccolo2d.nodes.PPath;
import org.piccolo2d.util.PPaintContext;
public class Try_PGrid {
public static class PEndless extends PNode {
#Override
protected void paint(PPaintContext paintContext) {
Rectangle2D localClip = paintContext.getLocalClip();
paintContext.getGraphics().setColor(Color.yellow);
paintContext.getGraphics().fill(localClip);
}
}
public static void main(String[] args) {
new PFrame() {
#Override
public void initialize() {
PPath circle = PPath.createEllipse(0, 0, 100, 100);
PEndless grid = new PEndless();
grid.addChild(circle);
//grid.setBounds(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); // not working at all
//grid.setBounds(-10, -10, 50, 50); // yellow until circle visible
grid.setBounds(-1000, -1000, 2000, 2000); // yellow until bounds
getCanvas().getLayer().addChild(grid);
}
};
}
}
Unfortunately, I found that:
1) if bounds are default (empty) then yellow paints only if circle is visible
2) if bounds are big and finite, then yellow paints withing bounds
3) it is not supporting infinite doubles, although this is legal in principle; if set infinite, it does no draw anything at all.
How to implement?
It's part of how Java handles the drawing.
Look here for more information on this and a guide on how to prevent this issue.
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
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.
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: