1

I am apologising in advance for the upcoming silly question.

Is there a replacement for the paintComponents method a JPanel has for javaFX? Or should I just use a JFXPanel which is embed with Swing?

For example I figured that Timeline is the same as a Timer in Swing, what is the corresponding thing for a Panel/paintComponents?

EDIT: For example how would we go about animating a circle from an x-y coordinate to another? (Without the use of an TranslateTransition, of course)

I tried painting things with canvas but I couldn't figure out how to constantly update the canvas, like calling repaint() in Swing?

  • I am guessing you have to use a JavaFx Canvas. – SedJ601 Apr 07 '17 at 13:25
  • 2
    Graphics primitives in JavaFX are run via Prism, which in many cases will defer to the native graphics toolkit. Thus there's no direct equivalent of `paintComponent` (as there's no general access to a `Graphics` object on which to paint). You can, as suggested by @SedrickJefferson, use a [`Canvas`](http://docs.oracle.com/javase/8/javafx/api/javafx/scene/canvas/Canvas.html), or perhaps subclass `Region` and override `layoutChildren()` (along with the methods determining the dimension), using `Shape`s and `Text` elements as the child nodes, as needed. – James_D Apr 07 '17 at 13:31
  • If you can be more specific about what you are trying to do, you might get more useful suggestions. – James_D Apr 07 '17 at 13:32
  • It sounds like you are trying to use JavaFX but trying to avoid using it in the way it was intended to be used. The way you would animate a circle from one location to another would be to use a `TranslateTransition` or a `Timeline`, or possibly an `AnimationTimer`, depending on the exact requirements. Why would you want to do it *without* the animation API? (The equivalent of `repaint()`, though, is [`requestLayout()`](http://docs.oracle.com/javase/8/javafx/api/javafx/scene/Parent.html#requestLayout--) in `Parent`; there is no real equivalent in `Canvas` - you would animate the `Canvas`). – James_D Apr 07 '17 at 13:41
  • Transitions are great, but they can't do everything. Yes the examples I gave could easily be done with transitions, but I'm more interested in the part where I can update what I'm doing. –  Apr 07 '17 at 13:47
  • What do you need to do that cannot be done with the `AnimationTimer`? – James_D Apr 07 '17 at 13:49
  • I've drawn a grid and I want it to scale up/down when I resize my scene to fill it. (I'm not saying this can't be done with an AnimationTimer, but calling repaint() was quite useful) –  Apr 07 '17 at 13:51
  • Just add listeners to the size? Or wrap it in a `Region` and override `layoutChildren()`, depending on how you have implemented the grid. Again, I think you need to make your question specific. – James_D Apr 07 '17 at 14:11
  • Okay I'm getting way off topic here just because I can't find a suitable example to this. Could you please explain to me how I would do this; Lets say I added a couple of lines to the rootPane, and when I resize the pane, I want those lines to be redrawn, to match the size of the pane. –  Apr 07 '17 at 14:21
  • I would just do that with bindings. See "answer". – James_D Apr 07 '17 at 14:29
  • See also this related [example](http://stackoverflow.com/q/31761361/230513) that compares the same animation in Swing and JavaFX. – trashgod Apr 07 '17 at 20:28

2 Answers2

2

Here is an example of a grid, that resizes automatically using simple bindings.

import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Line;

public class Grid {
    private final Pane view = new Pane();

    private final int numColumns ;
    private final int numRows ;

    // arbitrary defaults of 20:
    private final DoubleProperty prefColumnWidth = new SimpleDoubleProperty(20);
    private final DoubleProperty prefRowHeight = new SimpleDoubleProperty(20);

    public Grid(int numColumns, int numRows) {
        this.numColumns = numColumns ;
        this.numRows = numRows ;

        for (int x = 0 ; x <= numColumns ; x++) {
            Line line = new Line();
            line.startXProperty().bind(view.widthProperty().multiply(x).divide(numColumns));
            line.endXProperty().bind(line.startXProperty());
            line.setStartY(0);
            line.endYProperty().bind(view.heightProperty());
            view.getChildren().add(line);
        }

        for (int y = 0 ; y <= numRows ; y++) {
            Line line = new Line();
            line.startYProperty().bind(view.heightProperty().multiply(y).divide(numRows));
            line.endYProperty().bind(line.startYProperty());
            line.setStartX(0);
            line.endXProperty().bind(view.widthProperty());
            view.getChildren().add(line);
        }

        view.prefWidthProperty().bind(prefColumnWidth.multiply(numColumns));
        view.prefHeightProperty().bind(prefRowHeight.multiply(numRows));
    }

    public final DoubleProperty prefColumnWidthProperty() {
        return this.prefColumnWidth;
    }


    public final double getPrefColumnWidth() {
        return this.prefColumnWidthProperty().get();
    }


    public final void setPrefColumnWidth(final double prefColumnWidth) {
        this.prefColumnWidthProperty().set(prefColumnWidth);
    }


    public final DoubleProperty prefRowHeightProperty() {
        return this.prefRowHeight;
    }


    public final double getPrefRowHeight() {
        return this.prefRowHeightProperty().get();
    }


    public final void setPrefRowHeight(final double prefRowHeight) {
        this.prefRowHeightProperty().set(prefRowHeight);
    }

    public Pane getView() {
        return view;
    }

    public int getNumColumns() {
        return numColumns;
    }

    public int getNumRows() {
        return numRows;
    }

}

Here's a simple test:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class GridTest extends Application {

    @Override
    public void start(Stage primaryStage) {
        Grid grid = new Grid(10,10);
        Scene scene = new Scene(grid.getView());
        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

There are a bunch of different approaches you could use for this. Note that the approach above doesn't really require subclassing at all, so you could just write a method that created the pane:

private Pane createGrid(int numColumns, int numRows) {

    Pane view = new Pane();

    for (int x = 0 ; x <= numColumns ; x++) {
        Line line = new Line();
        line.startXProperty().bind(view.widthProperty().multiply(x).divide(numColumns));
        line.endXProperty().bind(line.startXProperty());
        line.setStartY(0);
        line.endYProperty().bind(view.heightProperty());
        view.getChildren().add(line);
    }

    for (int y = 0 ; y <= numRows ; y++) {
        Line line = new Line();
        line.startYProperty().bind(view.heightProperty().multiply(y).divide(numRows));
        line.endYProperty().bind(line.startYProperty());
        line.setStartX(0);
        line.endXProperty().bind(view.widthProperty());
        view.getChildren().add(line);
    }

    view.setPrefSize(20*numColumns, 20*numRows);
    return view ;
}

Or, if you wanted something closer to an AWT/Spring way of doing things, you could subclass Region, use a Canvas, and override Region.layoutChildren(). The layoutChildren() method is called as part of a layout pass (which will be triggered if the region changes size). In this one I added support for padding:

import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.Region;

public class Grid extends Region {

    private Canvas canvas ;
    private final int numColumns ;
    private final int numRows ;

    public Grid(int numColumns, int numRows) {
        this.numColumns = numColumns ;
        this.numRows = numRows ;
        canvas = new Canvas();
        getChildren().add(canvas);
    }

    @Override
    protected void layoutChildren() {
        double w = getWidth() - getPadding().getLeft() - getPadding().getRight() ;
        double h = getHeight() - getPadding().getTop() - getPadding().getBottom() ;

        canvas.setWidth(w+1);
        canvas.setHeight(h+1);

        canvas.setLayoutX(getPadding().getLeft());
        canvas.setLayoutY(getPadding().getRight());

        GraphicsContext gc = canvas.getGraphicsContext2D();
        gc.clearRect(0, 0, w, h);

        for (int i = 0 ; i <= numColumns ; i++) {

            // adding 0.5 here centers the line in the physical pixel,
            // making it appear crisper:
            double x = w*i/numColumns + 0.5;

            gc.strokeLine(x, 0, x, h);
        }

        for (int j = 0 ; j <= numRows ; j++) {
            double y = h*j/numRows + 0.5 ;
            gc.strokeLine(0, y, w, y);
        }
    }

    @Override
    protected double computePrefWidth(double height) {
        return 20 * numColumns;
    }

    @Override
    protected double computePrefHeight(double width) {
        return 20 * numRows ;
    }

}

Here's a test for this:

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class GridTest extends Application {

    @Override
    public void start(Stage primaryStage) {
        Grid grid = new Grid(10,10);
        grid.setPadding(new Insets(20));
        Scene scene = new Scene(grid, 400, 400);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}
James_D
  • 201,275
  • 16
  • 291
  • 322
  • 1
    I appreciate the effort you gave into this, and thank you, but I still don't understand why I have to make so much effort out of this instead of being able to just call repaint() and get it done? JavaFX has many improvements over Swing and I find Swing to be very messy, but this was a bummer for me. –  Apr 07 '17 at 14:32
  • 1
    @MertDuman I don't see how defining a `repaint()` method that computes the positions of the lines is easier than expressing the positions of the lines as bindings. Isn't the code essentially identical? The advantage here is that JavaFX now automatically knows when redrawing this grid is required: you don't have to specify that and the JavaFX framework can optimize the repaint maximally (i.e. only repaint if something has changed, and only repaint on a pulse - so it doesn't recompute the layout on the change of every line, just once when any line has changed). – James_D Apr 07 '17 at 14:37
  • @MertDuman And again, as I mentioned in my very first comment, the lines here are (probably, depending on your platform) actually drawn using the native (hardware-accelerated) graphics; if you explicitly draw them in Java code that can't be done. – James_D Apr 07 '17 at 14:41
  • g.drawLine( 0, 0, 0, this.getHeight()); g.drawLine( 0, 0, this.getWidth(), 0); This is the code required which draws two lines that resizes when the screen size changes. –  Apr 07 '17 at 14:47
  • @MertDuman There is barely any more code than that for two lines in JavaFX, though, no? I think it is merely the familiarity that makes you think swing is easier here. – James_D Apr 07 '17 at 14:50
  • Probably, but I really want to switch to FX because its much better. Do you know any good tutorials or lectures for it? My university teacher is over 50 years old and doesn't know FX, hence teaches us Swing -,- Also much thanks for the answer. –  Apr 07 '17 at 14:56
  • @MertDuman A lot of people like [this tutorial](http://code.makery.ch/library/javafx-8-tutorial/part1/). And there is the [official Oracle page](http://docs.oracle.com/javase/8/javase-clienttechnologies.htm). I just learned from the Oracle page and then [API docs](http://www.oracle.com/pls/topic/lookup?ctx=javase80&id=JFXAP), and for advanced stuff I look at the [source code](http://hg.openjdk.java.net/openjfx/8u60/rt/file/996511a322b7/modules) to see how the standard controls etc work. – James_D Apr 07 '17 at 16:20
  • @MertDuman FWIW I will be 50 next year: what are you implying ;) – James_D Apr 07 '17 at 16:21
  • Nothing, you're awesome ^^ –  Apr 07 '17 at 17:51
0

In JavaFX, the only thing that has a GraphicsContext drawing surface is a Canvas. So you could extend Canvas and add a repaint() method that gets Canas.getGraphicsContext2D() and does its own painting from there.

Taking that further, you could extend Pane and have an overlayed Canvas as a child node (bind the Pane and Canvas width/height properties together). Then you have a parent component that supports custom painting (and reacts to mouse events via the canvas events) just like JPanel from Swing.

BinaryDigit09
  • 110
  • 10
  • I should add that the first thing this repaint() method should do is call clearRect(0, 0, getWidth(), getHeight()) on the canvas' GraphicsContext. Otherwise you'll get artifacts from all the drawing done in the previous repaint() call(s). – BinaryDigit09 Apr 07 '17 at 16:57
  • You would still have to find a way to make sure your `repaint()` method was called when needed: that wouldn't happen automatically. – James_D Apr 07 '17 at 17:20