Search code examples
canvasjavafxdrawingscrollpane

Drawing inside a scrollpane in JavaFX8


After hours of searching I couldn't find a solution for my problem.

I got a simple drawing application where I have a rectangle to draw lines on. This rectangle should be inside a Scrollpane, so that if it' bigger than the program window, the user just scrolls to a different location.

Here is my current approach:

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        Application.launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Drawing Board Demo");
        final Group root = new Group();

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

        // A group to hold all the drawn path elements
        line_Group = new Group();


        final Rectangle canvas = new Rectangle(scene.getWidth() - 20, scene.getHeight() - 20);

...

        // Build the canvas
        canvas.setCursor(Cursor.CROSSHAIR);
        canvas.setFill(Color.LIGHTGRAY);
        canvas.setOnMousePressed(new EventHandler<MouseEvent>() {

            @Override
            public void handle(MouseEvent me) {

                path = new Path();
                path.setMouseTransparent(true);
                path.setStrokeWidth(sampleLine.getStrokeWidth());
                path.setStroke(sampleLine.getStroke());
                line_Group.getChildren().add(path);
                path.getElements().add(new MoveTo(me.getSceneX(), me.getSceneY()));
            }
        });


        canvas.setOnMouseDragged(new EventHandler<MouseEvent>() {

            @Override
            public void handle(MouseEvent me) {

                // keep lines within rectangle

                if (canvas.getBoundsInLocal().contains(me.getX(), me.getY())) {
                path.getElements().add(new LineTo(me.getSceneX(), me.getSceneY()));
                }

            }
        });


        // Build the VBox container for the toolBox, sampleline, and canvas
        final Group board  = new Group();
        board.getChildren().addAll(canvas, line_Group);
        ScrollPane scroll = createScrollPane(board);

        VBox vb = new VBox(20);
        vb.setPrefWidth(scene.getWidth() - 20);
        vb.setLayoutY(20);
        vb.setLayoutX(10);
        vb.getChildren().addAll(toolBox, stackpane, scroll);
        root.getChildren().addAll(vb);
        primaryStage.setScene(scene);
        primaryStage.show();

    }

...

    private ScrollPane createScrollPane(Group layout) {
        ScrollPane scroll = new ScrollPane();
        scroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
        scroll.setMaxHeight(400);
        scroll.setContent(layout);
        return scroll;
    }
}

The problem here is, that the drawn path is always drawn in the same location i.e. if I move the scrollbar to draw further down, it doesn't change anything.

I've tried using ".getX()" instead of ".getsceneX()" in the mouseEvent, but that didn't help either.

I'm new to JavaFX so please bear with me


Solution

  • It's a simple application, but there are a few things which will change depending upon specific requirements, e.g. what happens if the user drags outside of the scrollpane? Is the drawing area fixed or does it size to be at least the minimum size of the viewable area?, etc.

    Here is a sample application I came up with. It won't match your requirements exactly as I don't know precisely what they are, but hopefully it will give you enough basic information to get you going again in the right direction.

    Press and drag the mouse to draw lines on the pane displayed inside the scrollpane. If you drag outside the pane to the bottom or right, the drawing pane minimum size will update to fit the new line content in it. Mouse listeners onMousePressed, onMouseDragged, onMouseReleased are used to handle the start of a drawing operation, setting the endpoint and completing the drawing operation.

    lines in scrollpane

    import javafx.application.Application;
    import javafx.scene.Scene;
    import javafx.scene.control.ScrollPane;
    import javafx.scene.layout.Pane;
    import javafx.scene.shape.Line;
    import javafx.stage.Stage;
    
    public class LineDrawer extends Application {
    
        public static void main(String[] args) {
            launch(args);
        }
    
        private Line curLine;
    
        @Override
        public void start(Stage stage) throws Exception {
            Pane drawingPane = new Pane();
            drawingPane.setPrefSize(800, 800);
            drawingPane.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
            ScrollPane scrollPane = new ScrollPane(drawingPane);
            scrollPane.setPrefSize(300, 300);
            scrollPane.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
            scrollPane.setFitToWidth(true);
            scrollPane.setFitToHeight(true);
            scrollPane.setStyle("-fx-focus-color: transparent;");
    
            drawingPane.setOnMousePressed(event -> {
                if (!event.isPrimaryButtonDown()) {
                    return;
                }
    
                curLine = new Line(
                    event.getX(), event.getY(), 
                    event.getX(), event.getY()
                );
                drawingPane.getChildren().add(curLine);
            });
    
            drawingPane.setOnMouseDragged(event -> {
                if (!event.isPrimaryButtonDown()) {
                    return;
                }
    
                if (curLine == null) {
                    return;
                }
    
                curLine.setEndX(event.getX());
                curLine.setEndY(event.getY());
    
                double mx = Math.max(curLine.getStartX(), curLine.getEndX());
                double my = Math.max(curLine.getStartY(), curLine.getEndY());
    
                if (mx > drawingPane.getMinWidth()) {
                    drawingPane.setMinWidth(mx);
                }
    
                if (my > drawingPane.getMinHeight()) {
                    drawingPane.setMinHeight(my);
                }
            });
    
            drawingPane.setOnMouseReleased(event -> curLine = null);
    
            Scene scene = new Scene(scrollPane);
            stage.setMinWidth(100);
            stage.setMinHeight(100);
            stage.setScene(scene);
            stage.show();
        }
    }