All posts in the JavaFX series:
- JavaFX Tutorial: Getting Started
- JavaFX Tutorial: Hello world!
- JavaFX Tutorial: FXML and SceneBuilder
- JavaFX Tutorial: Basic Layouts
- JavaFX Tutorial: Advanced Layouts
- JavaFX Tutorial: CSS styling
- JavaFX Weaver: Integrating JavaFX and Spring Boot Applications
Separation of visual elements
In a previous article on FXML, we learned how JavaFX provides a clear separation of tasks by splitting UI code in two. Components and their properties are declared in the FXML file, and the interaction logic is clearly allocated to the controller.
In addition, there is a third part, the FXML language, which controls only the components of your application, their properties and how they are embedded in each other. It does not define the visual elements of the component, namely: fonts, colors, backgrounds, indents. In general, you can achieve this in FXML, but you should not. Instead, visual elements should be clearly defined in CSS style sheets.
Thus, your design becomes independent and can be easily replaced or changed without affecting the rest of the application. You can even simply implement several themes that can be switched at the request of the user.
CSS
You are probably familiar with the CSS (Cascading Style Sheets) used to style HTML pages on the web. A similar approach is implemented in JavaFX, although JavaFX uses a set of its own custom properties.
Let's look at an example:
.button { -fx-font-size: 15px; }
Two main concepts are used here. The first is the .button selector. It determines which components the style should apply to. In this example, the style is applied to all buttons.
The second part is the actual properties of the style, which will be applied to all components that match our selector. Properties are everything inside curly braces.
Each property has a specific meaning. In our example, there is the -fx-font-size property, which determines how large the text will be. In the example, the value is 15px , but this value can be any other.
To summarize - we created a rule that states that all buttons everywhere should have text of 15 pixels.
Selectors
Now let's take a closer look at how selectors work in JavaFX. This happens almost the same as in regular CSS.
Class
A class in CSS represents several similar elements. For example, buttons or checkboxes. The selector, which should be applied to all elements of the same class, begins with a dot ".", Followed immediately by the name of the class. A class naming convention is to separate individual words with the "-" character. The following selector applies to all elements with the label class.
.label { // Some properties }
Built-in classes
The good news is that all JavaFX built-in components (such as Label or Button) already have a predefined class. If you want to customize the style of all labels in your application, you do not need to add any custom classes for each of your labels. Each label has a label class by default.
It is easy to determine the class name from the component:
- Take the name of the Java component class - for example. Label
- Make the name lowercase
- If it consists of several words, separate them with the "-" symbol
Some examples:
- Label β Label
- CheckBox β check-box
When using classes such as selectors, be sure to add ".". This means that the selector for the label class is .label .
Custom classes
If the built-in classes are not enough, you can add your own classes to your components. You can use several classes separated by comma:
<Label styleClass="my-label,other-class">I am a simple label</Label>
Or in Java:
Label label = new Label("I am a simple label"); label.getStyleClass().addAll("my-label", "other-class");
Adding classes in this way does not remove the component class by default (in this case, label ).
There is one special class called root . It is the root component of your scene. You can use it to style everything inside your scene (for example, set a global font). This is similar to using the body tag selector in HTML.
ID
Another way to select components in CSS is to use a component identifier (ID). It is a unique identifier for a component. Unlike classes that can be assigned to multiple components, the identifier must be unique in the scene.
Whereas, the symbol "." Is used to indicate the class. in front of the name in their selectors, identifiers are marked with the symbol "#".
#my-component { ... }
In FXML, you can use fx: id to set the CSS identifier of a component.
<Label fx:id="foo">I am a simple label</Label>
However, there is one caveat. The same identifier is used to refer to the component object declared in your controller with the same name. Since the identifier and the field name in the controller must match, fx: id must take into account the Java naming restriction for field names. Although the CSS naming convention defines individual words separated by a "-" character, it is an invalid character for Java field names. So for fx: id with a few words, you need to use another naming convention like CamelCase, or use underscore.
<!-- This is not valid --> <Label fx:id="my-label">I am a simple label</Label> <!-- This is valid --> <Label fx:id="my_label">I am a simple label</Label> <Label fx:id="MyLabel">I am a simple label</Label>
In Java, you can simply call the setId () method of your component.
Label label = new Label("I am a simple label"); label.setId("foo");
Properties
Although the CSS used in JavaFX is very similar to the original web CSS, there is one big difference. Property names are different, and there are many new JavaFX-specific properties. They have the prefix -fx- .
Here are some examples:
- -fx-background-color : Background color
- -fx-text-fill : Text color
- -fx-font-size : Text size
You can find a list of all the properties in the official design guide .
Pseudo-classes
In addition to the usual classes that mark specific components, there are so-called pseudo-classes that indicate the state of a component. This could be, for example, a class for marking that the component has focus or the mouse cursor is on it.
There are many built-in pseudo-classes. Let's look at the button. There are several pseudo-classes that you can use, for example:
- hover : mouse over button
- focused : the button has focus
- disabled : button is disabled
- pressed : button pressed
Pseudo-classes begin with the ":" character (for example :: hover ) in CSS selectors. Of course, you need to indicate which component your pseudo-class belongs to - for example, button: hover . The following example shows a selector that applies to all buttons that have focus:
.button:focused { -fx-background-color: red; }
Unlike CSS, which has only basic pseudo-classes for states such as focus and hover , JavaFX has component-specific pseudo-classes that relate to different states or properties of components.
For example:
- Scrollbars have horizontal and vertical pseudo-classes
- Elements (Cells) have pseudo-classes odd and even
- TitledPane has expanded and collapsed pseudo-classes
Custom Pseudo-Classes
In addition to the built-in pseudo-classes, you can define and use your own pseudo-classes.
Let's create our own label (inheriting from the Label class). It will have a new logical property called shiny . In this case, we want our label to have a shiny pseudo- class .
Since the tag has a shiny pseudo- class , we can set the background of the gold tag:
.shiny-label:shiny { -fx-background-color: gold; }
Now create the class itself.
public class ShinyLabel extends Label { private BooleanProperty shiny; public ShinyLabel() { getStyleClass().add("shiny-label"); shiny = new SimpleBooleanProperty(false); shiny.addListener(e -> { pseudoClassStateChanged(PseudoClass.getPseudoClass("shiny"), shiny.get()); }); } public boolean isShiny() { return shiny.get(); } public void setShiny(boolean shiny) { this.shiny.set(shiny); } }
There are several important parts:
- We have the BooleanProperty boolean property instead of the usual boolean . This means that the shiny object is observable, and we can track (listen to) changes in its value.
- We register a listener that will be called every time the value of the shiny object changes using shiny.addListener () .
- When the shiny value changes, we add / remove the shiny pseudo- class depending on the current value of pseudoClassStateChanged (PseudoClass.getPseudoClass ("shiny"), shiny.get ()) .
- We add a custom class for all shiny-label labels , instead of having only the label class inherited from the parent. Thus, we can only select shiny tags.
Default Style Sheet
Even if you yourself do not provide any styles, each JavaFX application already has some visual styles. There is a default stylesheet that applies to each application. It is called modena (since JavaFX 8, previously it was called caspian ).
This stylesheet can be found in the file:
jfxrt.jar \ com \ sun \ javafx \ scene \ control \ skin \ modena \ modena.css
Or you can find the file here . In the same directory there are many images used by the stylesheet.
This style sheet provides default styles, but has the lowest priority over other types of style sheets, so you can easily override it.
Scene stylesheet
In addition to the default stylesheet mentioned above, you can of course provide your own. The highest level at which you can apply stylization is the whole scene. You can implement this in your FXML:
<BorderPane xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" stylesheets="styles.css" ... > ... </BorderPane>
Or in your Java code:
String stylesheet = getClass().getResource("/styles.css").toExternalForm(); scene.getStylesheets().add(stylesheet);
Pay attention to the call toExternalForm () . Scene expects to get the contents of the stylesheet as a string, not a file, so we need to provide the contents of our stylesheet as a string.
Parent stylesheet
In addition to the stylesheet for the entire scene, it is sometimes useful to have styles at the layout level. That is - for a separate container, such as VBox, HBox or GridPane. The common parent of all layouts is the parent class, which defines methods for processing style sheets at the layout level. These styles apply only to the components in this layout, and not to the entire scene. A style at the layout level takes precedence over a style at the scene level.
<HBox stylesheets="styles.css"> ... </HBox>
In Java, you need to load the contents of the stylesheet yourself, just like before for the scene:
HBox box = new HBox(); String stylesheet = getClass().getResource("/styles.css").toExternalForm(); box.getStylesheets().add(stylesheet);
Inline styles
So far, we have only looked at cases where an external style sheet has been assigned to an entire scene or layout. But you can set individual style properties at the component level.
Here you do not need to worry about the selector, since all properties are set for a specific component.
You can specify several properties separated by semicolons:
<Label style="-fx-background-color: blue; -fx-text-fill: white"> I'm feeling blue. </Label>
In Java, you can use the setStyle () method:
Label label = new Label("I'm feeling blue."); label.setStyle("-fx-background-color: blue; -fx-text-fill: white");
Styles at the component level take precedence over scene styles as well as parent styles at the layout level.
Why you need to avoid them
Component-level styling can be convenient, but it's a quick and dirty solution. You are giving up the main advantage of CSS, which is the separation of styles from components. Now you rigidly bind your visual elements directly to the components. You will no longer be able to easily switch your style sheets when necessary, you cannot change themes.
Moreover, you no longer have a single central place where your style is defined. When you need to change something in a set of similar components, you need to change each of the components separately, and not edit only one place in the external style sheet. Therefore, inline component styles should be avoided.
Stylesheet priorities
You can provide styling at several levels - scene, parent, inline styles, and there is also a default modem stylesheet. If you change the same property of the same component at several levels, JavaFX has a priority setting that determines which styles should be used. Priority list - from highest to lowest:
- Inline styles
- Parent styles
- Scene styles
- Default styles
This means that if you set the background color of a certain label at both the inline and scene level, JavaFX will use the value set in the inline styles because it has a higher priority.
Additional reading
JavaFX has many CSS properties, and a description of them is beyond the scope of this post; for a detailed list, see the official CSS reference guide for JavaFX .