JavaFX - Generating shapes and moving them with arrows

Viewed 126

I'm working on a JavaFX code which generates 4 different shapes (circle,rectangle,line,ellipse) each time the user presses the button , so far I've done the circle and the rectangle , the shapes are getting generated very well , but the problem is that when I press the button I can't move the shapes , even though it moved them very well before I added the button , I can't figure out what's wrong with the code.

Here is the CODE :

package sample;

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Ellipse;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
import javafx.scene.control.Button;
import javafx.scene.shape.Rectangle;
import javafx.geometry.Insets;
import java.util.Random;


public class Main extends Application {
Pane pane;
Circle circle;
Rectangle rectangle;
    @Override
    public void start(Stage primaryStage) {
        pane = new Pane();
        Button button = new Button("Generate");
        CreateRect c = new CreateRect();
        button.setOnAction(c);
        pane.setPadding(new Insets(30, 30, 30, 30));
        circle = new Circle(30, 30, 30);
        rectangle = new Rectangle(30,30,50,20);
        pane.getChildren().add(button);
        pane.setOnKeyPressed(e -> {
            switch (e.getCode()) {
                case UP : circle.setCenterY(circle.getCenterY() >
                        circle.getRadius() ? circle.getCenterY() - 15 :
                        circle.getCenterY()); break;
                case DOWN : circle.setCenterY(circle.getCenterY() <
                        pane.getHeight() - circle.getRadius() ?
                        circle.getCenterY() + 15 : circle.getCenterY());
                    break;
                case LEFT : circle.setCenterX(circle.getCenterX() >
                        circle.getRadius() ? circle.getCenterX() - 15 :
                        circle.getCenterX()); break;
                case RIGHT : circle.setCenterX(circle.getCenterX() <
                        pane.getWidth() - circle.getRadius() ?
                        circle.getCenterX() + 15: circle.getCenterX());
            }
        });

        Scene scene = new Scene(pane, 500, 300);
        primaryStage.setTitle("SHAPES");
        primaryStage.setScene(scene);
        primaryStage.show();
        pane.requestFocus();
    }

private class CreateRect implements EventHandler<ActionEvent> {

    @Override
    public void handle(ActionEvent e) {
        double s;
        Random generator = new Random();
        s = generator.nextDouble();
        pane.getChildren().remove(rectangle);
        pane.getChildren().remove(circle);
        if (s < 0.5) {
            pane.getChildren().add(rectangle);
            rectangle.setX(100);
            rectangle.setY(100);
            rectangle.setWidth(50);
            rectangle.setHeight(20);
            rectangle.setFill(Color.RED);
        } else {
            pane.getChildren().add(circle);
            circle.setCenterX(100);
            circle.setCenterY(100);
            circle.setRadius(80);
            circle.setFill(Color.RED);
        }
    }
}

public static void main(String[] args) {
    launch(args);
}
}
3 Answers

The problem is that the button retains keyboard focus after you press it, and consumes the key events. One solution is to relinquish focus by passing it to, for example, the shape you create:

private class CreateRect implements EventHandler<ActionEvent> {

    @Override
    public void handle(ActionEvent e) {
        double s;
        Random generator = new Random();
        s = generator.nextDouble();
        pane.getChildren().remove(rectangle);
        pane.getChildren().remove(circle);
        if (s < 0.5) {
            pane.getChildren().add(rectangle);
            rectangle.setX(100);
            rectangle.setY(100);
            rectangle.setWidth(50);
            rectangle.setHeight(20);
            rectangle.setFill(Color.RED);
            rectangle.requestFocus();
        } else {
            pane.getChildren().add(circle);
            circle.setCenterX(100);
            circle.setCenterY(100);
            circle.setRadius(80);
            circle.setFill(Color.RED);
            circle.requestFocus();
        }
    }
}

If you specifically want the button to retain focus (so it responds to its usual key events, such as generating an action from SPACE, you can use a different key event. This seems a less robust solution though (relying on the internal event handling of the button):

    pane.setOnKeyReleased(e -> {
        // ...
    });

your code for

switch (e.getCode()) {
            case UP : circle.setCenterY(circle.getCenterY() >
                    circle.getRadius() ? circle.getCenterY() - 15 :
                    circle.getCenterY()); break;
            case DOWN : circle.setCenterY(circle.getCenterY() <
                    pane.getHeight() - circle.getRadius() ?
                    circle.getCenterY() + 15 : circle.getCenterY());
                break;
            case LEFT : circle.setCenterX(circle.getCenterX() >
                    circle.getRadius() ? circle.getCenterX() - 15 :
                    circle.getCenterX()); break;
            case RIGHT : circle.setCenterX(circle.getCenterX() <
                    pane.getWidth() - circle.getRadius() ?
                    circle.getCenterX() + 15: circle.getCenterX());
        }

is only for the circle so you have to take care in your code for other shapes

for example:

pane.setOnKeyPressed(e -> {
        switch (e.getCode()) {
        case UP : circle.setCenterY(circle.getCenterY() >
                circle.getRadius() ? circle.getCenterY() - 15 :
                circle.getCenterY());
            break;
        case DOWN : circle.setCenterY(circle.getCenterY() <
                pane.getHeight() - circle.getRadius() ?
                circle.getCenterY() + 15 : circle.getCenterY());
            break;
        case LEFT : circle.setCenterX(circle.getCenterX() >
                circle.getRadius() ? circle.getCenterX() - 15 :
                circle.getCenterX());
        break;
        case RIGHT : circle.setCenterX(circle.getCenterX() <
                pane.getWidth() - circle.getRadius() ?
                circle.getCenterX() + 15: circle.getCenterX());
            break;
    }
        switch (e.getCode()) {
            case UP : rectangle.setY(rectangle.getY()-15);
                break;
            case DOWN : rectangle.setY(rectangle.getY()+15);
                break;
            case LEFT : rectangle.setX(rectangle.getX()-15);
                break;
            case RIGHT : rectangle.setX(rectangle.getX()+15);
                break;
        }
});

(better do with more OOP method)

It's easier to conceptualize if you treat the event handler as just a trigger to invoke an action. Then you can break things down a little more easily. So I've converted your EventHandler class into a simple method and invoke it from a lambda which also resets the focus back to the pane:

public class Main extends Application {
    Pane pane;
    Circle circle;
    Rectangle rectangle;

    @Override
    public void start(Stage primaryStage) {
        pane = new Pane();
        Button button = new Button("Generate");
        button.setOnAction(evt -> {
            createShape();
            pane.requestFocus();
        });
        pane.setPadding(new Insets(30, 30, 30, 30));
        circle = new Circle(30, 30, 30);
        rectangle = new Rectangle(30, 30, 50, 20);
        pane.getChildren().addAll(button, circle);
        pane.setOnKeyPressed(e -> {
            switch (e.getCode()) {
                case UP:
                    circle.setCenterY(circle.getCenterY() >
                            circle.getRadius() ? circle.getCenterY() - 15 :
                            circle.getCenterY());
                    break;
                case DOWN:
                    circle.setCenterY(circle.getCenterY() <
                            pane.getHeight() - circle.getRadius() ?
                            circle.getCenterY() + 15 : circle.getCenterY());
                    break;
                case LEFT:
                    circle.setCenterX(circle.getCenterX() >
                            circle.getRadius() ? circle.getCenterX() - 15 :
                            circle.getCenterX());
                    break;
                case RIGHT:
                    circle.setCenterX(circle.getCenterX() <
                            pane.getWidth() - circle.getRadius() ?
                            circle.getCenterX() + 15 : circle.getCenterX());
            }
        });

        Scene scene = new Scene(pane, 500, 300);
        primaryStage.setTitle("SHAPES");
        primaryStage.setScene(scene);
        primaryStage.show();
        pane.requestFocus();
    }

    public void createShape() {
        double s;
        Random generator = new Random();
        s = generator.nextDouble();
        pane.getChildren().remove(rectangle);
        pane.getChildren().remove(circle);
        if (s < 0.5) {
            pane.getChildren().add(rectangle);
            rectangle.setX(100);
            rectangle.setY(100);
            rectangle.setWidth(50);
            rectangle.setHeight(20);
            rectangle.setFill(Color.RED);
        } else {
            pane.getChildren().add(circle);
            circle.setCenterX(100);
            circle.setCenterY(100);
            circle.setRadius(80);
            circle.setFill(Color.RED);
        }
    }


    public static void main(String[] args) {
        launch(args);
    }
}

That should work. Sarel's comments about having shape specific movements are totally valid, though. I'd move the shapes with setTranslateX() and setTranslateY().

Related