Search code examples
javajavafxscrollpane

ScrollPane doesnt "know" when to expand


I'm not sure how to call this problem but you will definitely understand what I mean... at least I hope you will.

To start with: I have my Pane with a ScrollPane in it. This Pane also contains two Buttons on which I can create rectangles in my ScrollPane.

I can drag those Rectangles around and they can be moved out of view. This far everything works as it should be except the ScrollPane. Usually you had the ScrollPane expand his Scroll so you can, if you let go of the Rectangle, scroll to that Rectangle and grab it again but nice and friendly JavaFX doesnt know how to expand the Scroll so I cant get to this Rectangle again.

Or I just miss some important part (which is the more likely thing to happen)

Some words to my Code: I don't think that its a problem in my existing code but more a problem that needs a workaround. The only things that I changed for the ScrollPane are that I set the ScrollBarPolicy to always and set the ScrollPane Pannable but this is consumed when I click on a Rectangle so I can move them and don't pan the ScrollPane.

enter image description here

What you see above is one Rectangle in its full size and one which is partly out of the ScrollPane boundaries but the ScrollPane doesn't expand the ScrollBar. Doesn't matter how far I move that Rectangle away from those boundaries the Scrollbar won't change which is bad for this is the only reason I chose a ScrollPane and also, I think, the only (easiest) pane to achieve this "editor like" view.

Yea so thank you for at least reading this far and like always sorry for my broken English ^^. I hope you can give me a advice on how to fix this problem!

EDIT:

Here a minimal example: Controller.java

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;

import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Rectangle;

public class Controller implements Initializable{
    @FXML ScrollPane actionWin;
    Rectangle r;
    Double orgTranslateX, orgTranslateY, orgSceneX, orgSceneY, newTranslateX, newTranslateY, offsetY, offsetX;
    int i = 50;
    List<Rectangle> recs = new ArrayList<Rectangle>();

@Override
public void initialize(URL arg0, ResourceBundle arg1) {
    actionWin.setPannable(true);
    actionWin.setHbarPolicy(ScrollBarPolicy.ALWAYS);
    actionWin.setVbarPolicy(ScrollBarPolicy.ALWAYS);
}

@FXML
public void createEntity(){

       Pane container = new Pane();

        // Button 1
        r = new Rectangle();
        r.addEventHandler(MouseEvent.ANY, event -> {
            if(event.getButton() != MouseButton.MIDDLE) event.consume();
        });

        r.setOnMousePressed(onMousePressedEventHandler);
        r.setOnMouseDragged(onMouseDraggedEventHandler);
        r.setX(i);
        r.setY(i);
        r.setWidth(50);
        r.setHeight(20);
        i+=30;
        recs.add(r);

        for(Rectangle rec : recs){
            container.getChildren().add(rec);
        }

        actionWin.setContent(container);
        try {
            AnchorPane root = (AnchorPane) FXMLLoader.load(getClass().getResource("Main.fxml"));
        } catch (IOException e) {
            e.printStackTrace();
        }
}

EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() {
    @Override
    public void handle(MouseEvent arg0) {
        orgSceneX = arg0.getSceneX();
        orgSceneY = arg0.getSceneY();
        orgTranslateX = ((Rectangle) (arg0.getSource())).getTranslateX();
        orgTranslateY = ((Rectangle) (arg0.getSource())).getTranslateY();
    }};

EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
    @Override
    public void handle(MouseEvent arg0) {
        offsetX = arg0.getSceneX() - orgSceneX;
        offsetY = arg0.getSceneY() - orgSceneY;
        newTranslateX = orgTranslateX + offsetX;
        newTranslateY = orgTranslateY + offsetY;
        ((Rectangle)(arg0.getSource())).setTranslateX(newTranslateX);
        ((Rectangle)(arg0.getSource())).setTranslateY(newTranslateY);
    }};
}

Main.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.Spinner?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane fx:id="root" maxHeight="720.0" maxWidth="1280.0" minHeight="720.0" minWidth="1280.0" prefHeight="720.0" prefWidth="1280.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.Controller">
   <children>
      <Button fx:id="createEntity_B" layoutX="14.0" layoutY="14.0" mnemonicParsing="false" onAction="#createEntity" text="Create Entity" />
      <ScrollPane fx:id="actionWin" layoutX="14.0" layoutY="39.0" prefHeight="672.0" prefWidth="996.0" />
   </children>
</AnchorPane>

Main.java

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;


public class Main extends Application {
    public void start(Stage primaryStage) {
        try {
            AnchorPane root = (AnchorPane) 
            FXMLLoader.load(getClass().getResource("Main.fxml"));
            Scene scene = new Scene(root);
            primaryStage.setScene(scene);
            primaryStage.setResizable(false);
            primaryStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

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

Wrap these three classes in a package called "application" and you're good to go.


Solution

  • You're using translateX and translateY to determine the new position of the Rectangles.

    Transforms like the translate properties are not taken into account when the parent determines it's size. The child is treated as if it was located at (layoutX, layoutY). You need to modify those properties, if you want the Pane to be resized properly.

    Some other things in the createEntity seem odd though:

    • You recreate the Pane instead of creating it once in the fxml.
    • You load a fxml that never seems to be used.

    Also you should prefer the primitive type double to the reference type Double unless you're not doing any arithmetic operations or comparisons.


    <AnchorPane fx:id="root" maxHeight="720.0" maxWidth="1280.0" minHeight="720.0" minWidth="1280.0" prefHeight="720.0" prefWidth="1280.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.Controller">
       <children>
          <Button fx:id="createEntity_B" layoutX="14.0" layoutY="14.0" mnemonicParsing="false" onAction="#createEntity" text="Create Entity" />
          <ScrollPane fx:id="actionWin" layoutX="14.0" layoutY="39.0" prefHeight="672.0" prefWidth="996.0">
              <content>
                  <Pane fx:id="container"/>
              </content>
          </ScrollPane>
       </children>
    </AnchorPane>
    
    @FXML
    private Pane container;
    
    @FXML
    public void createEntity() {
        class DraggedHandler implements EventHandler<MouseEvent> {
    
            double offsetX;
            double offsetY;
    
            @Override
            public void handle(MouseEvent event) {
                Node source = (Node) event.getSource();
                Point2D pt = source.localToParent(event.getX(), event.getY());
                source.setLayoutX(pt.getX() + offsetX);
                source.setLayoutY(pt.getY() + offsetY);
            }
    
        }
    
        Rectangle rect = new Rectangle(i, i, 50, 20);
        i += 30;
        container.getChildren().add(rect);
    
        rect.addEventHandler(MouseEvent.ANY, event -> {
            if (event.getButton() != MouseButton.MIDDLE) {
                event.consume();
            }
        });
    
        DraggedHandler handler = new DraggedHandler();
        rect.setOnMouseDragged(handler);
        rect.setOnMousePressed(evt -> {
            Node source = (Node) evt.getSource();
            Point2D pt = source.localToParent(evt.getX(), evt.getY());
            handler.offsetX = source.getLayoutX() - pt.getX();
            handler.offsetY = source.getLayoutY() - pt.getY();
        });
    }