Case Silva
Case Silva

Reputation: 514

Trying to use JavaFX to create shapes with Mouse

I am trying to create a Java application that uses JavaFX to allow the user to create shapes by selecting radio button options and then clicking and dragging in a BorderPane area to create their shapes.

I am on the right track so far. The problem I'm having is with getting them positioned correctly. I'm hoping that someone can help me figure out why the shapes aren't being created in the place that I expect them to.

Currently, the first shape I create gets placed in the upper left hand corner of the HBox that I have in the Center section of the BorderPane, regardless of where I click to start creating. Dragging the mouse seems to accurately size the box in accordance with the cursor.

Subsequent attempts to create shapes results in shapes created off-location of the cursor, and dragging will resize, but also not in correlation with the cursor.

Here is my code. I've taken out parts that aren't relevant to the issue at hand to hopefully make it more readable:

public class Main extends Application{
    public static String shapeType = "";
    public static String color = "";

    static Rectangle customRectangle = null;

    public void start(Stage mainStage) throws Exception{
        Group root = new Group();
        Scene scene = new Scene(root, 600, 400);

        BorderPane borderPane = new BorderPane();

        .....

        HBox canvas = new HBox();
        Group canvasGroup = new Group();
        canvas.getChildren().add(canvasGroup);

        canvas.setStyle("-fx-background-color: yellow");

        borderPane.setTop(shapeOptions);
        borderPane.setLeft(colorOptions);
        borderPane.setCenter(canvas);

        canvas.addEventFilter(MouseEvent.ANY, new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent event) {
                if(event.getEventType() == MouseEvent.MOUSE_PRESSED && shapeType != ""){
                    switch(shapeType){
                        case "rectangle":
                            createCustomRectangle(event, color, canvasGroup);
                    }
                }
                if(event.getEventType() == MouseEvent.MOUSE_DRAGGED){
                    switch (shapeType){
                        case "rectangle":
                            editCustomRectangle(event);
                    }
                }
            }
        });

        root.getChildren().add(borderPane);
        mainStage.setScene(scene);
        mainStage.show();
    }

    public static void createCustomRectangle(MouseEvent event, String color, Group canvasGroup){
        customRectangle = new Rectangle(0, 0, 10,10);
        customRectangle.relocate(event.getX(), event.getY());

        customRectangle.setFill(Color.RED);
        canvasGroup.getChildren().add(customRectangle);
    }

    public static void editCustomRectangle(MouseEvent event){
        customRectangle.setWidth(event.getX() - customRectangle.getTranslateX());
        customRectangle.setHeight(event.getY() - customRectangle.getTranslateY());
    }

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

I also wanted to attach a couple of images to make my issue more clear. Here is attempting to create the first shape: Clicking and dragging to create first shape

And here is trying to create a subsequent shape: Clicking and dragging to create another shape

Hopefully the description, code, and images are enough to convey what's going on. Any help would be greatly appreciated.

Thanks!

Upvotes: 0

Views: 2641

Answers (3)

Case Silva
Case Silva

Reputation: 514

Thanks everyone for the suggestions! I found what I think is the most straight forward solution to my problem. One large part was that Group was not the kind of node that would allow my intended action. It appended every newly added Node/Object to the right of the previously created one. So, I switched to a StackPane and got better results. The problem there is that it creates everything on the center of the StackPane. My solution to that was to set the alignment for the shape as it's created. Largely, everything else remained the same.

Here is the pertinent code segments for anyone looking to perform a similar operation (my original post has more complete code):

StackPane stackCanvas = new StackPane();

stackCanvas.setStyle("-fx-background-color: yellow");

borderPane.setTop(shapeOptions);
borderPane.setLeft(colorOptions);
borderPane.setCenter(stackCanvas);

public static void createCustomRectangle(MouseEvent event, String color, StackPane stackCanvas){
    customRectangle = new Rectangle();
    StackPane.setAlignment(customRectangle, Pos.TOP_LEFT);
    stackCanvas.getChildren().add(customRectangle);

    customRectangle.setTranslateX(event.getX());
    customRectangle.setTranslateY(event.getY());

    customRectangle.setFill(Color.RED);
}

Upvotes: 0

JKostikiadis
JKostikiadis

Reputation: 2917

Let's start fixing each problem at a time. First of all a friendly advice, try to name your variables in a way that helps the reader understand their meaning and their identity ( when I first saw the canvas variable I thought it was an actual Canvas ).

Now your layout is something like this :

BorderPane 
    TOP 
        - Something
    CENTER 
        - HBox
            - Group
                - Rectangle
                - Rectangle
                - ...
    Left
        - Something
    Bottom 
        - Something
  1. The HBox takes all the available height and calculates it's width depending on its children. So in order to take all the available space inside the BorderPane you need to actually specify it or bind its preferredWidthProperty with the widthProperty of the BorderPane.

  2. From the documentation of the Group class you can see that :

Any transform, effect, or state applied to a Group will be applied to all children of that group. Such transforms and effects will NOT be included in this Group's layout bounds, however, if transforms and effects are set directly on children of this Group, those will be included in this Group's layout bounds.

So when you relocate the Node ( the actual rectangle ) the method relocate() just set the translateX and translateY values and that transformation is applied to the Group's layout bounds as well. To fix that you could change the Group to an AnchorPane.

  1. The way you resize the rectangle is not correct. You need to take the first mouse click coordinates when the first click event takes place and then on a drag event you will take the new coordinates, calculate the delta value of X and Y and just add that value to the width and height for the rectangle finally update the firstX and firstY variable on drag event listener :

    deltaX = event.getX() - firstX;
    deltaY = event.getY() - firstY;
    
    customRectangle.setWidth(customRectangle.getWidth + deltaX);
    customRectangle.setHeight(customRectangle.getHeight + deltaY);
    

Here is an example of the above :

import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class Main extends Application {
    public static String shapeType = "";
    public static String color = "";

    private static Rectangle customRectangle = null;

    private double firstX = 0;
    private double firstY = 0;

    public void start(Stage mainStage) throws Exception {

        BorderPane mainPane = new BorderPane();

        HBox centerPane = new HBox();

        centerPane.prefWidthProperty().bind(mainPane.widthProperty());
        centerPane.prefHeightProperty().bind(mainPane.heightProperty());

        AnchorPane anchorPane = new AnchorPane();

        anchorPane.prefWidthProperty().bind(centerPane.widthProperty());
        anchorPane.prefHeightProperty().bind(centerPane.heightProperty());

        centerPane.getChildren().add(anchorPane);

        centerPane.setStyle("-fx-background-color: yellow");

        shapeType = "rectangle";

        mainPane.setCenter(centerPane);

        centerPane.addEventFilter(MouseEvent.ANY, new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent event) {

                if (event.getEventType() == MouseEvent.MOUSE_PRESSED && shapeType != "") {
                    switch (shapeType) {
                    case "rectangle":
                        firstX = event.getX();
                        firstY = event.getY();
                        createCustomRectangle(event, color, anchorPane);
                    }
                }
                if (event.getEventType() == MouseEvent.MOUSE_DRAGGED) {
                    switch (shapeType) {
                    case "rectangle":
                        editCustomRectangle(event);
                        firstX = event.getX();
                        firstY = event.getY();
                    }
                }
            }
        });

        Scene scene = new Scene(mainPane, 600, 400);
        mainStage.setScene(scene);
        mainStage.show();
    }

    public void createCustomRectangle(MouseEvent event, String color, AnchorPane canvasGroup) {
        customRectangle = new Rectangle(0, 0, 10, 10); // or just set the actual X and Y from the start
        customRectangle.relocate(event.getX(), event.getY());

        customRectangle.setFill(Color.RED);
        canvasGroup.getChildren().add(customRectangle);
    }

    public void editCustomRectangle(MouseEvent event) {

        double deltaX = event.getX() - firstX;
        double deltaY = event.getY() - firstY;

        double width = customRectangle.getWidth() + deltaX;
        double height = customRectangle.getHeight() + deltaY;

        customRectangle.setWidth(width);
        customRectangle.setHeight(height);
    }

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

Upvotes: 2

MMAdams
MMAdams

Reputation: 1498

Well, first off, customRectangle.relocate(event.getX(), event.getY()); clearly isn't doing what it's supposed to do. It might be better to create the rectangle at the appropriate spot in the first place.

Instead of:

customRectangle = new Rectangle(0, 0, 10,10);
customRectangle.relocate(event.getX(), event.getY());

try:
customRectangle = new Rectangle(event.getX(), event.getY(), 10,10);

Secondly, it looks like customRectangle.getTranslateX() and customRectangle.getTranslateY() always return 0, so I'd take a look at those methods and see what's going on there as well. Good luck, hope this helped.

Edit: instead of relocate maybe try using setTranslateX() and setTranslateY().

Upvotes: 0

Related Questions