1

I'm creating UI editor and for that I need to draw UI components on mouse events. I'm stuck on drawing button with caption inside of it. As a result of my searches over stackoverflow I tried to use StackPane for creating Rectangle with caption. For layout I'm using Group element. The problem is, when I add StackPane to the Group it's being displayed on the top left corner of the Group. However, if I draw just Rectangle itself, it's being displayed on that place, where I'm releasing the mouse. How to achieve the same effect for StackPane?

Here is my code:

public class Main extends Application {
double startingPointX, startingPointY;
Group rectanglesGroup = new Group();
Rectangle newRectangle = null;
boolean newRectangleIsBeingDrawn = false;

// the following method adjusts coordinates so that the rectangle
// is shown "in a correct way" in relation to the mouse event
void adjustRectanglePRoperties(double startingPointX,
        double startingPointY, double endingPointX, double endingPointY,
        Rectangle givenRectangle) {
    givenRectangle.setX(startingPointX);
    givenRectangle.setY(startingPointY);
    givenRectangle.setWidth(endingPointX - startingPointX);
    givenRectangle.setHeight(endingPointY - startingPointY);

    if (givenRectangle.getWidth() < 0) {
        givenRectangle.setWidth(-givenRectangle.getWidth());
        givenRectangle.setX(givenRectangle.getX()
                - givenRectangle.getWidth());
    }

    if (givenRectangle.getHeight() < 0) {
        givenRectangle.setHeight(-givenRectangle.getHeight());
        givenRectangle.setY(givenRectangle.getY()
                - givenRectangle.getHeight());
    }
}

@Override
public void start(Stage primaryStage) {
    primaryStage.setTitle("Drawing rectangles");
    Scene scene = new Scene(rectanglesGroup, 800, 600);
    scene.setFill(Color.BEIGE);

    scene.setOnMousePressed(e -> {
        if (newRectangleIsBeingDrawn == false) {
            startingPointX = e.getSceneX();
            startingPointY = e.getSceneY();

            newRectangle = new Rectangle();

            // a non finished rectangle has always the same color
            newRectangle.setFill(Color.SNOW); // almost white color
            //Line line = new Line(20,120,270,120);
            newRectangle.setStroke(Color.BLACK);
            newRectangle.setStrokeWidth(1);
            newRectangle.getStrokeDashArray().addAll(3.0, 7.0, 3.0, 7.0);

            rectanglesGroup.getChildren().add(newRectangle);

            newRectangleIsBeingDrawn = true;
        }
    });

    scene.setOnMouseDragged(e -> {
        if (newRectangleIsBeingDrawn == true) {
            double currentEndingPointX = e.getSceneX();
            double currentEndingPointY = e.getSceneY();

            adjustRectanglePRoperties(startingPointX, startingPointY,
                    currentEndingPointX, currentEndingPointY, newRectangle);
        }
    });

    scene.setOnMouseReleased(e->{
        if(newRectangleIsBeingDrawn == true){
            //now the drawing of the new rectangle is finished
            //let's set the final color for the rectangle

            /******************Drawing textbox*******************************/
            //newRectangle.setFill(Color.WHITE);
            //newRectangle.getStrokeDashArray().removeAll(3.0, 7.0, 3.0, 7.0);
            /****************************************************************/


            /*****************Drawing button*********************************/
            Image image = new Image("file:button.png");
            ImagePattern buttonImagePattern = new ImagePattern(image);
            newRectangle.setFill(buttonImagePattern);
            newRectangle.setStroke(Color.WHITE);
            newRectangle.getStrokeDashArray().removeAll(3.0,7.0,3.0,7.0);
            Text text = new Text("Button");
            rectanglesGroup.getChildren().remove(newRectangle);
            StackPane stack = new StackPane();

            stack.getChildren().addAll(newRectangle, text);
            rectanglesGroup.getChildren().add(stack);

            /****************************************************************/
            colorIndex++; //index for the next color to use

            //if all colors have been used we'll start re-using colors
            //from the beginning of the array

            if(colorIndex>=rectangleColors.length){
                colorIndex=0;
            }

            newRectangle=null;
            newRectangleIsBeingDrawn=false; 

        }
    });
    primaryStage.setScene(scene);
    primaryStage.show();

}

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

}

I'm using OnMouseReleased event to create components.

I looked for the setX, setPosition or something like this methods, but couldn't find them in StackPane's methods. And I don't know how translate methods work. So I didn't try them to achieve my goal.

Umriyaev
  • 1,150
  • 11
  • 17
  • possible duplicate of [JavaFX: How to position a component/node?](http://stackoverflow.com/questions/29934183/javafx-how-to-position-a-component-node) – ItachiUchiha May 18 '15 at 05:21
  • Also have a look at [JavaFX issue when placing square at coordinates](http://stackoverflow.com/questions/29716079/javafx-issue-when-placing-square-at-coordinates) – ItachiUchiha May 18 '15 at 05:22

1 Answers1

3

You should read the documentation about a JavaFX Node.

You can position the nodes absolutely via setLayoutX (and Y) or relative via setTranslateX (and Y), which adds to the current layout position.

A StackPane is just a container and in your case no different to any other Node you want to place on your Scene. Just create it, set the dimensions and location and put it on the Scene.

Your code doesn't work, so I created my own. Here's example code about how to approach this matter:

import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.StrokeLineCap;
import javafx.stage.Stage;

public class RubberBandSelectionDemo extends Application {

    CheckBox drawButtonCheckBox;

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


    Pane root;

    @Override
    public void start(Stage primaryStage) {

        root = new Pane();
        root.setStyle("-fx-background-color:white");
        root.setPrefSize(1024, 768);

        drawButtonCheckBox = new CheckBox( "Draw Button");
        root.getChildren().add( drawButtonCheckBox);

        primaryStage.setScene(new Scene(root, root.getWidth(), root.getHeight()));
        primaryStage.show();

        new RubberBandSelection(root);

    }


    public class RubberBandSelection {

        final DragContext dragContext = new DragContext();
        Rectangle rect;

        Pane group;

        public RubberBandSelection( Pane group) {

            this.group = group;

            rect = new Rectangle( 0,0,0,0);
            rect.setStroke(Color.BLUE);
            rect.setStrokeWidth(1);
            rect.setStrokeLineCap(StrokeLineCap.ROUND);
            rect.setFill(Color.LIGHTBLUE.deriveColor(0, 1.2, 1, 0.6));

            group.addEventHandler(MouseEvent.MOUSE_PRESSED, onMousePressedEventHandler);
            group.addEventHandler(MouseEvent.MOUSE_DRAGGED, onMouseDraggedEventHandler);
            group.addEventHandler(MouseEvent.MOUSE_RELEASED, onMouseReleasedEventHandler);

        }

        EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() {

            @Override
            public void handle(MouseEvent event) {
                dragContext.mouseAnchorX = event.getSceneX();
                dragContext.mouseAnchorY = event.getSceneY();

                rect.setX(dragContext.mouseAnchorX);
                rect.setY(dragContext.mouseAnchorY);
                rect.setWidth(0);
                rect.setHeight(0);

                group.getChildren().add( rect);

            }
        };

        EventHandler<MouseEvent> onMouseReleasedEventHandler = new EventHandler<MouseEvent>() {

            @Override
            public void handle(MouseEvent event) {

                // get coordinates
                double x = rect.getX();
                double y = rect.getY();
                double w = rect.getWidth();
                double h = rect.getHeight();

                if( drawButtonCheckBox.isSelected()) {

                    // create button
                    Button node = new Button();
                    node.setDefaultButton(false);
                    node.setPrefSize(w, h);
                    node.setText("Button");
                    node.setLayoutX(x);
                    node.setLayoutY(y);
                    root.getChildren().add( node);


                } else {
                    // create rectangle
                    Rectangle node = new Rectangle( 0, 0, w, h);
                    node.setStroke( Color.BLACK);
                    node.setFill( Color.BLACK.deriveColor(0, 0, 0, 0.3));
                    node.setLayoutX( x);
                    node.setLayoutY( y);
                    root.getChildren().add( node);
                }


                // remove rubberband
                rect.setX(0);
                rect.setY(0);
                rect.setWidth(0);
                rect.setHeight(0);

                group.getChildren().remove( rect);


            }
        };

        EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {

            @Override
            public void handle(MouseEvent event) {

                double offsetX = event.getSceneX() - dragContext.mouseAnchorX;
                double offsetY = event.getSceneY() - dragContext.mouseAnchorY;

                if( offsetX > 0)
                    rect.setWidth( offsetX);
                else {
                    rect.setX(event.getSceneX());
                    rect.setWidth(dragContext.mouseAnchorX - rect.getX());
                }

                if( offsetY > 0) {
                    rect.setHeight( offsetY);
                } else {
                    rect.setY(event.getSceneY());
                    rect.setHeight(dragContext.mouseAnchorY - rect.getY());
                }
            }
        };

        private final class DragContext {

            public double mouseAnchorX;
            public double mouseAnchorY;


        }
    }
}

And here's an image:

enter image description here

The demo shows a rubberband selection which allows you to draw a selection rectangle. Upon release of the mouse button either a rectangle or a button is drawn, depending on the "Draw Button" checkbox selection in the top left corner. If you'd like to draw a StackPane, just change the code accordingly in the mouse released handler.

And of course, if you want to draw the components directly instead of the rubberband, just exchange the Rectangle in the rubberband selection code with e. g. a Button. Here's the Button drawing code only, just replace it in the above example.

public class RubberBandSelection {

    final DragContext dragContext = new DragContext();
    Button button;

    Pane group;

    public RubberBandSelection( Pane group) {

        this.group = group;

        button = new Button();
        button.setPrefSize(0, 0);

        group.addEventHandler(MouseEvent.MOUSE_PRESSED, onMousePressedEventHandler);
        group.addEventHandler(MouseEvent.MOUSE_DRAGGED, onMouseDraggedEventHandler);
        group.addEventHandler(MouseEvent.MOUSE_RELEASED, onMouseReleasedEventHandler);

    }

    EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() {

        @Override
        public void handle(MouseEvent event) {
            dragContext.mouseAnchorX = event.getSceneX();
            dragContext.mouseAnchorY = event.getSceneY();

            button.setLayoutX(dragContext.mouseAnchorX);
            button.setLayoutY(dragContext.mouseAnchorY);
            button.setPrefWidth(0);
            button.setPrefHeight(0);

            group.getChildren().add( button);

        }
    };

    EventHandler<MouseEvent> onMouseReleasedEventHandler = new EventHandler<MouseEvent>() {

        @Override
        public void handle(MouseEvent event) {

            // get coordinates
            double x = button.getLayoutX();
            double y = button.getLayoutY();
            double w = button.getWidth();
            double h = button.getHeight();

            // create button
            Button node = new Button();
            node.setDefaultButton(false);
            node.setPrefSize(w, h);
            node.setText("Button");
            node.setLayoutX(x);
            node.setLayoutY(y);
            root.getChildren().add( node);

            // remove rubberband
            button.setLayoutX(0);
            button.setLayoutY(0);
            button.setPrefWidth(0);
            button.setPrefHeight(0);

            group.getChildren().remove( button);


        }
    };

    EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {

        @Override
        public void handle(MouseEvent event) {

            double offsetX = event.getSceneX() - dragContext.mouseAnchorX;
            double offsetY = event.getSceneY() - dragContext.mouseAnchorY;

            if( offsetX > 0)
                button.setPrefWidth( offsetX);
            else {
                button.setLayoutX(event.getSceneX());
                button.setPrefWidth(dragContext.mouseAnchorX - button.getLayoutX());
            }

            if( offsetY > 0) {
                button.setPrefHeight( offsetY);
            } else {
                button.setLayoutY(event.getSceneY());
                button.setPrefHeight(dragContext.mouseAnchorY - button.getLayoutY());
            }
        }
    };

    private final class DragContext {

        public double mouseAnchorX;
        public double mouseAnchorY;


    }
}
Roland
  • 18,114
  • 12
  • 62
  • 93
  • Thank you very much for your answer. I also was thinking about using javafx controls in UI editor, but for me it would be more interesting to use shapes in order to achieve my goal. But your solution is better for speeding up the development process. – Umriyaev May 18 '15 at 08:32
  • And one more question, what is better way of implementing Panel and Table components? Panel is used to group components, Table is used to display images and react to events like cell_clicked, cell_dragged - for merging the cells, cell_dropped - for dropping the image sources into cells. In UI editor it should be possible to drag&drop the panels with controls inside. – Umriyaev May 18 '15 at 08:35
  • Roland thanks for the effort! it helped me understand the Scenegraph a bit more! – Londane Nov 11 '15 at 10:40