I've been into custom controls with JavaFX recently and was wondering what the best way is to create a button that simply is an image. For instance here on Stack Overflow we have these buttons (I suppose that in reality they are links but I want the same effect in JavaFX) that do not look like buttons at all.
What is the best way of creating something similar in JavaFX? I know you can add images to buttons but is there then also a way of completely removing the background (I suspect there is)?
I use a custom ImageButton class in my projects. This is similar to fabian's approach, but uses an ImageView. It is also a bit simpler to implement, in my view.
public class ImageButton extends Button {
private final String STYLE_NORMAL = "-fx-background-color: transparent; -fx-padding: 2, 2, 2, 2;";
private final String STYLE_PRESSED = "-fx-background-color: transparent; -fx-padding: 3 1 1 3;";
public ImageButton(Image originalImage, double h, double w) {
ImageView image = new ImageView(originalImage);
image.setFitHeight(h);
image.setFitHeight(w);
image.setPreserveRatio(true);
setGraphic(image);
setStyle(STYLE_NORMAL);
setOnMousePressed(event -> setStyle(STYLE_PRESSED));
setOnMouseReleased(event -> setStyle(STYLE_NORMAL));
}
}
Then you just need to pass it the Image and dimensions:
ImageButton newButton = new ImageButton(new Image("icon.png"), 16, 16);
Just set the graphic property of the button accordingly.
Since stackoverflow uses svg paths, the following example uses SVGPath, but it could easily be changed to an ImageView and the scaling could replaced with setting fitWidth/fitHeight. If you do want to use ImageView though, you should be aware of the fact that ImageView does not provide a fill property and you need to work with opacity or with different images instead.
public static Button createIconButton(String svg) {
SVGPath path = new SVGPath();
path.setContent(svg);
Bounds bounds = path.getBoundsInLocal();
// scale to size 20x20 (max)
double scaleFactor = 20 / Math.max(bounds.getWidth(), bounds.getHeight());
path.setScaleX(scaleFactor);
path.setScaleY(scaleFactor);
path.getStyleClass().add("button-icon");
Button button = new Button();
button.setPickOnBounds(true); // make sure transparent parts of the button register clicks too
button.setGraphic(path);
button.setAlignment(Pos.CENTER);
button.getStyleClass().add("icon-button");
return button;
}
#Override
public void start(Stage primaryStage) {
// the following svg paths were copied from the stackoverflow website
HBox root = new HBox(
createIconButton("M15.19 1H4.63c-.85 0-1.6.54-1.85 1.35L0 10.79V15c0 1.1.9 2 2 2h16a2 2 0 0 0 2-2v-4.21l-2.87-8.44A2 2 0 0 0 15.19 1zm-.28 10l-2 2h-6l-2-2H1.96L4.4 3.68A1 1 0 0 1 5.35 3h9.12a1 1 0 0 1 .95.68L17.86 11h-2.95z"),
createIconButton("M15 2V1H3v1H0v4c0 1.6 1.4 3 3 3v1c.4 1.5 3 2.6 5 3v2H5s-1 1.5-1 2h10c0-.4-1-2-1-2h-3v-2c2-.4 4.6-1.5 5-3V9c1.6-.2 3-1.4 3-3V2h-3zM3 7c-.5 0-1-.5-1-1V4h1v3zm8.4 2.5L9 8 6.6 9.4l1-2.7L5 5h3l1-2.7L10 5h2.8l-2.3 1.8 1 2.7h-.1zM16 6c0 .5-.5 1-1 1V4h1v2z"));
Scene scene = new Scene(root);
scene.getStylesheets().add("style.css");
primaryStage.setScene(scene);
primaryStage.show();
}
style.css
/* set default fill of svg path */
.icon-button .button-icon {
-fx-fill: #888888;
}
/* set default fill of svg path */
.icon-button:focused {
-fx-background-color: lightblue;
-fx-background-radius: 0;
}
/* remove default button style & set size */
.icon-button {
-fx-background-color: transparent, transparent, transparent, transparent, transparent;
-fx-pref-height: 30;
-fx-pref-width: 30;
-fx-min-height: 30;
-fx-min-width: 30;
-fx-max-height: 30;
-fx-max-width: 30;
}
/* modify svg path fill for hovered/pressed button */
.icon-button:pressed .button-icon,
.icon-button:hover .button-icon {
-fx-fill: #444444;
}
Despite this being an old post, it still might be nice for those using a SceneBuilder to know that you can do this with a Button and an ImageView:
(I am using Gluon SceneBuilder)
Button Setup:
Under 'Graphic' set the Display to 'GRAPHIC_ONLY'
Then for the in-line CSS use: -fx-background-colour: transparent;
ImageView:
Set the image to whatever you want
In the 'Hierarchy' pick up and drop the ImageView onto the button
What it looks like when done:
Transparent Button ~ with only Image shown
Related
I have created a TableView in JavaFX which dynamically loads VBoxs containing a list of CheckBoxs into in row. This is shown in the figure below:
As you can see in the figure, the labels next to the checkboxes do not line up with the checkbox its self.
I use the following to dynamically create the VBoxes:
private VBox getVBox(Map<Integer, String> item, Set<Integer> completed, String id) {
VBox box = new VBox();
box.setSpacing(0);
box.setPadding(new Insets(3,3,3,3));
for(Integer i : item.keySet()) {
CheckBox checkbox = new CheckBox(item.get(i));
checkbox.setId("item"+id+"-" + i.intValue());
if(completed.contains(i)) {
checkbox.setSelected(true);
}
checkbox.setPrefHeight(21);
box.getChildren().add(checkbox);
}
return box;
}
Even if I remove the setSpacing setting and setPadding setting and setPrefHeight setting, the issue remains unchanged.
The only CSS which I have applied to this table which is not by default is as follows:
.table-view{
-fx-background-color: transparent;
}
.table-view:focused{
-fx-background-color: transparent;
}
.table-row-cell:odd{
-fx-background-color: #E0E0E0;
-fx-background-insets: 0, 0 0 1 0;
-fx-padding: 0.0em;
}
.table-row-cell:even{
-fx-background-color: #BBD9E0;
-fx-background-insets: 0, 0 0 1 0;
-fx-padding: 0.0em;
}
My Question:
How can I make the checkbox labels line up with the checkboxes in a vertical manner? Can this not be done with a VBox?
Thank you for the assistance.
I suggest you use the ScenicView tool and check what element should be set to get vertical alignment
Download it add as library and put in code below lines:
ScenicView.show(current scene instance);
I have a toggle button in my program that starts/stops a script. I would like for this button to be green and say "START" when the button is not selected, and red and say "STOP" when it is selected. More importantly, I would like the unselected hover color to be a slightly darker version of the original green, and the selected hover color to be a slightly darker version of the red color. My current CSS for this button looks like this:
#startStopButton {
-fx-border-color:#d4d4d4;
-fx-background-color:#85eca5;
-fx-background-image: url("startButton.png");
-fx-background-size: 50px;
-fx-background-repeat: no-repeat;
-fx-background-position: 80% 50%;
-fx-alignment: CENTER_LEFT;
-fx-effect: dropshadow(three-pass-box, #e7e7e7, 15, 0, 0, 0);
}
#startStopButton:hover {
-fx-background-color:#80dc9c;
}
#startStopButton:selected{
-fx-background-color: #ff6060;
-fx-text:"STOP";
}
#startStopButton:selected:focused{
-fx-background-color: #ff6060;
-fx-text:"STOP";
}
Currently, this will work fine, except for when the button turns red. In this case, there is no hover effect. Within my FXML controller, there is a function that is activated every time this button is clicked:
private void startStopClick()
{
if(startStopButton.isSelected())
{
startStopButton.setText(" STOP");
// startStopButton.setStyle()
}
else {
startStopButton.setText(" START");
}
}
Is there any way to 1) set the button text within CSS so that I can leave that out of my controller?
2) Get the current toggle button state in CSS, so that I can have multiple hover effects. For example, something like this:
#startStopButton:unselected{
-fx-background-color: #ff6060;
-fx-text:"STOP";
}
If there is no way to do this in CSS, can I set the hover styles in the Java code in the FXML controller?
CSS properties are only available for the look of nodes. With a few exceptions the basic JavaFX nodes don't allow you to specify content via CSS. The text property of buttons is no exception; it cannot be set using CSS.
As for the colors: The rules occuring last override values assigned by rules with the same precedence occuring before them. This means the background color assigned by the rules for #startStopButton:selected and #startStopButton:selected:focused always override the color #startStopButton:hover assigns.
Since in both cases you want a darker color when hovering, the derive function and a lookedup color may work for you.
Example
#Override
public void start(Stage primaryStage) {
ToggleButton btn = new ToggleButton();
btn.getStyleClass().add("start-stop");
btn.textProperty().bind(Bindings.when(btn.selectedProperty()).then(" STOP").otherwise(" START"));
Pane p = new Pane(btn);
Scene scene = new Scene(p);
scene.getStylesheets().add("style.css");
primaryStage.setScene(scene);
primaryStage.show();
}
style.css
.start-stop.toggle-button {
base-color: #85eca5;
-fx-background-color: base-color;
}
.start-stop.toggle-button:selected {
base-color: #ff6060;
}
.start-stop.toggle-button:hover {
-fx-background-color: derive(base-color, -20%);
}
If you cannot use derive since you need to specify different colors for all 4 states you could still rely on looked-up colors to avoid relying on the rule ordering:
.start-stop.toggle-button {
unselected-color: blue;
selected-color: yellow;
-fx-background-color: unselected-color;
}
.start-stop.toggle-button:hover {
unselected-color: red;
selected-color: green;
}
.start-stop.toggle-button:selected {
-fx-background-color: selected-color;
}
I have initialized a decorator into my project, which was at first fine and nice looking. But now I have encountered several problems, I did not figure out how to change the border color (currently it is black). I also want to remove the button which resizes the window.
Here is the code which touches the decorator:
public void start(Stage primaryStage){
JFXDecorator decorator = new JFXDecorator(primaryStage, gridContainer);
decorator.setCustomMaximize(false);
decorator.setText("Window Title");
decorator.setStyle("-fx-background-color: #ffffff; -fx-font-family:'Franklin Gothic Medium'");
Scene scene= new Scene(decorator, 350, 500, Color.BEIGE);
}
A couple of minutes later I came up with a simple but adequate solution. I have created a decorator class in my css file.
Here is the code I have added into my css file:
.jfx-decorator{
-fx-decorator-color: white;
}
.jfx-decorator .jfx-decorator-buttons-container{
-fx-background-color: -fx-decorator-color;
}
.jfx-decorator .resize-border{
-fx-border-color: -fx-decorator-color;
-fx-border-width: 0 4 4 4;
}
I am struggling and will no doubt have to buy a manual to understand JavaFX CSS, or the JavaFX CSS Reference Guide...
But what I want to do is make a 1 pixel border around some of my nodes, such as a TableView or ScrollPane on one side, and a GridPane or ScrollPane on the other side of a Scene I'm working on. I say "or", because I'll take either one. Ha ha!
(And regardless of whether it's filled up with controls or not)
Cheers.
Here's a test sample for demoing some ways to make borders in CSS:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class TableViewBorderTest extends Application {
#Override
public void start(Stage primaryStage) {
HBox root = new HBox(5);
TableView<String> table = new TableView<>();
table.getColumns().add(new TableColumn<String, String>("Data"));
ScrollPane scroller = new ScrollPane();
scroller.setMinWidth(200);
GridPane grid = new GridPane();
grid.setMinWidth(200);
grid.getStyleClass().add("grid");
root.getChildren().addAll(table, scroller, grid);
// padding so we can easily see borders:
root.setPadding(new Insets(10));
Scene scene = new Scene(root);
scene.getStylesheets().add("border-table.css");
primaryStage.setScene(scene);
primaryStage.show();
// remove focus from tables
root.requestFocus();
}
public static void main(String[] args) {
launch(args);
}
}
You can run this with just an empty border-table.css file. The first thing to notice is that the table view and scroll pane already have a 1-pixel border in a medium gray (slightly darker than the default background):
This border is defined (see how later) in the default stylesheet, modena.css, and is set to a "looked-up color" called -fx-box-border. Just for demo purposes, to make it easier to see the border, you can reassign this color with the following in border-table.css:
.root {
-fx-box-border: red ;
}
This gives
Notice that the table header and column headers use the same color as a border. If you compare to the first image, you can probably see the borders more clearly in it too.
To replace the border in the table view and scroll pane, you can define borders in the css file. The simplest, but not necessarily the best, way is to define a -fx-border-color. Replace the border-table.css with the following:
.table-view, .scroll-pane {
-fx-border-color: green ;
}
The default value of -fx-border-width is 1, so this gives a one-pixel green border:
For the GridPane, note that it has no default border and also has no style class (see CSS docs). In the Java code, I defined a style class for it:
grid.getStyleClass().add("grid");
so we can add the same border just by adding this style class to the selector:
.table-view, .scroll-pane, .grid {
-fx-border-color: green ;
}
It's interesting to note that the default stylesheet, modena.css doesn't use -fx-border-... properties at all. Instead, it creates borders by creating two (or more) "nested backgrounds". For example, it has:
.scroll-pane,
.split-pane,
.list-view,
.tree-view,
.table-view,
.tree-table-view,
.html-editor {
-fx-background-color: -fx-box-border, -fx-control-inner-background;
-fx-background-insets: 0, 1;
-fx-padding: 1;
}
This defines, for TableView, ScrollPane, and other similar controls, two background colors. The first (so painted first, i.e. underneath) is a solid background fill in the looked-up color -fx-box-border, and the second (painted on top) is a solid background fill in the looked-up color -fx-control-inner-background. The first background fill has 0 insets, and the second has a 1-pixel inset, meaning that the bottom background fill will be visible for 1 pixel width around the edge of the control. (The padding ensures nothing is placed over this effective 1-pixel border.)
I haven't tested this at all, but it's claimed that the nested background approach is more efficient than drawing borders (I guess the native graphics is vey fast at rectangular background fills).
So you could use the same approach and replace border-table.css with
.table-view, .scroll-pane {
-fx-background-color: blue, -fx-control-inner-background ;
-fx-background-insets: 0, 1 ;
}
.grid {
-fx-background-color: blue, -fx-background ;
-fx-background-insets: 0, 1 ;
-fx-padding : 1 ;
}
And you could even introduce a looked-up color to make it easier to modify the style of the app:
.root {
-my-border: blue ;
}
.table-view, .scroll-pane {
-fx-background-color: -my-border, -fx-control-inner-background ;
-fx-background-insets: 0, 1 ;
}
.grid {
-fx-background-color: -my-border, -fx-background ;
-fx-background-insets: 0, 1 ;
-fx-padding : 1 ;
}
(this has exactly the same effect as the previous, but there is just one place to change the color definition instead of two).
Note these last two versions override the default focus border, which is implemented in the default style sheet by defining a different set of background colors when the controls are focused. You can restore these with:
.root {
-my-border: blue ;
}
.table-view, .scroll-pane {
-fx-background-color: -my-border, -fx-control-inner-background ;
-fx-background-insets: 0, 1 ;
}
.table-view:focused, .scroll-pane:focused {
-fx-background-color: -fx-faint-focus-color, -fx-focus-color, -fx-control-inner-background;
-fx-background-insets: -1.4, -0.3, 1;
-fx-background-radius: 2, 0, 0;
}
.grid {
-fx-background-color: -my-border, -fx-background ;
-fx-background-insets: 0, 1 ;
-fx-padding : 1 ;
}
which references two more looked-up colors, -fx-faint-focus-color and -fx-focus-color (the first is just a partially-transparent version of the second); of course you could redefine these for your own focus colors if you chose.
I require a tabbed pane with tabs on the left side, the tab text/graphic needs to be horizontal
I did this on Scenebuilder few months back.
However when I add additional tabs via Java code, the tabs is on the left side but the graphic text is vertical unlike the tabs created using Scene builder.
In the attached image first two tabs are created through Scenebuilder and they are in the correct orientation, the third one was dynamically added using Java code.
Tab studentAdmission = new Tab();
studentAdmission.setContent((Parent)new FXMLLoader(getClass().getResource("Customer_View.fxml")).load());
studentAdmission.setGraphic(new Label("Student Admission"));
mainTab.getTabs().add(studentAdmission);
Could some one advise why this tab doesn't rotate as the other one.
Just figured out after posting the question that you need to add a StackPane containing a group containing a label to achieve this.
Tab studentAdmission = new Tab();
studentAdmission.setContent((Parent)new FXMLLoader(getClass().getResource("Customer_View.fxml")).load());
Label l = new Label("Student Admission");
l.setRotate(90);
StackPane stp = new StackPane(new Group(l));
studentAdmission.setGraphic(stp);
mainTab.getTabs().add(studentAdmission);
// Firstly
tabPane.setSide(Side.LEFT);
tabPane.setRotateGraphic(true);
Label l = new Label("Titel Tab1");
l.setRotate(90);
StackPane stp = new StackPane(new Group(l));
stp.setRotate(90);
tab1.setGraphic(stp);
l = new Label("Titel Tab2");
l.setRotate(90);
stp = new StackPane(new Group(l));
stp.setRotate(90);
tab2.setGraphic(stp);
tabPane.setTabMinHeight(100);
tabPane.setTabMaxHeight(100);
I needed to get something similar to this, but with left-aligned titles. I ended up with this solution.
TabPane setup:
TabPane tabPane = new TabPane();
tabPane.setSide(Side.LEFT);
tabPane.setRotateGraphic(true);
tabPane.setTabMinHeight(200); // Determines tab width. I know, its odd.
tabPane.setTabMaxHeight(200);
tabPane.getStyleClass().add("horizontal-tab-pane");
Tab setup:
Tab tab = new Tab(title, content);
tab.setClosable(false);
tab.setGraphic(graphic); // Graphic required. If you don't want one, use an empty label.
Platform.runLater(() -> {
// Get the "tab-container" node. This is what we want to rotate/shift for easy left-alignment.
// You can omit the last "getParent()" with a few tweaks for centered labels
Parent tabContainer = tab.getGraphic().getParent().getParent();
tabContainer.setRotate(90);
// By default the display will originate from the center.
// Applying a negative Y transformation will move it left.
// Should be the 'TabMinHeight/2'
tabContainer.setTranslateY(-100);
});
And the css:
.horizontal-tab-pane *.tab {
/* Determines the tab height */
-fx-pref-width: 60px;
/* Everything else is just aesthetics */
-fx-padding: 20px;
-fx-background-insets: 2 -1 -1 -1;
-fx-border-width: 1 0 1 1;
-fx-border-color: rgb(55, 55, 56) black black black;
}
.horizontal-tab-pane *.tab:selected {
-fx-background-color: rgb(45, 45, 46);
-fx-border-color: rgb(75, 125, 200) black rgb(45, 45, 46) black;
}