SteeveDroz
SteeveDroz

Reputation: 6136

Binding an ImageView size to an AnchorPane always gives a size of 0

I have an ImageView inside an AnchorPane, built using FXML.

<fx:root prefHeight="600.0" prefWidth="800.0" type="AnchorPane" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.LifeMandelbrot">
  <children>
    <ImageView fx:id="view" fitHeight="600.0" fitWidth="800.0" onMouseClicked="#moveCenter" pickOnBounds="true" preserveRatio="true" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
    <HBox alignment="CENTER" spacing="10.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0">
      <children>
        <Button fx:id="zoomOut" mnemonicParsing="false" onAction="#zoomOut" text="Zoom-" />
        <Button fx:id="zoomIn" mnemonicParsing="false" onAction="#zoomIn" text="Zoom+" />
        <Button fx:id="defaultView" mnemonicParsing="false" onAction="#defaultView" text="Vue par défaut" />
      </children>
    </HBox>
  </children>
</fx:root>

As you can see, the ImageView fits the AnchorPane using anchors.

When I click one of the button, the ImageView is repainted.

Problem: view.getFitWidth() always shows 0, same for the height.


EDIT

The controller code looks like that:

package application;

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

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;

public class LifeMandelbrot extends AnchorPane implements Initializable {
    private static final double DEFAULT_ZOOM = 200.0;
    private static final Complex DEFAULT_CENTER = new Complex(0, 0);
    private static final double ZOOM_RATIO = 1.2;
    @FXML
    private Button zoomOut;

    @FXML
    private Button zoomIn;

    @FXML
    private Button defaultView;

    @FXML
    private Button julia;

    @FXML
    ImageView view;

    private double zoom;
    private Complex center;
    private List<Color> colors;
    private int colorStep;

    public LifeMandelbrot() {
    zoom = DEFAULT_ZOOM;
    center = DEFAULT_CENTER;
    colors = new ArrayList<Color>();
    colors.add(Color.RED);
    colors.add(Color.GREEN);
    colors.add(Color.BLUE);
    colorStep = 20;
    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {
    repaint();
    view.fitWidthProperty().bind(widthProperty());
    view.fitHeightProperty().bind(heightProperty());
    }

    @FXML
    void defaultView(ActionEvent event) {
    zoom = DEFAULT_ZOOM;
    center = DEFAULT_CENTER;
    repaint();
    }

    @FXML
    void julia(ActionEvent event) {
    }

    @FXML
    void zoomIn(ActionEvent event) {
    zoom *= ZOOM_RATIO;
    repaint();
    }

    @FXML
    void zoomOut(ActionEvent event) {
    zoom /= ZOOM_RATIO;
    repaint();
    }

    @FXML
    void moveCenter(MouseEvent event) {
    center = fractalFromView(event.getX(), event.getY());
    repaint();
    }

    private void repaint() {
    WritableImage image = new WritableImage((int) view.getFitWidth(), (int) view.getFitHeight());
    PixelWriter pw = image.getPixelWriter();
    for (int x = 0; x < image.getWidth(); x++) {
        for (int y = 0; y < image.getHeight(); y++) {
        Complex c = fractalFromView(x, y);
        int iterations = Fractal.mandelbrot(c);
        if (iterations == -1) {
            pw.setColor(x, y, new Color(0, 0, 0, 1));
        } else {
            int colorIndex = iterations / colorStep;
            int colorAdd = iterations % colorStep;
            Color color1 = colors.get(colorIndex % colors.size());
            Color color2 = colors.get((colorIndex + 1) % colors.size());
            Color color = color1.interpolate(color2, (double) colorAdd / colorStep);
            pw.setColor(x, y, color);
        }
        }
    }
    view.setImage(image);
    }

    private Complex fractalFromView(double x, double y) {
    return new Complex((x - view.getFitWidth() / 2) / zoom + center.getReal(),
        (y - view.getFitHeight() / 2) / zoom + center.getImaginary());
    }
}

Loaded from there:

package application;

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

public class Main extends Application {
    @Override
    public void start(Stage primaryStage) {
    try {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("../lifeMandelbrot.fxml"));
        loader.setRoot(new LifeMandelbrot());
        AnchorPane root = loader.load();
        Scene scene = new Scene(root);
        scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
        primaryStage.setScene(scene);
        primaryStage.setTitle("LIFE Is a Fractal Explorer");
        primaryStage.show();
    } catch (Exception e) {
        e.printStackTrace();
    }
    }

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

Upvotes: 0

Views: 1551

Answers (1)

James_D
James_D

Reputation: 209339

You have two instances of LifeMandelbrot: one you create by calling the constructor and explicitly set as the dynamic root of the FXML; the other is created for you by the FXMLLoader and is used as the controller. The one you create you use in the scene graph (scene = new Scene(root)). The one that is created as the controller is never placed in the scene graph. Consequently it never undergoes layout and always has width and height of zero.

Of course, the handler methods and initialize() methods are called on the "controller instance", not the "root instance", so you bind fitWidth and fitHeight to zero.

You need

FXMLLoader loader = new FXMLLoader(getClass().getResource("../lifeMandelbrot.fxml"));
LifeMandelbrot root = new LifeMandelbrot();
loader.setRoot(root);
loader.setController(root);
loader.load();
Scene scene = new Scene(root);

and then you need to remove the fx:controller attribute from the root element of the FXML. This way the controller and root node are the same instance.

Since your FXML already explicitly ties itself to an AnchorPane by using the anchor pane settings on the child nodes, it might be clearer just to use the standard pattern for this. I.e.

<AnchorPane fx:id="root" fx:controller="application.LifeMandelbrot" prefHeight="600.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
  <children>
    <ImageView fx:id="view" onMouseClicked="#moveCenter" pickOnBounds="true" preserveRatio="true" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
    <HBox alignment="CENTER" spacing="10.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0">
      <children>
        <Button fx:id="zoomOut" mnemonicParsing="false" onAction="#zoomOut" text="Zoom-" />
        <Button fx:id="zoomIn" mnemonicParsing="false" onAction="#zoomIn" text="Zoom+" />
        <Button fx:id="defaultView" mnemonicParsing="false" onAction="#defaultView" text="Vue par défaut" />
      </children>
    </HBox>
  </children>
</AnchorPane>

In the controller:

public class LifeMandelbrot implements Initializable {

    @FXML
    private AnchorPane root ;

    // existing code...

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        repaint();
        view.fitWidthProperty().bind(root.widthProperty());
        view.fitHeightProperty().bind(root.heightProperty());
    }    

    // existing code...
}

and then just

FXMLLoader loader = new FXMLLoader(getClass().getResource("../lifeMandelbrot.fxml"));
Scene scene = new Scene(loader.load()); 

Upvotes: 1

Related Questions