Issue
I'm trying to bind the color(fill) that is ObjectProperty<Paint> (fillProperty)
of a JavaFX Circle Shape. For that, I am using a low level ObjectBinding. I want to use the Color.hsb()
to create a new color using the hue value which will be scaled according to the desiredSpeedProperty which is stored in the Car class, and the actual speed of the car.
I have tried multiple ways of binding the speed value of the Car including DoubleBinding to store the speed value. I have also explicitly tried to getFill()
of the Circle shape as I was assuming my problem was caused by the laziness of Java. But still no success.
Circle carShape = new Circle();
ObjectBinding<Paint> colorObjectBinding = new ObjectBinding<Paint>() {
{
super.bind(car.getSpeed().getXProperty(),car.getSpeed().getYProperty(),car.getParameters().desiredSpeedProperty());
}
@Override
protected Paint computeValue() {
double speedX = car.getSpeed().getX();
double speedY = car.getSpeed().getY();
Color color = Color.hsb(Math.sqrt(speedX*speedX+speedY*speedY)*110/car.getParameters().getDesiredSpeed(),0.94,0.94,0.94);
return color;
}
};
carShape.fillProperty().bind(colorObjectBinding);
car.speed
is of type Vector2D
which uses DoubleProperty
to store the x and y value of a Vector.
public class Vector2D implements Iterable<DoubleProperty>{
private DoubleProperty xProperty = new SimpleDoubleProperty(0);
private DoubleProperty yProperty = new SimpleDoubleProperty(0);
public DoubleProperty getXProperty() { return xProperty; }
public DoubleProperty getYProperty() { return yProperty; }
public double getX() { return xProperty.getValue(); }
public double getY() { return yProperty.getValue(); }
The desiredSpeed
is also a DoubleProperty
private DoubleProperty desiredSpeed = new SimpleDoubleProperty(0);
public double getDesiredSpeed() { return desiredSpeed.get(); }
I want the color of the car to change accordingly with the change of speed. But the Circles are colored just once upon creation with the color Red (0xf00e0ef0)
(which I suspect is because the initial speed is zero thus giving hue = 0 for which the color is red)
Update
I have found my mistake, the problem was in the method car.getSpeed()
public Vector2D getSpeed() { return new Vector2D(speed); }
Which as you can see is returning a copy of the speed object, which is then bound, which is of course non-sense since the copy will most likely never be changed or used again. :( :0
Solution
This is a working minimal, complete, verifiable example of how i finally managed to bind
the fillProperty
of the Circle
shape. The timeline is just a way to check if color change is responding.
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
import tomek.szypula.math.Vector2D;
import java.util.concurrent.Callable;
public class ColorBindingTest extends Application {
public static void main(String[] args) {
launch(args);
}
final private double maxSpeed = 10;
final private double maxColor = 110; //Green
@Override
public void start(Stage primaryStage) {
Group root = new Group();
Scene theScene = new Scene( root,1100, 800 );
primaryStage.setTitle("ColorBindingTest");
primaryStage.setScene(theScene);
Circle carShape = new Circle();
Vector2D speed = new Vector2D();
carShape.setCenterX(100);
carShape.setCenterY(100);
carShape.setRadius(10);
root.getChildren().add(carShape);
ObjectBinding<Color> colorObjectBinding1 = Bindings.createObjectBinding(
new Callable<Color>() {
@Override
public Color call() throws Exception {
double speedX = speed.getX();
double speedY = speed.getY();
Color color = Color.hsb(Math.sqrt(speedX*speedX+speedY*speedY)*maxColor/maxSpeed,0.94,0.94,0.94);
return color;
}
},speed.getXProperty(),speed.getYProperty()
);
carShape.fillProperty().bind(colorObjectBinding1);
Timeline timeline = new Timeline(new KeyFrame(
Duration.millis(200),
ae -> {
speed.setX(Math.random()*maxSpeed);
}));
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
primaryStage.show();
}
}
and a minimal version of the Vector2D
class, which could as well be just replaced with a DoubleProperty
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
public class Vector2D {
private DoubleProperty xProperty = new SimpleDoubleProperty(0);
private DoubleProperty yProperty = new SimpleDoubleProperty(0);
public Vector2D(double x, double y) {
this.setX(x);
this.setY(y);
}
public Vector2D(final Vector2D v) {
this(v.getX(), v.getY());
}
public Vector2D(){this(0,0); }
public double getX() {
return xProperty.getValue();
}
public void setX(double x) { this.xProperty.setValue(x); }
public double getY() {
return yProperty.getValue();
}
public void setY(double y) {
this.yProperty.setValue(y);
}
public void setVector(Vector2D vector2D) {
setY(vector2D.getY());
setX(vector2D.getX());
}
public DoubleProperty getXProperty() {
return xProperty;
}
public DoubleProperty getYProperty() {
return yProperty;
}
}
Answered By - Tomek Szypuła