Moving Two Rectangles with keyboard in Javafx - javafx

I'm fairly new to programming, and I've been working on a two player Pong game with JavaFX. One player uses W/S to move his "paddle" and the other player uses the Up/Down arrows for his. The problem I keep having is that only one player can move his paddle at a time. I can't figure out how to get it so they can each move their individual paddles at the same time. I had one keyboard event handler control both paddles, I thought that was the problem. I made two separate keyboard handlers, but got another set of problems tat I think is caused by the built in setFocusTraversable methods. I hope what I'm trying to do makes sense. Any ideas?
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Rectangle;
public class Game{
Rectangle leftPaddle;
double leftPaddleY = 260;
Rectangle rightPaddle;
double rightPaddleY = 260;
public void createGame(Group gameDisplay){
//creates background
Rectangle background = new Rectangle(0,0,800,600);
background.getStyleClass().add("background");
//draws field lines
Canvas game = new Canvas(800, 600);
GraphicsContext gc = game.getGraphicsContext2D();
gc.setStroke(Paint.valueOf("WHITE"));
gc.setLineWidth(5);
gc.strokeLine(400, 0, 400, 600);
gc.strokeOval(300, 200, 200, 200);
gc.strokeRect(0, 150, 100, 300);
gc.strokeRect(700, 150, 100, 300);
gc.setStroke(Paint.valueOf("BLACK"));
gc.setLineWidth(8);
gc.strokeRect(0, 0, 800, 600);
//creates red paddle
leftPaddle = new Rectangle(30, leftPaddleY, 20, 70);
leftPaddle.setOnKeyPressed(paddleMovement);
leftPaddle.setFocusTraversable(true);
leftPaddle.setFill(Color.RED);
//creates blue paddle
rightPaddle = new Rectangle(750, rightPaddleY, 20, 70);
rightPaddle.setOnKeyPressed(paddleMovement);
rightPaddle.setFocusTraversable(true);
rightPaddle.setFill(Color.BLUE);
gameDisplay.getStylesheets().add(getClass().getResource("GameDisplay.css").toExternalForm());
gameDisplay.getChildren().addAll(background, game, leftPaddle, rightPaddle);
}
public EventHandler<KeyEvent> paddleMovement = new EventHandler<KeyEvent>(){
#Override
public void handle(KeyEvent event) {
//red paddle movement
if(event.getCode().equals(KeyCode.W)){
leftPaddle.setY(leftPaddleY -= 6);
if(leftPaddle.getY() < 0){
leftPaddle.setY(0);
leftPaddleY = 0;
}
}
if(event.getCode().equals(KeyCode.S)){
leftPaddle.setY(leftPaddleY += 6);
if(leftPaddle.getY() < 0){
leftPaddle.setY(0);
leftPaddleY = 0;
}
}
//blue paddle movement
if(event.getCode().equals(KeyCode.UP)){
rightPaddle.setY(rightPaddleY -= 6);
if(rightPaddle.getY() < 0){
rightPaddle.setY(0);
rightPaddleY = 0;
}
}
if(event.getCode().equals(KeyCode.DOWN)){
rightPaddle.setY(rightPaddleY += 6);
if(rightPaddle.getY() < 0){
rightPaddle.setY(0);
rightPaddleY = 0;
}
}
}
};
}

The keyPressed event will only be triggered repeatedly for the last key pressed.
To work around this, just "remember" the desired movement for each paddle and listen to the keyReleased event too. You can execute the movements using a AnimationTimer to execute the updates every frame (you could also use a Timeline instead to have more control over the frequency of the updates).
Furthermore I recommend being a bit more restrictive with the visibility of your member variables, since usually you do not want other classes to be able to directly write to the fields of your class. Also I recommend handling the events in a single Node. Only one Node can have focus at a time and handling the event in different places just results in duplicate code.
public class Game {
private Rectangle leftPaddle;
private double leftPaddleY = 260;
private Rectangle rightPaddle;
private double rightPaddleY = 260;
private double leftPaddleDY;
private double rightPaddleDY;
private AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long now) {
// update paddle positions
leftPaddleY += leftPaddleDY;
rightPaddleY += rightPaddleDY;
if (leftPaddleY < 0) {
leftPaddleY = 0;
}
if (rightPaddleY < 0) {
rightPaddleY = 0;
}
leftPaddle.setY(leftPaddleY);
rightPaddle.setY(rightPaddleY);
}
};
public void createGame(Group gameDisplay) {
//creates background
Rectangle background = new Rectangle(0, 0, 800, 600);
background.getStyleClass().add("background");
//draws field lines
Canvas game = new Canvas(800, 600);
GraphicsContext gc = game.getGraphicsContext2D();
gc.setStroke(Paint.valueOf("WHITE"));
gc.setLineWidth(5);
gc.strokeLine(400, 0, 400, 600);
gc.strokeOval(300, 200, 200, 200);
gc.strokeRect(0, 150, 100, 300);
gc.strokeRect(700, 150, 100, 300);
gc.setStroke(Paint.valueOf("BLACK"));
gc.setLineWidth(8);
gc.strokeRect(0, 0, 800, 600);
//creates red paddle
leftPaddle = new Rectangle(30, leftPaddleY, 20, 70);
leftPaddle.setFill(Color.RED);
//creates blue paddle
rightPaddle = new Rectangle(750, rightPaddleY, 20, 70);
rightPaddle.setFill(Color.BLUE);
// register event handlers to Canvas
game.setFocusTraversable(true);
game.setOnKeyPressed(keyPressed);
game.setOnKeyReleased(keyReleased);
gameDisplay.getStylesheets().add(getClass().getResource("GameDisplay.css").toExternalForm());
gameDisplay.getChildren().addAll(background, game, leftPaddle, rightPaddle);
// start updates of paddle positions
timer.start();
}
private EventHandler<KeyEvent> keyReleased = new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
// set movement to 0, if the released key was responsible for the paddle
switch (event.getCode()) {
case W:
case S:
leftPaddleDY = 0;
break;
case UP:
case DOWN:
rightPaddleDY = 0;
break;
}
}
};
private EventHandler<KeyEvent> keyPressed = new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
// start movement according to key pressed
switch (event.getCode()) {
case W:
leftPaddleDY = -6;
break;
case S:
leftPaddleDY = 6;
break;
case UP:
rightPaddleDY = -6;
break;
case DOWN:
rightPaddleDY = 6;
break;
}
}
};
}

Related

JavaFX - Check the position of a label based on a mouse click

I try to write a code that find the label on which one have clicked.
Using an event listener, I got the positions of the event using getX() and getY().
However, I cannot find the adequate methods for the label positions in order to compare them.
Below is my code, and its ouput.
public class Beta extends Application {
final Label[] answerLabel = new Label[4];
#Override
public void start(Stage primaryStage) {
GridPane root = new GridPane();
root.setGridLinesVisible(true);
final int numCols = 7 ;
final int numRows = 12 ;
//final Label[] answerLabel = new Label[4];
for (int i = 0; i < numCols; i++) {
ColumnConstraints colConst = new ColumnConstraints();
colConst.setPercentWidth(100.0 / numCols);
root.getColumnConstraints().add(colConst);
}
for (int i = 0; i < numRows; i++) {
RowConstraints rowConst = new RowConstraints();
rowConst.setPercentHeight(100.0 / numRows);
root.getRowConstraints().add(rowConst);
}
for(int i = 0; i<4; i++){
answerLabel[i] = new Label();
answerLabel[i].setMaxWidth(Double.MAX_VALUE);
answerLabel[i].setMaxHeight(Double.MAX_VALUE);
answerLabel[i].setStyle("-fx-background-color: blue;-fx-font-size: 7pt;-fx-padding: 0;");
answerLabel[i].setPadding(new Insets(10));
answerLabel[i].setCursor(Cursor.HAND);
root.add(answerLabel[i], 3, i +5, 1, 1);
answerLabel[i].setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
answerLabelPressed(e);
}
});
}
primaryStage.setScene(new Scene(root, 900, 500));
primaryStage.show();
}
private void answerLabelPressed(MouseEvent e)
{
int labelSelected;
double px = e.getX();
double py = e.getY();
System.out.println("px = " + px + " py = " + py);
for (labelSelected = 0; labelSelected < 4; labelSelected++)
{
System.out.println("answerLabel[labelSelected].getLayoutX() = " + answerLabel[labelSelected].getLayoutX());
System.out.println("answerLabel[labelSelected].getLayoutY() = " + answerLabel[labelSelected].getLayoutY());
}
}
public static void main(String[] args) {
launch(args);
}
}
px = 42.0 py = 7.0
answerLabel[labelSelected].getLayoutX() = 386.0
view.answerLabel[labelSelected].getLayoutY() = 208.0
answerLabel[labelSelected].getLayoutX() = 386.0
view.answerLabel[labelSelected].getLayoutY() = 250.0
answerLabel[labelSelected].getLayoutX() = 386.0
view.answerLabel[labelSelected].getLayoutY() = 292.0
answerLabel[labelSelected].getLayoutX() = 386.0
view.answerLabel[labelSelected].getLayoutY() = 333.0
Upadate: The main purpose was to find/check the equivalent JavaFX methods of those used in Java Swing.
An alternative and better algorithm beeing as one can read in most popular Java books :
MouseListener ml = new MouseListener() {
public void mouseClicked(MouseEvent e) {
report("mouseClicked", e.paramString());
}
In Java Swing, one should read :
Point p = e.getComponent().getLocation();
System.out.println("px = " + p.getX() + " py = " + p.getY());
for (labelSelected = 0; labelSelected < 4; labelSelected++)
{
System.out.println("answerLabel[labelSelected].getX() = " + answerLabel[labelSelected].getX());
System.out.println("answerLabel[labelSelected].getY() = " + answerLabel[labelSelected].getY());
}
I try to write a code that find the label on which one have clicked.
You create four labels, and you create a listener for each label. Each listener is only registered with one label.
So there is no need to get your hands dirty with the coordinates of the click (the event handling mechanism has already done all of that for you, when it decided to which node to dispatch the event). Just reference the label that was clicked:
public class Beta extends Application {
final Label[] answerLabel = new Label[4];
#Override
public void start(Stage primaryStage) {
GridPane root = new GridPane();
root.setGridLinesVisible(true);
final int numCols = 7 ;
final int numRows = 12 ;
for (int i = 0; i < numCols; i++) {
ColumnConstraints colConst = new ColumnConstraints();
colConst.setPercentWidth(100.0 / numCols);
root.getColumnConstraints().add(colConst);
}
for (int i = 0; i < numRows; i++) {
RowConstraints rowConst = new RowConstraints();
rowConst.setPercentHeight(100.0 / numRows);
root.getRowConstraints().add(rowConst);
}
for(int i = 0; i<4; i++){
answerLabel[i] = new Label();
answerLabel[i].setMaxWidth(Double.MAX_VALUE);
answerLabel[i].setMaxHeight(Double.MAX_VALUE);
answerLabel[i].setStyle("-fx-background-color: blue;-fx-font-size: 7pt;-fx-padding: 0;");
answerLabel[i].setPadding(new Insets(10));
answerLabel[i].setCursor(Cursor.HAND);
root.add(answerLabel[i], 3, i +5, 1, 1);
Label currentLabel = answerLabel[i];
int currentIndex = i ;
answerLabel[i].setOnMouseClicked(event -> {
System.out.println("Clicked on label "+currentIndex);
// just for demo: in real life use external stylesheets
// and pseudoclasses, etc.
for (Label label : answerLabel) {
label.setStyle("-fx-background-color: blue;-fx-font-size: 7pt;-fx-padding: 0;");
}
currentLabel.setStyle("-fx-background-color: gold;-fx-font-size: 7pt;-fx-padding: 0;");
});
}
primaryStage.setScene(new Scene(root, 900, 500));
primaryStage.show();
}
}
It is not necessary to manually compute which node the mouse clicked on. That calculation is already done for you by the framework. That's how the framework knows which event handlers to invoke. If you simply add a unique handler to each node, then when that handler is invoked only that node could be the source. This is demonstrated in #James_D's answer.
However, if you want to manually compute which node was clicked (e.g., for fun or just for learning purposes), then here is a runnable example:
import java.util.stream.IntStream;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Cursor;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Border;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
var labels = IntStream.range(0, 12)
.mapToObj(this::createLabel)
.toArray(Label[]::new);
var grid = new GridPane();
grid.setPadding(new Insets(20));
grid.setHgap(20);
grid.setVgap(20);
grid.setOnMouseClicked(e -> {
var clickedOnLabel = getClickedOnLabel(labels, e);
if (clickedOnLabel == null) {
System.out.println("You did not click on a label!");
} else {
System.out.printf("You clicked on a label: '%s'%n", clickedOnLabel.getText());
}
});
int i = 0;
for (int col = 0; col < 4; col++) {
for (int row = 0; row < 3; row++) {
grid.add(labels[i++], col, row);
}
}
primaryStage.setScene(new Scene(grid));
primaryStage.show();
}
private Label getClickedOnLabel(Label[] labels, MouseEvent event) {
for (var label : labels) {
var bounds = label.localToScene(label.getBoundsInLocal());
if (bounds.contains(event.getSceneX(), event.getSceneY())) {
return label;
}
}
return null;
}
private Label createLabel(int n) {
var label = new Label(String.format("Label #%02d", n));
label.setCursor(Cursor.HAND);
label.setPadding(new Insets(5));
label.setFont(Font.font("Monospaced", 15.0));
label.setBorder(Border.stroke(Color.BLACK));
return label;
}
}
The important part, the part which computes which label was clicked on, is here:
private Label getClickedOnLabel(Label[] labels, MouseEvent event) {
for (var label : labels) {
var bounds = label.localToScene(label.getBoundsInLocal());
if (bounds.contains(event.getSceneX(), event.getSceneY())) {
return label;
}
}
return null;
}
It gets the bounds of each Label in the scene's coordinate space, and then tests if the mouse's location—also in the scene's coordinate space—is contained within those bounds. You can use whatever coordinate space you like (e.g., the screen's, the grid pane's, the label's, etc.), as long as you use the same one for both the label's bounds and the mouse's location. Note the mouse's local coordinates (i.e., getX() and getY()) are in the source node's coordinate space. The source node is the node that the currently-being-invoked handler was registered with for the specific event currently being processed (the GridPane in the above example).
But again, for any "real" code, I strongly recommend you use the solution in #James_D's answer.

How can I make a ball move up or down in JavaFX?

I have javafx gui exercise to do and I have to make the ball move either up or down or right and left. Right now the ball goes randomly anywhere I suppose. Can you guys help me with the code? So that when I press the plus button, a ball will be added and goes up and down or right and left. And any other ball added has to shift either up or down? Any help would be awesome.Thanks.
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollBar;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.util.Duration;
public class MultipleBounceBall extends Application {
#Override // Override the start method in the Application class
public void start(Stage primaryStage) {
MultipleBallPane ballPane = new MultipleBallPane();
ballPane.setStyle("-fx-border-color: black");
Button btAdd = new Button("+");
Button btSubtract = new Button("-");
HBox hBox = new HBox(10);
hBox.getChildren().addAll(btAdd, btSubtract);
hBox.setAlignment(Pos.CENTER);
// Add or remove a ball
btAdd.setOnAction(e -> ballPane.add());
btSubtract.setOnAction(e -> ballPane.subtract());
// Pause and resume animation
ballPane.setOnMousePressed(e -> ballPane.pause());
ballPane.setOnMouseReleased(e -> ballPane.play());
// Use a scroll bar to control animation speed
ScrollBar sbSpeed = new ScrollBar();
sbSpeed.setMax(20);
sbSpeed.setValue(10);
ballPane.rateProperty().bind(sbSpeed.valueProperty());
BorderPane pane = new BorderPane();
pane.setCenter(ballPane);
pane.setTop(sbSpeed);
pane.setBottom(hBox);
// Create a scene and place the pane in the stage
Scene scene = new Scene(pane, 350, 450);
primaryStage.setTitle("Multiple Bounce Ball"); // Set the stage title
primaryStage.setScene(scene); // Place the scene in the stage
primaryStage.show(); // Display the stage
}
private class MultipleBallPane extends Pane {
private Timeline animation;
public MultipleBallPane() {
// Create an animation for moving the ball
animation = new Timeline(
new KeyFrame(Duration.millis(50), e -> moveBall()));
animation.setCycleCount(Timeline.INDEFINITE);
animation.play(); // Start animation
}
public void add() {
Color color = new Color(Math.random(),
Math.random(), Math.random(), 0.5);
getChildren().add(new Ball(30, 30, 20, color));
}
public void subtract() {
if (getChildren().size() > 0) {
getChildren().remove(getChildren().size() - 1);
}
}
public void play() {
animation.play();
}
public void pause() {
animation.pause();
}
public void increaseSpeed() {
animation.setRate(animation.getRate() + 0.1);
}
public void decreaseSpeed() {
animation.setRate(
animation.getRate() > 0 ? animation.getRate() - 0.1 : 0);
}
public DoubleProperty rateProperty() {
return animation.rateProperty();
}
protected void moveBall() {
for (Node node: this.getChildren()) {
Ball ball = (Ball)node;
// Check boundaries
if (ball.getCenterX() < ball.getRadius() ||
ball.getCenterX() > getWidth() - ball.getRadius()) {
ball.dx *= -1; // Change ball move direction
}
if (ball.getCenterY() < ball.getRadius() ||
ball.getCenterY() > getHeight() - ball.getRadius()) {
ball.dy *= -1; // Change ball move direction
}
// Adjust ball position
ball.setCenterX(ball.dx + ball.getCenterX());
ball.setCenterY(ball.dy + ball.getCenterY());
}
}
}
class Ball extends Circle {
private double dx = 1, dy = 1;
Ball(double x, double y, double radius, Color color) {
super(x, y, radius);
setFill(color); // Set ball color
}
}
/**
* The main method is only needed for the IDE with limited
* JavaFX support. Not needed for running from the command line.
*/
public static void main(String[] args) {
launch(args);
}
}
The problem is that the displacement elements of the ball movement (dx and dy) are both assigned to 1. Thus the ball moves to both x and y directions i.e. diagonally.
Change the declaration of these variables to:
private double dx = 1, dy = 0; // The ball will move right
private double dx = -1, dy = 0; // The ball will move left
private double dx = 0, dy = 1; // The ball will move down
private double dx = 0, dy = -1; // The ball will move up
If you would like the balls to move in random directions, you can do it for example by changing your Ball class:
class Ball extends Circle {
private double dx;
private double dy;
Ball(double x, double y, double radius, Color color) {
super(x, y, radius);
setFill(color); // Set ball color
switch (ThreadLocalRandom.current().nextInt(0, 4)) {
case 0:
dx = 0;
dy = 1;
break;
case 1:
dx = 0;
dy = -1;
break;
case 2:
dx = 1;
dy = 0;
break;
case 3:
dx = -1;
dy = 0;
break;
}
}
}
And if you would like them to start from random locations, you can do it by changing your MultipleBallPane.add() method:
public void add() {
Color color = new Color(Math.random(),
Math.random(), Math.random(), 0.5);
int radius = 20;
int x = ThreadLocalRandom.current().nextInt(radius, (int) getWidth() - radius);
int y = ThreadLocalRandom.current().nextInt(radius, (int) getHeight() - radius);
getChildren().add(new Ball(x, y, radius, color));
}

How to play pathtransitions in sequence?

Is there a special way (some kind of event/transition queue?) in JavaFX with which you can play animations/transitions in sequence?
In a solitaire game at the end the cards are usually already in sequence (K->-Q->J-10->-9->...) and I want to finish them automatically. So first card 9 has to move from the tableau to the foundation, then 10, then J, etc.
I'd like to do the path transition in a transition queue, rather than all at once.
I can't use a SequentialTranstition because you can't modify its children once the animation plays.
public final ObservableList getChildren()
A list of Animations that will be played sequentially.
It is not possible to change the children of a running
SequentialTransition. If the children are changed for a running
SequentialTransition, the animation has to be stopped and started
again to pick up the new value.
Verifiable with this minimal example:
public class PathTransitionDemo extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Group root = new Group();
List<Rectangle> rectangles = new ArrayList<>();
double w = 50;
double h = 50;
double spacing = 10;
for( int i=0; i < 10; i++) {
Rectangle rectangle = new Rectangle ( spacing * (i+1) + w * i, 50, w, h);
rectangle.setStroke( Color.BLUE);
rectangle.setFill( Color.BLUE.deriveColor(1, 1, 1, 0.2));
rectangles.add( rectangle);
}
root.getChildren().addAll( rectangles);
primaryStage.setScene(new Scene(root, 1024, 768));
primaryStage.show();
// transition
SequentialTransition seq = new SequentialTransition();
for( Rectangle rectangle: rectangles) {
Path path = new Path();
path.getElements().add (new MoveTo ( rectangle.getX(), rectangle.getY()));
path.getElements().add (new LineTo( 900, 600));
PathTransition pathTransition = new PathTransition();
pathTransition.setDuration(Duration.millis(1000));
pathTransition.setNode(rectangle);
pathTransition.setPath(path);
//pathTransition.play();
seq.getChildren().add( pathTransition);
if( seq.getStatus() != Status.RUNNING)
seq.play();
}
// seq.play();
}
}
The sequential transition stops after it played the first path transition.
A solution would be to modfiy the setOnFinished handler of the PathTransitions. But that's ugly. And it plays only exaclty 1 transtition after the other. Let's say one card transition lasts 2000 ms, it e. g. limits the possibility to start another transition of the queue within 500ms.
The animation in the Video Wall that I created has a similar requirement. There I solved it with an AnimationTimer. Excerpt from the code:
AnimationTimer timer = new AnimationTimer() {
long last = 0;
#Override
public void handle(long now) {
if( (now - last) > 40_000_000)
{
if( transitionList.size() > 0) {
ParallelTransition t = transitionList.remove(0);
t.getNode().setVisible(true);
t.play();
}
last = now;
}
if( transitionList.size() == 0) {
stop();
}
}
};
Is there a better way to solve this in JavaFX?
Thank you very much!

JavaFX 2 Timeline setcyclecount()

I'm using javafx in javase8 and netbeans 8.0.2, I made randomly generated shape images and show them sequentially with timeline. But last image isn't shown. timeline.setcyclecount(12) i use java generate 12 images but doesn't show 12. image in timeline.
public class JavaFXApplication3 extends Application {
int k;
Timeline timeline;
class ResizableCanvas extends Canvas {
private void draw() {
int[] uyaran = {3, 7, 12};
boolean[] type = new boolean[12];
for (int i = 0; i < 12; i++) {
type[i] = false;
}
for (int v : uyaran) {
type[v - 1] = true;
}
double w = getWidth();
double h = getHeight();
GraphicsContext gc = getGraphicsContext2D();
gc.clearRect(0, 0, w, h);
gc.setFill(Color.RED);
System.out.println(k);
if (type[k]) {
gc.fillOval(0, 0, w, h);
}
k++;
}
}
#Override
public void start(Stage stage) throws Exception {
k = 0;
ResizableCanvas canvas = new ResizableCanvas();
timeline = new Timeline(new KeyFrame(Duration.millis(1000), ae -> canvas.draw()));
timeline.setCycleCount(12);
timeline.setOnFinished(ActionEvent -> {
try {
Thread.sleep(10000);
} catch (InterruptedException ex) {}
stage.close();
});
timeline.play();
Pane pane = new Pane();
pane.getChildren().add(canvas);
canvas.widthProperty().bind(pane.widthProperty());
canvas.heightProperty().bind(pane.heightProperty());
stage.setScene(new Scene(pane));
stage.show();
}
}
You are calling Thread.sleep(...) on the FX Application Thread, which blocks the thread and prevents it from updating. The final ellipse is only actually rendered when the pause is over, but of course you then close the window so you never see it.
Use a PauseTransition to pause, and use its onFinished handler to do something when the pause is over:
timeline.setOnFinished(ae -> {
PauseTransition pause = new PauseTransition(Duration.seconds(10));
pause.setOnFinished(event -> stage.close());
pause.play();
});

Javafx canvas draw only moving object without redraw the background

I am drawing inside JavaFx Canvas the program draw many different shapes and one of them represent a position marker(Oval) in some situation its position must be updated every 1 second and this is OK to remove several traces of marker I have to redraw again and this slow the program the question how can I redraw only the current marker removing its traces without drawing all shapes?? much like swing repaint() .
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
public class DrawChart {
private Timeline timelinePosition;
private Canvas canvas;
private GraphicsContex graphicsContex;
public void start() {
canvas = new Canvas();
graphicsContex = canvas.getGraphicsContext2D();
drawManyShapes();
drawPositionMarker();
someStateMonitor();
}
public void drawManyShapes() {
draw many shapes .......
}
public void drawPositionMarker() {
EventHandler eventHandler = new EventHandler<ActionEvent>() {
public void handle(ActionEvent t) {
graphicsContex.strokeOval(posi_x , posi_y, width , hight );
}
};
Duration duration = Duration.millis(1000);
timelinePosition = new Timeline();
timelinePosition.setDelay(duration);
timelinePosition.setCycleCount(Timeline.INDEFINITE);
KeyFrame keyPosition = new KeyFrame(duration, drawPosition , null, null);
timelinePosition.getKeyFrames().add(keyPosition);
}
public void someStateMonitor() {
if(state == true) timelinePosition.play();
if(state == false) timelinePosition.stop();
}
}
You can add layers of canvases. Or you can save a rectangle under your shape and redraw that after.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class LayeredCanvas extends Application {
#Override
public void start(Stage primaryStage) {
Canvas layer1 = new Canvas(700, 300);
Canvas layer2 = new Canvas(700, 300);
GraphicsContext gc1 = layer1.getGraphicsContext2D();
GraphicsContext gc2 = layer2.getGraphicsContext2D();
gc1.setFill(Color.GREEN);
gc1.setFont(new Font("Comic sans MS", 100));
gc1.fillText("BACKGROUND", 0, 100);
gc1.fillText("LAYER", 0, 200);
gc1.setFont(new Font(30));
gc1.setFill(Color.RED);
gc1.fillText("Hold a key", 0, 270);
gc2.setFill(Color.BLUE);
Pane root = new Pane(layer1, layer2);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
scene.setOnKeyPressed((evt) -> {
gc2.clearRect(0, 0, layer2.getWidth(), layer2.getHeight());
gc2.fillOval(Math.random() * layer2.getWidth(), Math.random() * layer2.getHeight(), 20, 30);
});
}
public static void main(String[] args) {
launch(args);
}
}
I have encountered this issue as well (for me rectangle) and I made a rectangle class, you could to the same but with ovals. The example is just to give you a idea
class Rectangle {
//x position
int x;
//y position
int y;
//size for width
int xSize;
//size for height
int ySize;
//to draw on the canvas
GraphicsContext graphics;
//used to create a rectangle object
public Rectangle(GraphicsContext graphics, int x, int y, int xSize, int ySize) {
//sets fields used for the rectangle
this.x = x;
this.y = y;
this.xSize = xSize;
this.ySize = ySize;
this.graphics = graphics;
graphics.fillRect(x, y, xSize, ySize);
}
//used to get rid of rectangles
public void delete(Rectangle rectangle) {
//erase the rectangle
(rectangle.graphics).clearRect(rectangle.x, rectangle.y, rectangle.xSize, rectangle.ySize);
//erase its fields
rectangle.x = -1;
rectangle.y = -1;
rectangle.xSize = -1;
rectangle.ySize = -1;
}
//will redraw the rectangle
public void reDraw(Rectangle rectangle) {
(rectangle.graphics).fillRect(rectangle.x, rectangle.y, rectangle.xSize, rectangle.ySize);
}
//will move the rectangle
public void move(Rectangle rectangle, int x, int y) {
(rectangle.graphics).clearRect(rectangle.x, rectangle.y, rectangle.xSize+1, rectangle.ySize+1);
(rectangle.graphics).fillRect(x, y, rectangle.xSize, rectangle.ySize);
}
//redifine the fields
this.x = x;
this.y = y; } }
You could add other fields as well.

Resources