1

I am try to allow users to drag/resize a rectangle in javafx. Rectangle in javafx uses TopLeft-X, Y, height and width to draw.

What I am trying to do is to have 4 handles at each corner of the rectangle to allow users to drag the handle. Which will result in resizing the rectangle.

My application throws a stackoverflow exception which I know is caused by the recursive call of the listener.

Did i forget something? Or is there some listener that is unnecessary?

public class Handler extends Circle
{
    private double deltaX;
    private double deltaY;

    private static final double DEFAULT_RADIUS = 10;
    private static final Color DEFAULT_COLOR = Color.GOLD;

    private DoubleProperty xProperty;
    private DoubleProperty yProperty;

    public Handler(DoubleProperty xProperty, DoubleProperty yProperty)
    {
        super(DEFAULT_RADIUS, DEFAULT_COLOR);

        this.xProperty = xProperty;
        this.yProperty = yProperty;

        init();
    }   

    private void init()
    {
        setFill(DEFAULT_COLOR.deriveColor(1, 1, 1, 0.5));
        setStroke(DEFAULT_COLOR);
        setStrokeWidth(2);
        setStrokeType(StrokeType.OUTSIDE);

        centerXProperty().bind(xProperty);
        centerYProperty().bind(yProperty);

        setOnMousePressed(new EventHandler<MouseEvent>()
        {
            @Override
            public void handle(MouseEvent mouseEvent)
            {
                mouseEvent.consume();

                deltaX = getCenterX() - mouseEvent.getX();
                deltaY = getCenterY() - mouseEvent.getY();

                getScene().setCursor(javafx.scene.Cursor.MOVE);
            }
        });

        setOnMouseReleased(new EventHandler<MouseEvent>()
        {
            @Override
            public void handle(MouseEvent mouseEvent)
            {
                getScene().setCursor(javafx.scene.Cursor.HAND);
            }
        });

        setOnMouseEntered(new EventHandler<MouseEvent>()
        {
            @Override
            public void handle(MouseEvent mouseEvent)
            {
                if (!mouseEvent.isPrimaryButtonDown())
                {
                    getScene().setCursor(javafx.scene.Cursor.HAND);
                }
            }
        });

        setOnMouseExited(new EventHandler<MouseEvent>()
        {
            @Override
            public void handle(MouseEvent mouseEvent)
            {
                if (!mouseEvent.isPrimaryButtonDown())
                {
                    getScene().setCursor(javafx.scene.Cursor.DEFAULT);
                }
            }
        });

        setOnMouseDragged(new EventHandler<MouseEvent>()
        {
            @Override
            public void handle(MouseEvent mouseEvent)
            {
                double newX = mouseEvent.getX() + deltaX;
                double newY = mouseEvent.getY() + deltaY;

                if (newX > 0 && newX < getScene().getWidth())
                {                   
                    xProperty.set(newX);
                }

                if (newY > 0 && newY < getScene().getHeight())
                {                   
                    yProperty.set(newY);
                }
            }
        });
    }

    //JavaFx Accessor and mutator
}


public class CustomRectangle extends Rectangle
{   
    private DoubleProperty topRightX;
    private DoubleProperty topRightY;

    private DoubleProperty btmLeftX;
    private DoubleProperty btmLeftY;

    private DoubleProperty btmRightX;
    private DoubleProperty btmRightY;

    private DoubleProperty customeWidth;
    private DoubleProperty customHeight;

    public CustomRectangle()
    {
        super();
        init();
    }

    public CustomRectangle(double x, double y, double width, double height)
    {
        super(x, y, width, height);
        init();
    }

    public CustomRectangle(double width, double height, Paint fill)
    {
        super(width, height, fill);
        init();
    }

    public CustomRectangle(double width, double height)
    {
        super(width, height);
        init();
    }

    private void init()
    {       
        topRightX = new SimpleDoubleProperty();
        topRightY = new SimpleDoubleProperty();

        btmLeftX = new SimpleDoubleProperty();
        btmLeftY = new SimpleDoubleProperty();

        btmRightX = new SimpleDoubleProperty();
        btmRightY = new SimpleDoubleProperty();

        topRightX.addListener((observable, oldValue, newValue) ->
        {
            this.setWidth(this.getWidth() + (newValue.doubleValue() - oldValue.doubleValue()));
        });

        topRightY.addListener((observable, oldValue, newValue) ->
        {
            this.setY(newValue.doubleValue());
            this.setHeight(this.getHeight() - (newValue.doubleValue() - oldValue.doubleValue()));
        });

        btmLeftX.addListener((observable, oldValue, newValue) ->
        {
            this.setX(newValue.doubleValue());
            this.setWidth(this.getWidth() - (newValue.doubleValue() - oldValue.doubleValue()));
        });

        btmLeftY.addListener((observable, oldValue, newValue) ->
        {
            this.setY(newValue.doubleValue());
            this.setHeight(this.getHeight() + (newValue.doubleValue() - oldValue.doubleValue()));
        });

        btmRightX.addListener((observable, oldValue, newValue) ->
        {
            this.setWidth(this.getWidth() + (newValue.doubleValue() - oldValue.doubleValue()));
        });

        btmRightY.addListener((observable, oldValue, newValue) ->
        {
            this.setHeight(this.getHeight() + (newValue.doubleValue() - oldValue.doubleValue()));
        });

        this.xProperty().addListener((observable, oldValue, newValue) ->
        {           
            btmLeftX.set(newValue.doubleValue());
            topRightX.set(newValue.doubleValue() + this.getWidth());
            btmRightX.set(newValue.doubleValue() + this.getWidth());
        });

        this.yProperty().addListener((observable, oldValue, newValue) ->
        {           
            btmLeftY.set(newValue.doubleValue() + this.getHeight());
            topRightY.set(newValue.doubleValue());
            btmRightY.set(newValue.doubleValue() + this.getHeight());
        });

        this.widthProperty().addListener((observable, oldValue, newValue) ->
        {
            topRightX.set(this.getX() + (newValue.doubleValue() - oldValue.doubleValue()));
            btmRightX.set(this.getX() + (newValue.doubleValue() - oldValue.doubleValue()));
        });

        this.heightProperty().addListener((observable, oldValue, newValue) ->
        {
            btmLeftY.set(this.getY() + (newValue.doubleValue() - oldValue.doubleValue()));
            btmRightY.set(this.getY() + (newValue.doubleValue() - oldValue.doubleValue()));
        });

    } 
    //JavaFx Accessor and Mutator
}

public class FeatureHelper
{   
    private static double orgSceneX;    
    private static double orgSceneY;

    private static double orgTranslateX;
    private static double orgTranslateY;

    public static CustomRectangle createDraggableRectangle(double x, double y, double width, double height,
            boolean resizeImage)
    {
        CustomRectangle rect = new CustomRectangle(x, y, width, height);

        // top left resize handle:
        Handler topLeftHandle = new Handler(rect.topLeftXProperty(), rect.topLeftYProperty());

        // top right resize handle:
        Handler topRightHandle = new Handler(rect.topRightXProperty(), rect.topRightYProperty());

        // bottom left resize handle:
        Handler btmLeftHandle = new Handler(rect.btmLeftXProperty(), rect.btmLeftYProperty());

        // bottom right resize handle:
        Handler btmRightHandle = new Handler(rect.btmRightXProperty(), rect.btmRightYProperty());

        // force circles to live in same parent as rectangle:
        rect.parentProperty().addListener((obs, oldParent, newParent) ->
        {
            for (Circle c : Arrays.asList(topLeftHandle, topRightHandle, btmLeftHandle, btmRightHandle))
            {
                if (newParent != null)
                {
                    ((Pane) newParent).getChildren().add(c);
                }
            }
        });

        rect.setOnMousePressed(event -> 
        {
            event.consume();

            orgSceneX = event.getSceneX();
            orgSceneY = event.getSceneY();

            Node p = ((Node) (event.getSource()));

            orgTranslateX = p.getTranslateX();
            orgTranslateY = p.getTranslateY();          
        });

        rect.setOnMouseDragged(event ->
        {
            double offsetX = event.getSceneX() - orgSceneX;
            double offsetY = event.getSceneY() - orgSceneY;

            double newTranslateX = orgTranslateX + offsetX;
            double newTranslateY = orgTranslateY + offsetY;

            Node p = ((Node) (event.getSource()));
            p.setTranslateX(newTranslateX);
            p.setTranslateY(newTranslateY);

            for(Circle circle : Arrays.asList(topLeftHandle, topRightHandle, btmLeftHandle, btmRightHandle))
            {
                circle.setTranslateX(newTranslateX);
                circle.setTranslateY(newTranslateY);
            }
        });

        return rect;
    }
}
shadow
  • 800
  • 2
  • 16
  • 33

1 Answers1

0

You should have a look at this nice Example from karakullukcuhuseyin --> https://github.com/karakullukcuhuseyin/JavaFX-ImageCropper He extended the Rectangle Class as well and is using his custom Rectangle to select an area in an Image.

3barney21
  • 11
  • 3