I am experimenting JavaFX with a simple image viewer. I want it to display an image and, if it does not fit in the window, display scrollbars. The image to dislay is loaded with the FileChooser
and set to the ImageView
using imageView.setImage(image)
.
The problem is that the scrollbars of the containing ScrollPane
do not update after calling imageView.setImage(image)
. Instead, I need to perform an action that changes the scene (e.g. resize the window). It behaves the same when an image is displayed and another is loaded, i.e. the sizes of the scrollbars reflect the size of the previous image.
The bug is reproducible using the following cut-down version of the code:
(Java)
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import javafx.application.Application;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
public class ImageViewerBug extends Application
{
@FXML private ImageView imageView;
@Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("ImageViewerBug.fxml"));
fxmlLoader.setController(this);
try {
BorderPane borderPane = (BorderPane) fxmlLoader.load();
Scene scene = new Scene(borderPane);
primaryStage.setScene(scene);
primaryStage.show();
}
catch( IOException e ) {
throw new RuntimeException(e);
}
}
public void loadClicked() {
FileChooser fileChooser = new FileChooser();
File f = fileChooser.showOpenDialog(null);
if( f != null ) {
try( InputStream is = new FileInputStream(f) ) {
Image image = new Image(is);
imageView.setImage(image);
}
catch( IOException e ) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
launch(args);
}
}
(FXML)
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.Group?>
<?import javafx.scene.control.*?>
<?import javafx.scene.image.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.paint.*?>
<BorderPane id="BorderPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml">
<center>
<ScrollPane prefHeight="200.0" prefWidth="200.0">
<content>
<Group>
<ImageView fx:id="imageView" pickOnBounds="true" preserveRatio="true" />
</Group>
</content>
</ScrollPane>
</center>
<top>
<ToolBar>
<items>
<Button mnemonicParsing="false" onAction="#loadClicked" text="Load" />
</items>
</ToolBar>
</top>
</BorderPane>
I can confirm this bug. A quick and dirty workaround that fixed it for me:
The
snapshot()
Method sometimes works like a charm whenever some UI-element isn't updated or displayed correctly. But don't ask me why that works, maybe it has something to do with the rendering process, becausesnapshots
forces JavaFX to render the scene graph or the target elements. The random stuff is needed to create the right scrollbar sizes when adding a new image after one already part of theImageView
. Maybe this has something to do with the strange behaviour i found some weeks earlier. The workaround also solved my problems back then.(Really late)EDIT
The problem is caused by the
Group
which wraps theImageView
. If you use a node that inherits fromPane
the behaviour of theScrollPane
should be as expected even without the workaround. AGroup
can still be used as the direct child of theScrollPane
as long as aPane
wraps theImageView
.Example:
I have had the same problem with image...Instead of
You should write:
Image will refresh automatically, you don't have to do anything. For me, it works