Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Drawing on a Canvas

A JavaFX interface is usually built from nodes: every button and label is a separate object that draws itself. A canvas, Canvas, works differently: it is a single picture node you draw on with commands such as "fill a rectangle", "draw a line", "write some text". The commands go through a GraphicsContext object. A canvas suits custom graphs, games and drawing with the mouse. Let's give the notebook a "Sketch" window for quickly drawing a diagram.

The first drawing

Create the window class. The first version draws a few shapes:

// src/main/java/com/example/notes/SketchWindow.java — first version
package com.example.notes;
 
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.stage.Window;
 
public class SketchWindow {
 
    public void show(Window owner) {
        Canvas canvas = new Canvas(400, 200);
        GraphicsContext gc = canvas.getGraphicsContext2D();
 
        gc.setFill(Color.web("#4f6bed"));
        gc.fillRect(20, 20, 120, 80);
 
        gc.setStroke(Color.web("#d9534f"));
        gc.setLineWidth(4);
        gc.strokeOval(170, 20, 80, 80);
        gc.strokeLine(20, 150, 380, 150);
 
        gc.setFill(Color.BLACK);
        gc.setFont(Font.font(20));
        gc.fillText("Sketch", 270, 70);
 
        Stage stage = new Stage();
        stage.setTitle("Sketch");
        stage.initOwner(owner);
        stage.setScene(new Scene(new StackPane(canvas)));
        stage.show();
    }
}

Add <Button text="Sketch" onAction="#showSketch"/> to the bottom row of main-view.fxml, and a method to the controller:

// src/main/java/com/example/notes/MainController.java — a new method showSketch()
@FXML
private void showSketch() {
    new SketchWindow().show(table.getScene().getWindow());
}

Click "Sketch": the window shows a blue rectangle, a red circle, the word "Sketch" to the right of it and a red line at the bottom.

Coordinates and colors

The point (0, 0) is the top left corner of the canvas; X grows to the right and Y grows downwards. The fill... methods paint a shape with the color from setFill, and the stroke... methods draw an outline with the color from setStroke and the width from setLineWidth. A color is a constant like Color.BLACK, a string like Color.web("#4f6bed") or Color.rgb(79, 107, 237).

MethodWhat it draws
fillRect(x, y, w, h), strokeRect(...)A rectangle
fillOval(x, y, w, h), strokeOval(...)An oval inside the rectangle
strokeLine(x1, y1, x2, y2)A line segment
fillText(text, x, y)Text; y is the line the letters stand on
clearRect(x, y, w, h)Erases an area to transparent

Drawing with the mouse

A canvas does not remember what is drawn on it: it is just pixels. To be able to undo lines, we keep them in a list and redraw everything. The full version of the class:

// src/main/java/com/example/notes/SketchWindow.java
package com.example.notes;
 
import java.util.ArrayList;
import java.util.List;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.control.ColorPicker;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.StrokeLineCap;
import javafx.stage.Stage;
import javafx.stage.Window;
 
public class SketchWindow {
 
    private record Line(Color color, List<Point2D> points) {
    }
 
    private final Canvas canvas = new Canvas(500, 320);
    private final GraphicsContext gc = canvas.getGraphicsContext2D();
    private final ColorPicker colorPicker = new ColorPicker(Color.web("#4f6bed"));
    private final List<Line> lines = new ArrayList<>();
 
    public void show(Window owner) {
        canvas.setOnMousePressed(event -> {
            Line line = new Line(colorPicker.getValue(), new ArrayList<>());
            line.points().add(new Point2D(event.getX(), event.getY()));
            lines.add(line);
        });
        canvas.setOnMouseDragged(event -> {
            lines.getLast().points().add(new Point2D(event.getX(), event.getY()));
            redraw();
        });
 
        Button undo = new Button("Undo");
        undo.setOnAction(event -> {
            if (!lines.isEmpty()) {
                lines.removeLast();
                redraw();
            }
        });
        Button clear = new Button("Clear");
        clear.setOnAction(event -> {
            lines.clear();
            redraw();
        });
 
        VBox root = new VBox(8, new HBox(8, colorPicker, undo, clear), canvas);
        root.setPadding(new Insets(16));
        redraw();
 
        Stage stage = new Stage();
        stage.setTitle("Sketch");
        stage.initOwner(owner);
        stage.setScene(new Scene(root));
        stage.show();
    }
 
    private void redraw() {
        gc.setFill(Color.WHITE);
        gc.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
 
        gc.setLineWidth(3);
        gc.setLineCap(StrokeLineCap.ROUND);
        for (Line line : lines) {
            gc.setStroke(line.color());
            gc.beginPath();
            Point2D first = line.points().getFirst();
            gc.moveTo(first.getX(), first.getY());
            for (Point2D point : line.points()) {
                gc.lineTo(point.getX(), point.getY());
            }
            gc.stroke();
        }
 
        gc.setFill(Color.GRAY);
        gc.fillText("Lines: " + lines.size(), 10, canvas.getHeight() - 10);
    }
}

Pressing the mouse button starts a new line, and every movement with the button held adds a point and calls redraw(). The coordinates event.getX() and event.getY() are already measured from the corner of the canvas. redraw() fills the canvas with white and draws all lines again: beginPath starts a path, moveTo puts the pen down, lineTo moves it on, and stroke draws it. ColorPicker is the standard color chooser.

Draw a blue line, pick red and draw a second one: "Lines: 2" appears at the bottom. "Undo" removes the last line, "Clear" removes them all.

Common mistake

The color is set with setFill(Color.RED) before strokeLine, and the line is expected to be red, but it comes out black. There is no error: lines, outlines and stroke() take their color from setStroke, while setFill only affects the fill... methods and text. Both the stroke and the fill are black by default.

Thanks for reading!

Donate to the site — we work hard for you.