I am creating a game in which I am checking for collisions between two ImageView objects, but when I use the intersects method it doesn't quite work the way I want it to. Score in checkCollision() method is counted to huge size and even though first image is not touching second collision is already happening and I don't know why - my program should add a +1 whenever one object touches another.
Here code sample after improvemets based on minimal reproducible example:
import javafx.animation.PathTransition;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.scene.shape.CubicCurveTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.ArrayList;
public class TestCollision extends Application {
private Thread collisionThread;
private Scene scene;
private Pane pane;
private ImageView wolfIv;
private ArrayList<ImageView> eggsList;
private int score;
#Override
public void start(Stage primaryStage) throws Exception {
pane = new Pane();
scene = new Scene(pane,800, 600);
Image wolf = new Image("/images/wolf.png");
wolfIv = new ImageView(wolf);
eggsList = new ArrayList<>();
eggsList.add(new ImageView(new Image(getClass().getResourceAsStream("/images/egg.png"))));
eggsList.get(0).setFitHeight(45);
eggsList.get(0).setFitWidth(35);
pane.getChildren().add(eggsList.get(0));
MoveTo moveToEgg = new MoveTo();
moveToEgg.setX(60.0f);
moveToEgg.setY(95.0f);
Path eggPath = new Path();
eggPath.getElements().add(moveToEgg);
eggPath.getElements().add(new CubicCurveTo(190,200,190, 200,190,480));
PathTransition pathTransition = new PathTransition();
pathTransition.setDuration(Duration.millis(4000));
pathTransition.setNode(eggsList.get(0));
pathTransition.setPath(eggPath);
pathTransition.setCycleCount(PathTransition.INDEFINITE);
pathTransition.setAutoReverse(false);
pathTransition.play();
wolfIv.setFitWidth(200);
wolfIv.setFitHeight(250);
wolfIv.setX(140);
wolfIv.setY(218);
pane.getChildren().addAll(wolfIv);
primaryStage.setResizable(false);
primaryStage.setScene(scene);
primaryStage.show();
collisionCheckThread();
}
public void checkCollision(ImageView imageView, ImageView imageView2){
if(imageView.getBoundsInParent().intersects(imageView2.getBoundsInParent())){
score++;
System.out.println(score);
System.out.println("Boom");
}
}
public void collisionCheckThread()
{
collisionThread = new Thread(){
#Override
public void run(){
while(scene.getWindow().isShowing() == true){
checkCollision(wolfIv,eggsList.get(0));
}
}
};
collisionThread.start();
}
public static void main(String[] args) {
launch(args);
}
}
The reason the score "becomes huge" is that the background thread is repeatedly checking for collisions as fast and as often as it can. Any time it checks and the bounds intersect, it adds one to the score.
The reason you see incorrect results sometimes, such as a collision being detected immediately when none should happen, is more complex. This occurs because values (in particular the boundsInParent of the two image views) are being changed on one thread (the FX Application thread) and observed on another thread (your background thread) without proper synchronization. JavaFX, like most UI toolkits, is designed as a single-threaded toolkit, and so there is no way to add synchronization to this.
What actually happens here is due to something called "hoisting". The code in your background thread is essentially
public void run(){
while(scene.getWindow().isShowing() == true){
if(imageView.getBoundsInParent().intersects(imageView2.getBoundsInParent())){
score++;
System.out.println(score);
System.out.println("Boom");
}
}
}
It is legal, per the Java Language Specification, for a JVM to optimize code in certain ways. For variables that are not declared volatile, and without synchronization, the JVM is allowed to reorder code assuming that each thread is independent of each other. Under this assumption, since there are no changes to the boundsInParent made in this thread, the code can be treated as being equivalent to
public void run(){
if(imageView.getBoundsInParent().intersects(imageView2.getBoundsInParent())){
while(scene.getWindow().isShowing() == true){
score++;
System.out.println(score);
System.out.println("Boom");
}
}
}
Furthermore, this optimization may be made at an arbitrary time (when the JVM decides it may be beneficial), so if you are accessing the boundsInParent from a thread other than the FX Application Thread, the behavior of this code becomes essentially non-deterministic.
For more information, I recommend reading the relevant items in Joshua Bloch's book Effective Java. (No one who has spent more than an hour programming in Java should be without this book.)
Using a background thread here is completely the wrong approach anyway.
What you actually want to do, I assume, is add one to the score (and perhaps perform other actions) when the state of the images changes from "not intersecting" to "intersecting". You can do this by creating a BooleanBinding with the correct value, and which is bound to the two boundsInParent properties. Then register a listener with that binding and react when it changes from false to true:
BooleanBinding collision = Bindings.createBooleanBinding(
() -> wolfIv.getBoundsInParent().intersects(eggsList.get(0).getBoundsInParent()),
wolfIv.boundsInParentProperty(),
eggsList.get(0).boundsInParentProperty()
);
collision.addListener((obs, wasCollision, isNowCollision) -> {
if (isNowCollision) {
score++;
System.out.println(score);
System.out.println("Boom");
}
});
Here's a complete runnable example which demonstrates this:
import java.util.ArrayList;
import javafx.animation.PathTransition;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.CubicCurveTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.stage.Stage;
import javafx.util.Duration;
public class TestCollision extends Application {
private Scene scene;
private Pane pane;
private ImageView wolfIv;
private ArrayList<ImageView> eggsList;
private int score;
#Override
public void start(Stage primaryStage) throws Exception {
pane = new Pane();
scene = new Scene(pane,800, 600);
WritableImage wolf = new WritableImage(1, 1);
wolf.getPixelWriter().setColor(0, 0, Color.RED);
wolfIv = new ImageView(wolf);
eggsList = new ArrayList<>();
WritableImage egg = new WritableImage(1, 1);
egg.getPixelWriter().setColor(0, 0, Color.YELLOW);
eggsList.add(new ImageView(egg));
BooleanBinding collision = Bindings.createBooleanBinding(
() -> wolfIv.getBoundsInParent().intersects(eggsList.get(0).getBoundsInParent()),
wolfIv.boundsInParentProperty(),
eggsList.get(0).boundsInParentProperty()
);
collision.addListener((obs, wasCollision, isNowCollision) -> {
if (isNowCollision) {
score++;
System.out.println(score);
System.out.println("Boom");
}
});
eggsList.get(0).setFitHeight(45);
eggsList.get(0).setFitWidth(35);
pane.getChildren().add(eggsList.get(0));
MoveTo moveToEgg = new MoveTo();
moveToEgg.setX(60.0f);
moveToEgg.setY(95.0f);
Path eggPath = new Path();
eggPath.getElements().add(moveToEgg);
eggPath.getElements().add(new CubicCurveTo(190,200,190, 200,190,480));
PathTransition pathTransition = new PathTransition();
pathTransition.setDuration(Duration.millis(4000));
pathTransition.setNode(eggsList.get(0));
pathTransition.setPath(eggPath);
pathTransition.setCycleCount(PathTransition.INDEFINITE);
pathTransition.setAutoReverse(false);
pathTransition.play();
wolfIv.setFitWidth(200);
wolfIv.setFitHeight(250);
wolfIv.setX(140);
wolfIv.setY(218);
pane.getChildren().addAll(wolfIv);
primaryStage.setResizable(false);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Related
my apologies if this is an easy thing for you, but my mind boggles. After several years of not programming at all, I am working on a pet project (2d tile based game engine) where I would like to use Java FX headless in order to make use of the graphics capabilities.
I have understood from here and here
that you need to a Java FX Application in order to have the graphics system initialized.
So I basically took the ImageViewer example and implemented Runnable:
package net.ck.game.test;
import java.io.BufferedReader;
import java.util.ArrayList;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Logger;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class ImageTest extends Application implements Runnable {
protected static final Logger logger = (Logger) LogManager.getLogger(ImageTest.class);
BufferedReader x;
#Override public void start(#SuppressWarnings("exports") Stage stage) {
logger.error(Thread.currentThread().getName() + ", executing run() method!");
Image standardImage = new Image("file:graphics/image1.png");
logger.error("image height image1: "+ standardImage.getHeight());
logger.error("image width image1:" + standardImage.getWidth());
Image movingImage = new Image("file:graphics/image2.png");
ArrayList<Image> images = new ArrayList<Image>();
images.add(movingImage);
images.add(standardImage);
ImageView iv1 = new ImageView();
iv1.setImage(standardImage);
ImageView iv2 = new ImageView();
iv2.setImage(movingImage);
Group root = new Group();
Scene scene = new Scene(root);
scene.setFill(Color.BLACK);
HBox box = new HBox();
box.getChildren().add(iv1);
box.getChildren().add(iv2);
root.getChildren().add(box);
stage.setTitle("ImageView");
stage.setWidth(415);
stage.setHeight(200);
stage.setScene(scene);
stage.sizeToScene();
stage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void run()
{
Application.launch(ImageTest.class);
}
}
When I run this as its own application, this works fine and displays the two images I want it to display.
When I run it like this in the "game" constructor:
public class Game {
private boolean animated;
public boolean isAnimated() {
return animated;
}
public void setAnimated(boolean animated) {
this.animated = animated;
}
public Game() {
setAnimated(true);
if (isAnimated() == true)
{
ImageTest imageTest = new ImageTest();
new Thread(imageTest).start();
}
}
There are no errors, ImageTest runs in its own thread, the application window opens, but it is empty.
I do not understand this at all, why is that?
Can someone pleaese shed some light on this?
UPDATE:
I had different working contexts by accident. Fixing this fixed the problem.
UPDATE: I had different working contexts by accident. Fixing this fixed the problem.
I'm working on a simple app and it features a pie chart. My goal is that if a user hovers their mouse over any section of the chart, it will expand and present more information. In my program, the chart is made of 3 arcs. Here's my code.
import javafx.scene.Group; //Maybe too many imports, I just use em all
import javafx.scene.Scene; //because I'm lazy
import javafx.stage.Stage;
import javafx.util.Duration;
import javafx.scene.paint.Color;
import javafx.scene.shape.Arc;
import javafx.scene.shape.Rectangle;
import javafx.event.EventHandler;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.application.Application;
import javafx.scene.shape.*;
import javafx.scene.shape.Line;
import javafx.scene.text.Text;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.input.MouseEvent;
import javafx.animation.ScaleTransition;
import java.lang.Thread;
public class AnimatingDemo extends Application{
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
//create 3 arc variables
Arc arc1 = new Arc();
Arc arc2 = new Arc();
Arc arc3 = new Arc();
//set up arc1 in place and to right color
arc1.setFill(Color.rgb(35,25,43,1.0));
arc1.setCenterX(250);
arc1.setCenterY(250);
arc1.setRadiusX(100.0f);
arc1.setRadiusY(100.0f);
arc1.setStartAngle(315);
arc1.setLength(-90);
arc1.setType(ArcType.ROUND);
//set up and color arc2
arc2.setFill(Color.rgb(39,70,144,1.0));
arc2.setCenterX(250);
arc2.setCenterY(250);
arc2.setRadiusX(100.0f);
arc2.setRadiusY(100.0f);
arc2.setStartAngle(90);
arc2.setLength(-135);
arc2.setType(ArcType.ROUND);
//set up and color arc3
arc3.setFill(Color.rgb(54,65,86,1.0));
arc3.setCenterX(250);
arc3.setCenterY(250);
arc3.setRadiusX(100.0f);
arc3.setRadiusY(100.0f);
arc3.setStartAngle(225);
arc3.setLength(-135);
arc3.setType(ArcType.ROUND);
//create root group
Group root = new Group();
//set up window
//add nodes to root
root.getChildren().addAll(arc1,arc2,arc3);
Scene scene = new Scene(root, 500,500);
stage.setTitle("Testing arc animation");
stage.setScene(scene);
stage.show();
}
}
This is just a sample I made so I can recreate the problem, but it gets the point across. I did research about various methods of animation in Javafx. The animation class seemed most viable, so I tried it in my program. I used the following code:
arc1.addEventFilter(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
ScaleTransition st = new ScaleTransition(Duration.millis(1500),arc1);
st.setByX(0.3);
st.setByY(0.3);
st.setCycleCount(1);
st.setAutoReverse(false);
st.play();
}
});
I repeated it 3 times for each arc but that's redundant here.
Anyway, the result is that both ends of the arc scale, so the pi chart looks messy and isn't centered anymore, also the scaling increases depending on the size of the arc so it's inconsistent.
I then decided to move onto a more basic method, using thread.sleep().
arc1.addEventFilter(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
for(int a = 0; a < 10; a++) {
try {
arc1.setRadiusX(arc1.getRadiusX() + 1);
arc1.setRadiusY(arc1.getRadiusY() + 1);
Thread.sleep(100);
}
catch(InterruptedException e) {
e.printStackTrace();
}
}
}
});
this doesn't work either, the circle just instantly expands by the given amount of units I wanted (instantly).
So my question is, what can I do? Is there a method to prevent the skewing in the animation class? Can I make the thread.sleep animation fluent in some way? Any advice helps, thank you for your time!
P.S. If it needs more comments let me know
I'm not sure this is exactly what you are looking for, since you went with Arc's instead of a proper PieChart, but I used the PieChart class and did everything you did and it works out just fine.
Here I took the PieChart.Data class and made three separate ones just for testing.
I added an EventFilter by calling .getNode() on the PieChart.Data variable, and then just pasted your method you supplied above.
Again, I assume there was a reason you used Arc instead of PieChart, but I got it to work this way.
#Override
public void start(Stage stage) {
ObservableList<PieChart.Data> data = FXCollections.observableArrayList();
PieChart pieChart = new PieChart(data);
PieChart.Data one = new PieChart.Data("one", 50.0);
PieChart.Data two = new PieChart.Data("two", 33.0);
PieChart.Data three = new PieChart.Data("three", 17.0);
data.addAll(one, two, three);
one.getNode().addEventFilter(MouseEvent.MOUSE_PRESSED, mouseEvent -> {
ScaleTransition st = new ScaleTransition(Duration.millis(1500),one.getNode());
st.setByX(0.3);
st.setByY(0.3);
st.setCycleCount(1);
st.setAutoReverse(false);
st.play();
});
two.getNode().addEventFilter(MouseEvent.MOUSE_PRESSED, mouseEvent -> {
ScaleTransition st = new ScaleTransition(Duration.millis(1500),two.getNode());
st.setByX(0.3);
st.setByY(0.3);
st.setCycleCount(1);
st.setAutoReverse(false);
st.play();
});
three.getNode().addEventFilter(MouseEvent.MOUSE_PRESSED, mouseEvent -> {
ScaleTransition st = new ScaleTransition(Duration.millis(1500),three.getNode());
st.setByX(0.3);
st.setByY(0.3);
st.setCycleCount(1);
st.setAutoReverse(false);
st.play();
});
//create root group
Group root = new Group();
//set up window
//add nodes to root
root.getChildren().addAll(pieChart);
Scene scene = new Scene(root, 500,500);
stage.setTitle("Testing arc animation");
stage.setScene(scene);
stage.show();
}
My goal is to create a basic 2D game using the Java Fx library. And my subgoal is to create responsive controls and smooth movement of the character. My issue is the player in my game (ImageView node) is a little jerky when I use setTranslate on it. It seems to jerk for every second of continuous movement. Included in this post is an example. I've done research and I've been given conflicting answers... For instance, one person told me to use a WritableImage(because he said it's like BufferedImage), but that didn't seem to solve the jerkyness issue. Any help would be most appreciated.
UPDATE: Thank you for the responses. I found out what the issue was. I use a program called f.lux that automatically adjusts screen brightness based on the time of day and it was drastically affecting the game's performance for some reason.
package pleasehelp;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class PleaseHelp extends Application {
ImageView imageView;
public void start(Stage stage) {
Pane root = new Pane();
Scene scene = new Scene(root, 800, 200);
stage.setTitle("Please Help!");
stage.setScene(scene);
Animation animation = new Animation(this);
Image image = new Image("http://lessonpix.com/drawings/192/100x100/Gray+Square.png");
imageView = new ImageView();
imageView.setImage(image);
root.getChildren().add(imageView);
animation.start();
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
package pleasehelp;
import javafx.animation.AnimationTimer;
public class Animation extends AnimationTimer {
PleaseHelp please;
Animation(PleaseHelp please) {
this.please = please;
}
#Override
public void handle(long now) {
please.imageView.setTranslateX(please.imageView.getTranslateX() + 1);
}
public void start() {
super.start();
}
public void stop() {
super.stop();
}
}
When writing animations like this it is normally a good idea to compute the next position based on the given time (long now) and not just add a constant value to the current position. That would make your animation more predictive.
I'm struggling with making an animation showing the searchin in Binary Search Tree in JavaFX.
The goal is to make a visualization of comparing the value of tree node with possibility to:
pause and play it any time
being able to play the animation backwards (to go at least one step back),
give a user an ability to play the animation step-by-step or whole at once.
The preview of visualization
My vision was to make a series of TranslateTransitions(TT) added in one SequentialTransition(ST). If the animation is marked as "step-by-step" the each TT pauses the whole ST in their OnFinished handler. However this kinda works only for going one-way.
My question is. What is the best approach to maintain going fluent and step-by-step animation in reverse direction ?
I was thinking about:
maybe making another sequence of inverse transitions (but how to tell
it from which step to continue ?)
somehow work with rate property ? is it possible to change it while the ST is running ?
Thank you very much for your answers.
In general, you can change the rate property of an Animation while it is in progress. The idea of using a SequentialTransition is appealing, but it doesn't work as easily as you might think. The problem arises when the sequential transition is paused at the boundary between two individual transitions: you don't have any way to tell which of the individual transitions is considered the current one (i.e. the next one or the previous one). So when you try to reverse the rate and play, the sequential transition can get confused and immediately think it's at the end of the one it's trying to play.
You might be able to hack this a little by using Animation.getCurrentTime() and Animation.jumpTo(...) to "nudge" the sequential transition a tiny amount in the correct direction before starting to play any step, but I think it's probably easier just to manage the individual transitions on their own instead of using a SequentialTransition.
Here's a simple example of using this technique to move a rectangle around:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javafx.animation.Animation;
import javafx.animation.Animation.Status;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class ReverseSequentialTransitionTest extends Application {
#Override
public void start(Stage primaryStage) {
Pane pane = new Pane();
Rectangle rect = new Rectangle(50, 50, 250, 150);
rect.setFill(Color.color(.5, .5, .1));
pane.getChildren().add(rect);
TranslateTransition ttForward = new TranslateTransition(Duration.seconds(1), rect);
ttForward.setFromX(0);
ttForward.setToX(400);
TranslateTransition ttDown = new TranslateTransition(Duration.seconds(1), rect);
ttDown.setFromY(0);
ttDown.setToY(100);
TranslateTransition ttBackward = new TranslateTransition(Duration.seconds(1), rect);
ttBackward.setFromX(400);
ttBackward.setToX(0);
TranslateTransition ttUp = new TranslateTransition(Duration.seconds(1), rect);
ttUp.setFromY(100);
ttUp.setToY(0);
List<Animation> transitions = Arrays.asList(ttForward, ttDown, ttBackward, ttUp);
IntegerProperty nextTransitionIndex = new SimpleIntegerProperty();
Button playButton = new Button("Play Forward");
playButton.setOnAction(event -> {
int index = nextTransitionIndex.get();
Animation anim = transitions.get(index);
anim.setOnFinished(evt -> nextTransitionIndex.set(index+1));
anim.setRate(1);
anim.play();
});
Button reverseButton = new Button("Play backward");
reverseButton.setOnAction(event -> {
int index = nextTransitionIndex.get()-1;
Animation anim = transitions.get(index);
anim.setOnFinished(evt -> nextTransitionIndex.set(index));
anim.setRate(-1);
anim.play();
});
// This is not really part of the answer to the current question, but the
// next three statements just disable the buttons when appropriate.
// This is a binding which is true if and only if any of the transitions are
// currently running:
BooleanBinding anyPlaying = createAnyPlayingBinding(transitions);
// Disable playButton if we are at the end of the last transition, or if
// any transitions are playing:
playButton.disableProperty().bind(
nextTransitionIndex.greaterThanOrEqualTo(transitions.size())
.or(anyPlaying)
);
// Disable reverseButton if we are at the beginning of the first transition,
// or if any transitions are currently playing:
reverseButton.disableProperty().bind(
nextTransitionIndex.lessThanOrEqualTo(0)
.or(anyPlaying));
HBox controls = new HBox(5);
controls.setAlignment(Pos.CENTER);
controls.getChildren().addAll(playButton, reverseButton);
BorderPane root = new BorderPane();
root.setCenter(pane);
root.setBottom(controls);
primaryStage.setScene(new Scene(root, 800, 400));
primaryStage.show();
}
private BooleanBinding createAnyPlayingBinding(List<Animation> transitions) {
return new BooleanBinding() {
{ // Anonymous constructor
// bind to the status properties of all the transitions
// (i.e. mark this binding as invalid if any of the status properties change)
transitions.stream()
.map(Animation::statusProperty)
.forEach(this::bind);
}
#Override
protected boolean computeValue() {
// return true if any of the transitions statuses are equal to RUNNING:
return transitions.stream()
.anyMatch(anim -> anim.getStatus()==Status.RUNNING);
}
};
}
public static void main(String[] args) {
launch(args);
}
}
In JDK 7, the event handler for the playButton looks like this:
playButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
final int index = nextTransitionIndex.get();
Animation anim = transitions.get(index);
anim.setOnFinished(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent evt) {
nextTransitionIndex.set(index + 1) ;
}
});
anim.setRate(1);
anim.play();
}
});
and similarly for reverseButton. You will need to declare a couple of things as final as well. The createAnyPlayingBinding method is something like
private BooleanBinding createAnyPlayingBinding(final List<Animation> transitions) {
return new BooleanBinding() {
{
for (Animation transition : transitions) {
this.bind(transition.statusProperty();
}
}
#Override
protected boolean computeValue() {
// return true if any of the transitions statuses are equal to RUNNING:
for (Animation anim : transitions) {
if (anim.getStatus() == Status.RUNNING) {
return true ;
}
}
return false ;
}
};
}
I am developing an application with several TextField objects that need to be updated to reflect changes in associated back-end properties. The TextFields are not editable, only the back-end may change their content.
As I understand, the correct way about this is to run the heavy computation on a separate thread so as not to block the UI. I did this using javafx.concurrent.Task and communicated a single value back to the JavaFX thread using updateMessage(), which worked well. However, I need more than one value to be updated as the back-end does its crunching.
Since the back-end values are stored as JavaFX properties, I tried simply binding them to the textProperty of each GUI element and let the bindings do the work. This doesn't work, however; after running for a few moments, the TextFields stop updating even though the back-end task is still running. No exceptions are raised.
I also tried using Platform.runLater() to actively update the TextFields rather than binding. The issue here is that the runLater() tasks are scheduled faster than the platform can run them, and so the GUI becomes sluggish and needs to time to "catch up" even after the back-end task is finished.
I found a few questions on here:
Logger entries translated to the UI stops being updated with time
Multithreading in JavaFX hangs the UI
but my issue persists.
In summary: I have a back-end making changes to properties, and I want those changes to appear on the GUI. The back-end is a genetic algorithm, so its operation is broken down into discrete generations. What I would like is for the TextFields to refresh at least once in between generations, even if this delays the next generation. It is more important that the GUI responds well than that the GA runs fast.
I can post a few code examples if I haven't made the issue clear.
UPDATE
I managed to do it following James_D's suggestion. To solve the issue of the back-end having to wait for the console to print, I implemented a buffered console of sorts. It stores the strings to print in a StringBuffer and actually appends them to the TextArea when a flush() method is called. I used an AtomicBoolean to prevent the next generation from happening until the flush is complete, as it is done by a Platform.runLater() runnable. Also note that this solution is incredibly slow.
Not sure if I completely understand, but I think this may help.
Using Platform.runLater(...) is an appropriate approach for this.
The trick to avoiding flooding the FX Application Thread is to use an Atomic variable to store the value you're interested in. In the Platform.runLater method, retrieve it and set it to a sentinel value. From your background thread, update the Atomic variable, but only issue a new Platform.runLater if it's been set back to its sentinel value.
I figured this out by looking at the source code for Task. Have a look at how the updateMessage method (line 1131 at the time of writing) is implemented.
Here's an example which uses the same technique. This just has a (busy) background thread which counts as fast as it can, updating an IntegerProperty. An observer watches that property and updates an AtomicInteger with the new value. If the current value of the AtomicInteger is -1, it schedules a Platform.runLater.
In the Platform.runLater, I retrieve the value of the AtomicInteger and use it to update a Label, setting the value back to -1 in the process. This signals that I am ready for another UI update.
import java.text.NumberFormat;
import java.util.concurrent.atomic.AtomicInteger;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
public class ConcurrentModel extends Application {
#Override
public void start(Stage primaryStage) {
final AtomicInteger count = new AtomicInteger(-1);
final AnchorPane root = new AnchorPane();
final Label label = new Label();
final Model model = new Model();
final NumberFormat formatter = NumberFormat.getIntegerInstance();
formatter.setGroupingUsed(true);
model.intProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(final ObservableValue<? extends Number> observable,
final Number oldValue, final Number newValue) {
if (count.getAndSet(newValue.intValue()) == -1) {
Platform.runLater(new Runnable() {
#Override
public void run() {
long value = count.getAndSet(-1);
label.setText(formatter.format(value));
}
});
}
}
});
final Button startButton = new Button("Start");
startButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
model.start();
}
});
AnchorPane.setTopAnchor(label, 10.0);
AnchorPane.setLeftAnchor(label, 10.0);
AnchorPane.setBottomAnchor(startButton, 10.0);
AnchorPane.setLeftAnchor(startButton, 10.0);
root.getChildren().addAll(label, startButton);
Scene scene = new Scene(root, 100, 100);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
public class Model extends Thread {
private IntegerProperty intProperty;
public Model() {
intProperty = new SimpleIntegerProperty(this, "int", 0);
setDaemon(true);
}
public int getInt() {
return intProperty.get();
}
public IntegerProperty intProperty() {
return intProperty;
}
#Override
public void run() {
while (true) {
intProperty.set(intProperty.get() + 1);
}
}
}
}
If you really want to "drive" the back end from the UI: that is throttle the speed of the backend implementation so you see all updates, consider using an AnimationTimer. An AnimationTimer has a handle(...) which is called once per frame render. So you could block the back-end implementation (for example by using a blocking queue) and release it once per invocation of the handle method. The handle(...) method is invoked on the FX Application Thread.
The handle(...) method takes a parameter which is a timestamp (in nanoseconds), so you can use that to slow the updates further, if once per frame is too fast.
For example:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.property.LongProperty;
import javafx.beans.property.SimpleLongProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
final BlockingQueue<String> messageQueue = new ArrayBlockingQueue<>(1);
TextArea console = new TextArea();
Button startButton = new Button("Start");
startButton.setOnAction(event -> {
MessageProducer producer = new MessageProducer(messageQueue);
Thread t = new Thread(producer);
t.setDaemon(true);
t.start();
});
final LongProperty lastUpdate = new SimpleLongProperty();
final long minUpdateInterval = 0 ; // nanoseconds. Set to higher number to slow output.
AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long now) {
if (now - lastUpdate.get() > minUpdateInterval) {
final String message = messageQueue.poll();
if (message != null) {
console.appendText("\n" + message);
}
lastUpdate.set(now);
}
}
};
timer.start();
HBox controls = new HBox(5, startButton);
controls.setPadding(new Insets(10));
controls.setAlignment(Pos.CENTER);
BorderPane root = new BorderPane(console, null, null, controls, null);
Scene scene = new Scene(root,600,400);
primaryStage.setScene(scene);
primaryStage.show();
}
private static class MessageProducer implements Runnable {
private final BlockingQueue<String> messageQueue ;
public MessageProducer(BlockingQueue<String> messageQueue) {
this.messageQueue = messageQueue ;
}
#Override
public void run() {
long messageCount = 0 ;
try {
while (true) {
final String message = "Message " + (++messageCount);
messageQueue.put(message);
}
} catch (InterruptedException exc) {
System.out.println("Message producer interrupted: exiting.");
}
}
}
public static void main(String[] args) {
launch(args);
}
}
The best way to performing this is by usage of Task in JavaFx. This is be by far the best technique I've come across to update UI Controls in JavaFx.
Task task = new Task<Void>() {
#Override public Void call() {
static final int max = 1000000;
for (int i=1; i<=max; i++) {
updateProgress(i, max);
}
return null;
}
};
ProgressBar bar = new ProgressBar();
bar.progressProperty().bind(task.progressProperty());
new Thread(task).start();