Reputation: 6136
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
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