Search code examples
javajavafxarraylistevent-handlingmouseevent

Display ArrayList of shapes in pane (JavaFX)


I am writing a code that is a program that has a user create circles on the screen, based on where they click. What I have tried is to put the create new circle method in the first event handler but all it did was give me problems. As of now, I am trying to approach the problem differently. I am now using an ArrayList to group all the shapes together and display them on the pane. But the circles are not displaying when I run the code.

This is my code:

import javafx.application.Application; 
import javafx.event.EventHandler; 
import javafx.scene.Scene; 
import javafx.scene.input.MouseEvent; 
import javafx.scene.layout.Pane; 
import javafx.scene.paint.Color; 
import javafx.scene.shape.Circle; 
import javafx.scene.shape.Line; 
import javafx.stage.Stage;
    
import java.util.ArrayList;


public class Main extends Application {

    private Pane root;
    private Circle circle;
    private Line line;
    private boolean isClicked = false;
    private ArrayList<Circle> circleList;


    @Override
    public void start(Stage primaryStage) {
        root = new Pane();

        circle = new Circle();
        line = new Line();

         circleList = new ArrayList<Circle>();

        root.getChildren().addAll(line);

        //root.getChildren().addAll(circle); //when this is uncommented the program runs just fine but there is only one circle there at a time

        root.getChildren().addAll(circleList); //I feel like the problem could be here?


        root.setOnMousePressed(new mouseClick());
        root.setOnMouseMoved(new moveMouse());

        Scene scene = new Scene(root, 600, 600);

        primaryStage.setTitle("blank");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private double getRadius(double pointOnRadiusX, double pointOnRadiusY, double circleCenterX, double circleCenterY) {
        return Math.sqrt(Math.pow(Math.abs(pointOnRadiusX) - Math.abs(circleCenterX), 2) + Math.pow(Math.abs(pointOnRadiusY) - Math.abs(circleCenterY), 2));
    }

    private class mouseClick implements EventHandler<MouseEvent> {


        @Override
        public void handle(MouseEvent e) {



            if (!isClicked) {
                if(e.getEventType() == MouseEvent.MOUSE_PRESSED){


                    circle.setRadius(0);
                    circle.setCenterX(e.getSceneX());
                    circle.setCenterY(e.getSceneY());
                    circle.setStroke(Color.RED);
                    circle.setFill(Color.TRANSPARENT);

                    line.setStartX(e.getSceneX());
                    line.setStartY(e.getSceneY());
                    line.setStroke(Color.RED);
                    isClicked = true;

                    circleList.add(circle);

                }

            }

            else {

                circle.setRadius(getRadius(e.getSceneX(),e.getSceneY(),circle.getCenterX(), circle.getCenterY()));

                circle.setStroke(Color.GREEN);
                line.setStroke(Color.TRANSPARENT);

                isClicked = false;
            }

        }
    }

    private class moveMouse implements EventHandler <MouseEvent>{
        @Override
        public void handle(MouseEvent e) {
            {
                if (isClicked) {

                    circle.setRadius(getRadius(e.getSceneX(),e.getSceneY(),circle.getCenterX(), circle.getCenterY()));

                    line.setEndX(e.getSceneX());
                    line.setEndY(e.getSceneY());


                }
            }
        }
    }


    public static void main(String[] args) {

        Application.launch(args);
    } }

Solution

  • When this code is executed:

    root.getChildren().addAll(circleList);
    

    The circleList is empty. Given that you later add to the circleList I'm going to assume you're under the impression that the addAll method somehow "links" the two lists together. It does not. All that method does is copy all the elements from one list and append them to the other. And note by "copy" I don't mean each element is duplicated; the elements added to the one list are the same instances as in the given list. But the lists themselves remain separate.

    You also must make sure not to add the same Circle instance to the root more than once. A Node can only appear in the scene graph at most once. When the processes for adding a new circle is started you should be creating a new Circle object. The same goes for your Line if you plan to have multiple lines displayed.

    Here's a working example (without your Line):

    import javafx.application.Application;
    import javafx.scene.Scene;
    import javafx.scene.input.MouseButton;
    import javafx.scene.input.MouseEvent;
    import javafx.scene.layout.Pane;
    import javafx.scene.paint.Color;
    import javafx.scene.shape.Circle;
    import javafx.stage.Stage;
    
    public class App extends Application {
    
      private Pane root;
      private Circle circle;
    
      @Override
      public void start(Stage primaryStage) {
        root = new Pane();
        root.setOnMousePressed(this::handleMousePressed);
        root.setOnMouseMoved(this::handleMouseMoved);
        primaryStage.setScene(new Scene(root, 1000.0, 650.0));
        primaryStage.show();
      }
    
      private void handleMousePressed(MouseEvent e) {
        if (e.getButton() == MouseButton.PRIMARY && e.getClickCount() == 1) {
          if (circle == null) {
            // start drawing a new circle
            circle = new Circle(e.getX(), e.getY(), 0.0, Color.TRANSPARENT);
            circle.setStroke(Color.RED);
            circle.setStrokeWidth(2.0);
            root.getChildren().add(circle);
          } else {
            // "lock" the circle in place
            circle.setStroke(Color.GREEN);
            circle = null;
          }
        }
      }
    
      private void handleMouseMoved(MouseEvent e) {
        // if 'circle' is null then there's no circle being drawn
        if (circle != null) {
          double x1 = circle.getCenterX();
          double y1 = circle.getCenterY();
          double x2 = e.getX();
          double y2 = e.getY();
          double r = Math.sqrt(Math.pow(x2 - x1, 2.0) + Math.pow(y2 - y1, 2.0));
          circle.setRadius(r);
        }
      }
    }
    

    Note I used private methods and method references to implement the mouse handlers. It's more concise that way but is behaviorally the same as your inner classes.

    Also note that I don't use Math.abs when computing the radius. Using abs is actually wrong and can give you the wrong results (|x2| - |x1| != x2 - x1). For example, what if you had -3 - 2? That gives you |-3| - |2| = 1 which is not the same as -3 - 2 = -5.