I would like to create a custom button, that has two states pressed or not, like a toggle button. I have got two images to do this (pressed and not pressed), so how can i create the button and display it with my images ? The button must take the size of the image.
I am not using FXML.
Thank you for helping.
4 Answers
There are a few different ways to accomplish this, I'll outline my favourites.
This question asks about toggle behaviour so the answer focuses on that. If you don't need the toggle behaviour, then just use a standard Button
, which can be styled the same as the ToggleButton
referenced in this answer.
See also:
Use a ToggleButton and apply a custom style to it. I suggest this because your required control is "like a toggle button" but just looks different from the default toggle button styling.
My preferred method is to define a graphic for the button in css:
.toggle-button {
-fx-graphic: url('http://icons.iconarchive.com/icons/aha-soft/desktop-buffet/128/Pizza-icon.png');
}
.toggle-button:selected {
-fx-graphic: url('http://icons.iconarchive.com/icons/aha-soft/desktop-buffet/128/Piece-of-cake-icon.png');
}
OR use the attached css to define a background image.
// file imagetogglebutton.css deployed in the same package as ToggleButtonImage.class
.toggle-button {
-fx-background-image: url('http://icons.iconarchive.com/icons/aha-soft/desktop-buffet/128/Pizza-icon.png');
-fx-background-repeat: no-repeat;
-fx-background-position: center;
}
.toggle-button:selected {
-fx-background-image: url('http://icons.iconarchive.com/icons/aha-soft/desktop-buffet/128/Piece-of-cake-icon.png');
}
I prefer the -fx-graphic specification over the -fx-background-* specifications as the rules for styling background images are tricky and setting the background does not automatically size the button to the image, whereas setting the graphic does.
And some sample code:
NOTE: the StackPaneBuilder
used in this example has been deprecated and removed from JavaFX, replace that code with new StackPane()
and call methods on the resultant stack pane to set stack pane properties rather than using the builder.
import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.StackPaneBuilder;
import javafx.stage.Stage;
public class ToggleButtonImage extends Application {
public static void main(String[] args) throws Exception { launch(args); }
@Override public void start(final Stage stage) throws Exception {
final ToggleButton toggle = new ToggleButton();
toggle.getStylesheets().add(this.getClass().getResource(
"imagetogglebutton.css"
).toExternalForm());
toggle.setMinSize(148, 148); toggle.setMaxSize(148, 148);
stage.setScene(new Scene(
StackPaneBuilder.create()
.children(toggle)
.style("-fx-padding:10; -fx-background-color: cornsilk;")
.build()
));
stage.show();
}
}
Some advantages of doing this are:
- You get the default toggle button behavior and don't have to re-implement it yourself by adding your own focus styling, mouse and key handlers etc.
- If your app gets ported to different platform such as a mobile device, it will work out of the box responding to touch events rather than mouse events, etc.
- Your styling is separated from your application logic so it is easier to restyle your application.
An alternate is to not use css and still use a ToggleButton, but set the image graphic in code:
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.scene.*;
import javafx.scene.control.ToggleButton;
import javafx.scene.image.*;
import javafx.scene.layout.StackPaneBuilder;
import javafx.stage.Stage;
public class ToggleButtonImageViaGraphic extends Application {
public static void main(String[] args) throws Exception { launch(args); }
@Override public void start(final Stage stage) throws Exception {
final ToggleButton toggle = new ToggleButton();
final Image unselected = new Image(
"http://icons.iconarchive.com/icons/aha-soft/desktop-buffet/128/Pizza-icon.png"
);
final Image selected = new Image(
"http://icons.iconarchive.com/icons/aha-soft/desktop-buffet/128/Piece-of-cake-icon.png"
);
final ImageView toggleImage = new ImageView();
toggle.setGraphic(toggleImage);
toggleImage.imageProperty().bind(Bindings
.when(toggle.selectedProperty())
.then(selected)
.otherwise(unselected)
);
stage.setScene(new Scene(
StackPaneBuilder.create()
.children(toggle)
.style("-fx-padding:10; -fx-background-color: cornsilk;")
.build()
));
stage.show();
}
}
The code based approach has the advantage that you don't have to use css if you are unfamilar with it.
For best performance and ease of porting to unsigned applet and webstart sandboxes, bundle the images with your app and reference them by relative path urls rather than downloading them off the net.

- 150,031
- 14
- 366
- 406
-
Wow! What can i say ? That is a really great answer, thanks a lot. I think i will go for the first solution, i'll apply the size to my button in the code, it seems to be common in JavaFX application unlike Swing. – jerome May 10 '12 at 06:58
-
2I had to add -fx-background-color: transparent; in the css to only have my button filled with my image. – jerome May 14 '12 at 17:05
-
It's a good answer but demonstrates a weakness in JavaFX. This kind of requirement is messy and unwarranted in the 21st century. – Andrew S Mar 22 '16 at 19:41
-
1What is an example of a stronger solution you would prefer Andrew? – jewelsea Mar 22 '16 at 20:50
You just need to create your own class inherited from parent. Place an ImageView on that, and on the mousedown and mouse up events just change the images of the ImageView.
public class ImageButton extends Parent {
private static final Image NORMAL_IMAGE = ...;
private static final Image PRESSED_IMAGE = ...;
private final ImageView iv;
public ImageButton() {
this.iv = new ImageView(NORMAL_IMAGE);
this.getChildren().add(this.iv);
this.iv.setOnMousePressed(new EventHandler<MouseEvent>() {
public void handle(MouseEvent evt) {
iv.setImage(PRESSED_IMAGE);
}
});
// TODO other event handlers like mouse up
}
}

- 197
- 3
A combination of previous 2 answers did the trick. Thanks. A new class which inherits from Button. Note: updateImages() should be called before showing the button.
import javafx.event.EventHandler;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
public class ImageButton extends Button {
public void updateImages(final Image selected, final Image unselected) {
final ImageView iv = new ImageView(selected);
this.getChildren().add(iv);
iv.setOnMousePressed(new EventHandler<MouseEvent>() {
public void handle(MouseEvent evt) {
iv.setImage(unselected);
}
});
iv.setOnMouseReleased(new EventHandler<MouseEvent>() {
public void handle(MouseEvent evt) {
iv.setImage(selected);
}
});
super.setGraphic(iv);
}
}

- 191
- 6
For some reason, when I have many of these on a scene (think toolbars), using Buttons always proved to be "sluggish" in their handling of image swaps during clicks, and re-sizing on the fly was extremely slow and would even sometimes cause the Java virtual machine itself to dump out.
Using the ImageView objects themselves has always proven to be extremely responsive, no matter how many I have on a Scene. They swap out the images instantly, they can be resized on the fly with instantaneous response and they feel very natural to use, unlike the Buttons which tend to feel ... 'resistive' in a way.
Here is how I do it:
public void buildImageButtons() {
double iconSize = 50;
Image unSelectedOne = new Image(Main.class.getResourceAsStream("UnselectedImageOne.png"));
Image selectedOne = new Image(Main.class.getResourceAsStream("SelectedImageOne.png"));
Image unSelectedTwo = new Image(Main.class.getResourceAsStream("UnselectedImageTwo.png"));
Image selectedTwo = new Image(Main.class.getResourceAsStream("SelectedImageTwo.png"));
ImageView ivOne = newImageView(unSelectedOne, selectedOne, iconSize);
ImageView ivTwo = newImageView(unSelectedTwo, selectedTwo, iconSize);
ivOne.setOnMouseClicked(e -> methodOne());
ivTwo.setOnMouseClicked(e -> methodTwo());
}
private ImageView newImageView(Image unselected, Image selected, double iconSize) {
ImageView iv = new ImageView(unselected);
iv.setPreserveRatio(true);
iv.setFitWidth(iconSize);
iv.setOnMousePressed(e->{
iv.setImage(selected);
});
iv.setOnMouseReleased(e->{
iv.setImage(unselected);
});
return iv;
}
private void methodOne() {
System.out.println("Image One Clicked");
}
private void methodTwo() {
System.out.println("Image Two Clicked");
}

- 2,360
- 1
- 16
- 29