Adding an image to a button can be a great way to improve the user experience – increasing recognisability of button functionality. It also fits with user expectations for touch devices, which are often designed for users with different first languages.

For one reason or other, JavaFX doesn’t make this as easy as it should, although with a few simple steps you can add and position an image.

An image can be added to a button in JavaFX by specifying a button graphic, which should be supplied as an ImageView object. The position of the image can be specified by invoking setContentDisplay(), specifying the position of the image using the JavaFX ContentDisplay enum.

What’s in this article

What sort of functionality you’ll achieve

JavaFX gives developers a lot of functionality with both adding and positioning buttons in JavaFX.

Here are a few of the simplest layouts possible, which will be covered in this article:

A simple JavaFX button with an image graphicA simple JavaFX button with an image graphicA simple JavaFX button with an image graphic
A simple JavaFX button with an image graphicA simple JavaFX button with an image graphicA simple JavaFX button with an image graphic
How you’ll do it.

Plus, I’ll show you how to achieve these using Java-only, FXML-only and CSS-only approaches. Read below for more.

How to add an image to a button

The functionality to add an image to a button is actually built into JavaFX! But JavaFX being predictable it’s just not listed as an ‘image’… That’s mostly because the functionality is intended to be more flexible – basically allowing any Node to be added as a ‘graphic’, but the overall effect can be a little confusing.

To set an image on a button, we just need to create an ImageView and add it to the Button using the setGraphic() method of the button object. The usual place to do this is in the initialize() method of the scene’s controller.

import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.image.ImageView;

public class Controller {

    @FXML
    Button button;

    public void initialize (){
        ImageView imageView = new ImageView(getClass().getResource("/com/edencoding/images/EdenCodingIcon.png").toExternalForm());
        button.setGraphic(imageView);
    }
}

Obviously in this example the Java code uses the @FXML annotation you’ll need to reference a Button with the fx:id of button inside the FXML file.

FXML:

If you want to be able to define the layout entirely in FXML, it’s also possible to set the graphic of a button using the <graphic> element within the <Button> object.

Just make sure to specify the <Image> object within the <ImageView> created. That’s usually where it trips me up.

<Button>
    <graphic>
        <ImageView>
            <Image url="@../images/EdenCodingIcon.png"/>
        </ImageView>
    </graphic>
</Button>

The default graphic position when one isn’t specified is on the left hand side of the text. That gives the button the following general appearance:

A simple JavaFX button with an image graphic

If that’s not what you want, we can use the JavaFX ContentDisplay enum to set the image’s position, which is what we’ll do next.

CSS:

You can complete all of this in CSS too, by referencing the -fx-graphic CSS property, which takes a URL (use the JavaFX CSS url() function to define it).

Resources note:

Two important things to note:

  1. The url() CSS function require you to specify a URL as a string. That means you can’t create an Image object like you can in Java code.
  2. CSS is structured in JavaFX such that you must navigate locally as you define your URL. Check out this post on how to navigate between resources if you’re stuck on using the url() function effectively in JavaFX’s CSS.

Here’s how the code looks. I actually think it’s more concise in a lot of cases than setting it in the Java or FXML code.

.button{
    -fx-graphic: url(../images/EdenCodingIcon.png);
}

Here, the .button type is specified, which will affect every button in the scene. often you’ll want to create a style class or id for your button so you can reference it directly.

Positioning an image inside a button

In JavaFX, the position of a button image can be set using the setContentDisplay() method, passing in the ContentDisplay enum value that represents the desired position of the image. The available ContentDisplay values are TOP, BOTTOM, LEFT, RIGHT and CENTER

In addition to the positional values, the ContentDisplay enum also allows for the values TEXT_ONLY and GRAPHIC_ONLY, which will produce a text and image-only button respectively.

Java/FXML valueCSS valueVisual Appearance
TOPtopA simple JavaFX button with an image graphic
BOTTOMbottomA simple JavaFX button with an image graphic
LEFTleftA simple JavaFX button with an image graphic
RIGHTrightA simple JavaFX button with an image graphic
CENTERcenterA simple JavaFX button with an image graphic
TEXT_ONLYtext-only
GRAPHIC_ONLYgraphic-onlyA simple JavaFX button with an image graphic

FXML:

To specify the layout using only FXML code, you’ll need to define the contentDisplay as an attribute of the button object itself. You can do this in one line, or by specifying nested enum objects in your code.

<Button fx:id="button" text="Text" contentDisplay="CENTER">
    <graphic>
        <ImageView preserveRatio="true" fitHeight="${controller.buttonWidth}">
            <Image url="@../images/EdenCodingIcon.png"/>
        </ImageView>
    </graphic>
</Button>

If you want to make the content display more explicit, you can create a <contentDisplay> element, which confusingly has to contain a nested <ContentDisplay> object that references the enum itself.

<Button text="Button">
    <contentDisplay>
        <ContentDisplay fx:value="GRAPHIC_ONLY"/>
    </contentDisplay>
    <graphic>
        <ImageView>
            <Image url="@../images/EdenCodingIcon.png"/>
        </ImageView>
    </graphic>
</Button>

This can look a little clumsy, because you end up defining a <contentDisplay> element with a <ContentDisplay> enum inside it, but it works fine just the same.

CSS:

To achieve the exact same thing in CSS, simply specify the -fx-content-display property with the required lower case value (see the table above for allowed values!)

.button{
    -fx-content-display: left;
}

Here, the .button type is specified, which will affect every button in the scene. often you’ll want to create a style class or id for your button so you can reference it directly.

Resizing an image with button size

Lastly, if you’re looking at creating a responsive JavaFX application you may need to alter the size of the button image as your app resizes too.

Luckily, it’s really easy to accomplish with a little Java code.

public class Controller {
    @FXML Button button;

    public void initialize() {
        ImageView imageView = new ImageView(getClass().getResource("/com/edencoding/images/EdenCodingIcon.png").toExternalForm());
        button.setGraphic(imageView);
        button.setContentDisplay(ContentDisplay.TOP);

        imageView.fitWidthProperty().bind(button.widthProperty().divide(10));
        imageView.setPreserveRatio(true);

        //Important otherwise button will wrap to text + graphic size (no resizing on scaling).
        button.setMaxWidth(Double.MAX_VALUE);    
    }
}

The important lines here are binding the fitWidth property of the ImageView to the button’s read-only width property. Now, when the button size changes, it automatically updates the size of it’s graphic.

Setting the maxWidth of the button is also pretty important. You can set it to any number you want (you may need a maximum size for your button). However, if you don’t set it at all, the button will wrap to the size of the text and graphic and your image will never have a chance to resize.

FXML:

As with everything JavaFX, you can also do this (at least partly) in FXML. By using the partial support for Expression Binding in FXML, you can perform that binding inside your FXML file.

<Button fx:id="button" text="Text" maxWidth="Infinity">
    <graphic>
        <ImageView preserveRatio="true" fitHeight="${controller.buttonWidth}">
            <Image url="@../images/EdenCodingIcon.png"/>
        </ImageView>
    </graphic>
</Button>

Unfortunately, complex bindings (like numeric division), aren’t currently supported. However, what we can do is bind the fitHeight property of the ImageView to a property we create ourselves inside the controller.

public class Controller {
    @FXML Button button;

    //Variable and methods to establish a property
    private final IntegerProperty buttonWidth = new SimpleIntegerProperty(100);
    public IntegerProperty buttonWidthProperty() {
        return buttonWidth;
    }
    public int getButtonWidth() {
        return buttonWidth.get();
    }

    //Code to calculate the width of our property based on the width of the button
    public void initialize() {
        buttonWidth.bind(button.widthProperty().divide(10));
    }
}

Honestly, I think this has the benefit of being slightly more elegant, but to be honest, you generate an awful lot of redundant code setting up the property.

Conclusion

Adding an image to a button is supported and relatively simple, but it can be confusing because of the constantly changing names.

Images are really graphics, until you’re trying to position them and then they’re content. Ugh! Here’s a quick summary to get you on the road:

PurposeNamingMethod
Creating an image for your buttonImageViewnew ImageView()
Add an image to the buttonGraphicsetGraphic()
Position button imageContentsetContentDisplay()