JavaFX - Centering BackgroundImage that covers BorderPane

1.3k views Asked by At

I am trying set BackgroundImage as background of BorderPane (pane) in JavaFX. I want that BackgroundImage covers BorderPane and that it is always positioned in the center. I want to do the same as in CSS (for web) with background-image.

I have already tried:

  1. Using CSS (fx-CSS)

    package application;
    
    import javafx.application.Application;
    import javafx.stage.Stage;
    import javafx.scene.Scene;
    import javafx.scene.control.Button;
    import javafx.scene.layout.BorderPane;
    
    public class ExternalCSS extends Application {
        @Override
        public void start(Stage primaryStage) {
            try {
    
                //BorderPane()
                //https://docs.oracle.com/javase/8/javafx/api/javafx/scene/layout/BorderPane.html
                BorderPane borderPane = new BorderPane();
    
    
                //Button(String text)
                //https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/Button.html
                Button btn = new Button("Center");
    
                //https://docs.oracle.com/javase/8/javafx/api/javafx/scene/layout/BorderPane.html#setCenter-javafx.scene.Node-
                borderPane.setCenter(btn);
    
    
                Scene scene = new Scene(borderPane, 600, 600);
    
                URL path = getClass().getResource("resources/BackgroundStyle.css");
    
                if(path != null) {
                    scene.getStylesheets().add(path.toExternalForm());
                } else {
                    scene.getStylesheets().add("https://raw.githubusercontent.com/4nds/CenteredBackgroundImage/master/Background/src/application/resources/BackgroundStyleExternalURL.css");
                }
    
                borderPane.setId("background_id");
    
                primaryStage.setScene(scene);
                primaryStage.show();
    
            } catch(Exception e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    

    This code uses external CSS (you can find my CSS code here) or you can use this code below with inline CSS:

    package application;
    
    import javafx.application.Application;
    import javafx.stage.Stage;
    import javafx.scene.Scene;
    import javafx.scene.control.Button;
    import javafx.scene.layout.BorderPane;
    
    public class InlineCSS extends Application {
        @Override
        public void start(Stage primaryStage) {
            try {
    
                //BorderPane()
                //https://docs.oracle.com/javase/8/javafx/api/javafx/scene/layout/BorderPane.html
                BorderPane borderPane = new BorderPane();
    
    
                //Button(String text)
                //https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/Button.html
                Button btn = new Button("Center");
    
                //https://docs.oracle.com/javase/8/javafx/api/javafx/scene/layout/BorderPane.html#setCenter-javafx.scene.Node-
                borderPane.setCenter(btn);
    
    
                Scene scene = new Scene(borderPane, 600, 600);
    
                URL path = getClass().getResource("resources/black_clock.png");
    
                String image = null;
                if(path != null) {
                    image = path.toExternalForm();
                } else {
                    image = "https://raw.githubusercontent.com/4nds/CenteredBackgroundImage/master/Background/src/application/resources/black_clock.png";
                }                   
    
                borderPane.setStyle("-fx-background-image: url('" + image + "'); " +
                           "-fx-background-position: center center; " +
                           "-fx-background-repeat: no-repeat;" +
                           "-fx-background-size: cover");
    
                primaryStage.setScene(scene);
                primaryStage.show();
    
            } catch(Exception e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    
  2. Using JavaFX only:

    package application;
    
    
    import java.io.File;
    
    import javafx.application.Application;
    import javafx.stage.Stage;
    import javafx.scene.Scene;
    import javafx.scene.control.Button;
    import javafx.scene.image.Image;
    import javafx.scene.layout.Background;
    import javafx.scene.layout.BackgroundImage;
    import javafx.scene.layout.BackgroundPosition;
    import javafx.scene.layout.BackgroundRepeat;
    import javafx.scene.layout.BackgroundSize;
    import javafx.scene.layout.BorderPane;
    
    
            public class OnlyJavaFX extends Application {
        @Override
        public void start(Stage primaryStage) {
            try {
    
                //BorderPane()
                //https://docs.oracle.com/javase/8/javafx/api/javafx/scene/layout/BorderPane.html
                BorderPane borderPane = new BorderPane();
    
    
                //File(String pathname)
                //https://docs.oracle.com/javase/8/docs/api/java/io/File.html
                File file = new File("./src/application/resourcesa/black_clock.png");
    
                Image img = null;
                if(file.exists()) {
                    //Image(InputStream is)
                    //https://docs.oracle.com/javase/8/javafx/api/javafx/scene/image/Image.html
                    img = new Image(file.getAbsoluteFile().toURI().toString());
                } else {
                    //Image(String url)
                    //https://docs.oracle.com/javase/8/javafx/api/javafx/scene/image/Image.html
                    img = new Image("https://raw.githubusercontent.com/4nds/CenteredBackgroundImage/master/Background/src/application/resources/black_clock.png");
                }
    
                //BackgroundSize(double width, double height, boolean widthAsPercentage, boolean heightAsPercentage, boolean contain, boolean cover)
                //https://docs.oracle.com/javase/8/javafx/api/javafx/scene/layout/BackgroundSize.html
                BackgroundSize bgSize = new BackgroundSize(0, 0, false, false, false, true);
    
                //public BackgroundImage(Image image, BackgroundRepeat repeatX, BackgroundRepeat repeatY, BackgroundPosition position, BackgroundSize size)
                //https://docs.oracle.com/javase/8/javafx/api/javafx/scene/layout/BackgroundImage.html
                BackgroundImage bgImg = new BackgroundImage(img,
                        BackgroundRepeat.NO_REPEAT,
                        BackgroundRepeat.NO_REPEAT,
                        BackgroundPosition.CENTER,
                        bgSize);
    
                //Background(BackgroundImage... images)
                //https://docs.oracle.com/javase/8/javafx/api/javafx/scene/layout/Background.html
                Background bg = new Background(bgImg);
    
                //https://docs.oracle.com/javase/8/javafx/api/javafx/scene/layout/Region.html#setBackground-javafx.scene.layout.Background-
                borderPane.setBackground(bg);
    
    
                //Button(String text)
                //https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/Button.html
                Button btn = new Button("Center");
    
                //https://docs.oracle.com/javase/8/javafx/api/javafx/scene/layout/BorderPane.html#setCenter-javafx.scene.Node-
                borderPane.setCenter(btn);
    
    
                Scene scene = new Scene(borderPane, 600, 600);
                primaryStage.setScene(scene);
                primaryStage.show();
    
            } catch(Exception e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    

Currentlly all versions of code (InlineCSS, ExternalCSS and OnlyJavaFX) produces result like this:

sample image

Here is CSS and HTML code for the same example. This is how I want it to look like:

sample image

Edit 1:

I realized that I have posted old versions of my code so I will update that. (The only change in code is that now it will work even without image and CSS file saved on your computer and instead program will get it online.)

Edit 2 after Jose Martinez's answer:

I have tested your code but it doesn't work. (I have even added translation of the background ImageView in direction of Y-axis, in your answer it was only in direction of X-axis, and also I have set the size of borderPane to match size of scene (app window) but it still doesn't work). Here is full code (same as in your answer but with minor fixes that I have already mentioned):

package application;


import java.io.File;

import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;

public class Try1 extends Application {
    @Override
    public void start(Stage primaryStage) {
        try {


            //BorderPane()
            //https://docs.oracle.com/javase/8/javafx/api/javafx/scene/layout/BorderPane.html
            BorderPane borderPane = new BorderPane();

            //jot down the width and height of the scene
            double width = 600;
            double height = 600;

            //File(String pathname)
            //https://docs.oracle.com/javase/8/docs/api/java/io/File.html
            File file = new File("./src/application/resourcesa/black_clock.png");

            Image img = null;
            if(file.exists()) {
                //Image(InputStream is)
                //https://docs.oracle.com/javase/8/javafx/api/javafx/scene/image/Image.html
                img = new Image(file.getAbsoluteFile().toURI().toString());
            } else {
                //Image(String url)
                //https://docs.oracle.com/javase/8/javafx/api/javafx/scene/image/Image.html
                img = new Image("https://raw.githubusercontent.com/4nds/CenteredBackgroundImage/master/Background/src/application/resources/black_clock.png");
            }


            ImageView background = new ImageView(img);

            //..center the background
            double translateX = width/2 - img.getWidth()/2;
            System.out.println(translateX);
            background.setTranslateX(translateX);

            double translateY = height/2 - img.getHeight()/2;
            System.out.println(translateY);
            background.setTranslateY(translateY);

            //Button(String text)
            //https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/Button.html
            Button btn = new Button("Center");

            //https://docs.oracle.com/javase/8/javafx/api/javafx/scene/layout/BorderPane.html#setCenter-javafx.scene.Node-
            borderPane.setCenter(btn);

            //make root with BorderPane and background image
            Group root = new Group (background, borderPane);

            Scene scene = new Scene(root, width, height);        

            primaryStage.setScene(scene);

            //this lines set borderPane's dimensions to be the same as scene
            borderPane.prefHeightProperty().bind(scene.heightProperty());
            borderPane.prefWidthProperty().bind(scene.widthProperty());

            primaryStage.show();

        } catch(Exception e) {
            e.printStackTrace();
        }
    }

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

The problem is that this code works only when you start the program

sample image

and as soon as you resize (app) window it isn't centered anymore.

sample image

1

There are 1 answers

9
Jose Martinez On BEST ANSWER

I would accomplish this by not using BackGroundImage and instead just have another Region where a background ImageView can be contained in. Then translate the background ImageView so that it is centered.

I tweaked your code to do just that. I used a Group as your root Node for your Scene. In that root I put the ImageView of the background and the BorderPane.

        //BorderPane()
        //https://docs.oracle.com/javase/8/javafx/api/javafx/scene/layout/BorderPane.html
        BorderPane borderPane = new BorderPane();

        //jot down the width and height of the scene
        double width = 600;
        double height = 600;

        //Image(InputStream is)
        //https://docs.oracle.com/javase/8/javafx/api/javafx/scene/image/Image.html
        Image img = new Image(file.getAbsoluteFile().toURI().toString());
        ImageView background = new ImageView(img);
        //..center the background
        double translateX = width/2 - img.getWidth()/2;
        background.setTranslateX(translateX);

        //Button(String text)
        //https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/Button.html
        Button btn = new Button("Center");

        //https://docs.oracle.com/javase/8/javafx/api/javafx/scene/layout/BorderPane.html#setCenter-javafx.scene.Node-
        borderPane.setCenter(btn);

        //make root with BorderPane and background image
        Group root = new Group (background, borderPane);

        Scene scene = new Scene(root, width, height);

EDIT: handling resize

1) Move the background ImageView to be a object field. 2) Create method for centering the background ImageView. 3) Create callback to center the background ImageView whenever the window is resized.

public class Try1 extends Application {

    private ImageView background = new ImageView(img);

    private final ChangeListener<Number> windowWidthResized = (ObservableValue<? extends Number> ov, Number t, Number t1) -> {
    windowResized();
};

    private final ChangeListener<Number> windowHeightResized = (ObservableValue<? extends Number> ov, Number t, Number t1) -> {
    windowResized();
};

    private void windowResized(){
        double newHeight = scene.getHeight();
        double newWidth = scene.getWidth();
        centerBackgroundImage(newWidth, newHeight);
    }

    private void centerBackgroundImage(double width, double height) {
            double translateX = width/2 - img.getWidth()/2;
            System.out.println(translateX);
            background.setTranslateX(translateX);

            double translateY = height/2 - img.getHeight()/2;
            System.out.println(translateY);
            background.setTranslateY(translateY);
    }

    @Override
    public void start(Stage primaryStage) {
        try {


            //BorderPane()
            //https://docs.oracle.com/javase/8/javafx/api/javafx/scene/layout/BorderPane.html
            BorderPane borderPane = new BorderPane();

            //jot down the width and height of the scene
            double width = 600;
            double height = 600;

            //File(String pathname)
            //https://docs.oracle.com/javase/8/docs/api/java/io/File.html
            File file = new File("./src/application/resourcesa/black_clock.png");

            Image img = null;
            if(file.exists()) {
                //Image(InputStream is)
                //https://docs.oracle.com/javase/8/javafx/api/javafx/scene/image/Image.html
                img = new Image(file.getAbsoluteFile().toURI().toString());
            } else {
                //Image(String url)
                //https://docs.oracle.com/javase/8/javafx/api/javafx/scene/image/Image.html
                img = new Image("https://raw.githubusercontent.com/4nds/CenteredBackgroundImage/master/Background/src/application/resources/black_clock.png");
            }


            background.setImage(img);

            //..center the background
            centerBackgroundImage(width, height);

            //Button(String text)
            //https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/Button.html
            Button btn = new Button("Center");

            //https://docs.oracle.com/javase/8/javafx/api/javafx/scene/layout/BorderPane.html#setCenter-javafx.scene.Node-
            borderPane.setCenter(btn);

            //make root with BorderPane and background image
            Group root = new Group (background, borderPane);

            Scene scene = new Scene(root, width, height);

            //add callbacks to handle window resize
            scene.heightProperty().addListener(windowResized);
            scene.widthProperty().addListener(windowWidthResized);        

            primaryStage.setScene(scene);

            //this lines set borderPane's dimensions to be the same as scene
            borderPane.prefHeightProperty().bind(scene.heightProperty());
            borderPane.prefWidthProperty().bind(scene.widthProperty());

            primaryStage.show();

        } catch(Exception e) {
            e.printStackTrace();
        }
    }

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