Javafx: Styling Application with CSS Selectors

JavaFX: Styling application with CSS Selectors

As you can see in the CSS documentation the HBOX class has no style class defined. Therefore you can't simply use .hbox
http://docs.oracle.com/javase/8/javafx/api/javafx/scene/doc-files/cssref.html#hbox

If you want to lookup only direct children of the toolbar the > sign can be used. Using the > sign in a CSS selector will have some benefit in performance issues because by doing so not the complete child hierarchy under the Toolbar Control need to be scanned. Matching Nodes will only be searched in the first hierarchy of children.

So if you want to select all buttons that are direct children of a sidebar you can do the following:

. sidebar > .button

But if you really want to style all button in a sidebar (even if they are wrapped in panes, etc.) you need to use the following selector:

.sidebar .button

Back to your HBOX question: Even if the HBOX has no defined style class (.hbox) it has a type that can be used for a type selector. As described in the CSS doc all nodes have a type:

Node's getTypeSelector method returns a String which is analogous to a
CSS Type Selector. By default, this method returns the simple name of
the class. Note that the simple name of an inner class or of an
anonymous class may not be usable as a type selector. In such a case,
this method should be overridden to return a meaningful value.

Because of that the HBOX selector is working.

Set CSS styling to a specific elements in JavaFX

From the JavaFX CSS Reference:

Each node in the scene graph has an id variable, a string. This is
analogous to the id="..." attribute that can appear HTML elements.
Supplying a string for a node's id variable causes style properties
for this node to be looked up using that id. Styles for specific ids
can be specified using the "#nodeid" selector syntax in a style sheet.

Example:

label.setId("customlabel");

CSS File:

#customlabel {
-fx-font-family: Arial;
-fx-font-size: 15pt;
}

To apply the same style for different nodes, you can use style classes:

Each node in the scene graph has a styleClass variable, a
List. This is analogous to the class="..." attribute that can
appear on HTML elements. Supplying a string for a node's styleClass
variable causes style properties for that style class to applied to
this node. Styles for style classes can be specified using the
".styleclass" selector syntax in a style sheet. Note that a node may
have more than one style class.

node.getStyleClass().add("custom-content");

CSS File:

.custom-content {
-fx-font-family: Arial;
-fx-font-size: 15pt;
}

javaFX css id selector with class selector not working

I think it might be useful for others so i am posting the answer.

What i wanted to achieve is this:

Sample Image

The problem was that i had a TabPane which had as a children another TabPane so the child inherits the css values from parent.

I had modified the css (but before using this you had to add this Java code):

parentTabPane.setId("SpecialTabPane");
parentTabPaneTab.setId("STab");

CSS Code:

#SpecialTabPane #STab.tab .tab-label{
-fx-text-fill:white;
-fx-rotate:90.0;
}

#SpecialTabPane #STab.tab:selected .tab-label{
-fx-text-fill:white;
}

#SpecialTabPane #STab.tab{
-fx-background-color:black;
-fx-background-radius:20.0 20.0 0.0 0.0;
-fx-padding:3.0em 0.0em 3.0em 0.0em;
-fx-cursor:hand;
-fx-effect: innershadow(two-pass-box, white, 15.0, 0.0, 0.0, 0.0);
}

Explaining some of css:

Let's say this piece of code:

#SpecialTabPane #STab.tab

[For every item with id selector #SpecialTabPane that has a subitem with id #Stab and class selector .tab do this..... ]

That's the key if you remove #STab it selects all the tabs from parent TabPane and Children TabPanes

JavaFX CSS type based selectors

Have you tried with capital letters?

HBox {
-fx-spacing:10px;
}

JavaFX CSS style inheritance

A quick and simple answer is Slaw's comment (which may not use CSS inheritance at all):

.base_arc, .child_arc { /* common styles */ } 
.child_arc { /* specific styles */ }

A longer answer, discussing CSS inheritance and its relationship with object-oriented inheritance is below.

Background on CSS inheritance vs object-oriented inheritance

CSS inheritance is not like the object oriented inheritance that Java has.

Instead, CSS inheritance is based upon the node position in the scene graph. Child nodes can inherit CSS properties from their parents (if the CSS property is inheritable). Inheritance is based upon position in the scene graph, not on the Java class type hierarchy. This is documented in the JavaFX CSS documentation on inheritance.

Applying CSS inheritance to your example

Create CSS rules for the parent node

Let's say you have a Pane in which you draw your arcs, and you set the style drawing-pane on it. If you have the following css rule:

.drawing-pane Arc { 
-fx-stroke: black;
}

, then all arcs drawn in the pane would be black. I didn't test that, but it is my understanding of how it works.

The Arc rule is a CSS type selector, so .drawing-pane Arc will select any arcs which have been drawn in the drawing pane.

Create CSS rules for specific types of child nodes

Now, to differentiate different arcs to have different styles, you need to have an additional, more specific, CSS rule which applies the specific style.

So, if you create the following rule:

.drawing-pane .child-arc {
-fx-stroke-line-cap: BUTT;
}

, then all of the arcs which are added to the drawing pane which also have the style class child-arc assigned to then will get a butt cap. They will also have a black stroke as the previous drawing-pane Arc rule still also applies to them (through CSS inheritance).

Associate your nodes with appropriate CSS rules

There are various ways you could associate the child-arc class with an arc, for example with a factory method:

Arc createChildArc() {
Arc arc = new Arc();
arc.getStyleCass().add("child-arc");
}

Or via inheritance by setting the style in a constructor:

public class ChildArc extends Arc {
public ChildArc() {
getStyleClass().add("child-arc");
}
}

Using CSS type selectors rather than class selectors

Note: it is possible to use a type selector (no . prefix and refers to a simple, non-package prefixed Java class name) rather than a css class selector (uses a . prefix), so you could do:

public class ChildArc extends Arc {}

and have CSS as:

.drawing-pane ChildArc {
-fx-stroke-line-cap: BUTT;
}

But, in general, the css style classes are probably a bit more flexible and also in more common usage then the type selectors, so I'd probably just stick with the class selectors.


I'm not really sure if this is the answer you really wanted, but it is my understanding of one way to solve the problem you currently have.

I think what you are really looking for is the info on SASS outlined below, though, in general it isn't how the problem would be solved when using straight CSS without additional tooling.

Using SASS to add object-oriented inheritance to CSS

If you use a pre-processor such as SASS on your css style sheets, you can bring a lot more features (from SASS) into your style sheets. The features that SASS supports include mix-ins and extensions for CSS styles. So SASS makes CSS more object oriented in how it defines its styling rules, by allowing object-oriented style inheritance of style information.

Whether you want to invest the time and assume the complexity to learn SASS and implement it into your build chain is up to you. Personally, for myself, I wouldn't use SASS unless I were writing an awful lot of CSS, which I just don't do.

The standard default css for JavaFX (modena.css), is large, complex and feature rich, and does not make use of SASS style features in its implementation. Studying modena.css is the best way to learn JavaFX CSS best usage practices and principles. If SASS isn't required for something as complicated as modena.css, then it is unlikely to be really necessary for the CSS stylesheets you create for your application.

JavaFX css id selector with dot (full stop) in name

If you Parse your CSS, you'll see:

    public static void main(String[] args) {
CSSParser cssParser = new com.sun.javafx.css.parser.CSSParser();
Stylesheet s = cssParser.parse("#the\\.button {\n" +
" -fx-graphic: url(\"Keyboard.png\");\n" +
"}\n" +
"\n" +
"#thebutton {\n" +
" -fx-graphic: url(\"Keyboard.png\");\n" +
"}");
System.out.println(""); //add a breakpoint here
}

That there is only one rule for the id thebutton and there is two ways to understand why:

  • One is looking at the source here
  • The other is paying attention to a warning in the documentation here

Which I quote below:

While the JavaFX CSS parser will parse valid CSS syntax, it is not a fully compliant CSS parser. One should not expect the parser to handle syntax not specified in this document.

Emphasis mine

Which means if you are trying something that belongs to the CSS standard, but isn't listed on the Oracle's documentation you are going to get a hard time.

extending CSS style in JAVAFX

You need to make a make a more specific selector available. You could e.g. add a style class:

Add the style class line to all lines and then also add the red or blue style classes to the lines that should get those colors.

Example

Java Code

Line redLine = ...
redLine.getStyleClass().add("line");
Line blueLine = ...
blueLine.getStyleClass().add("line");
Line blackLine = ...
blackLine.getStyleClass().add("line");

// add color related classes
redLine.getStyleClass().add("red");
blueLine.getStyleClass().add("blue");

...

CSS

.line {
-fx-stroke: black; /* define standard line color */
-fx-stroke-width: 5px;
}

.line.blue { /* rules for nodes that have style classes line AND blue */
-fx-stroke: blue;
}

.line.red { /* rules for nodes that have style classes line AND red */
-fx-stroke: red;
}

In CSS more specific rules will always overwrite properties of less specific rules. In this case .line is less specific than .line.blue and .line.red since the former selector contains only a single class instead of 2.


Note: There is inheritance in CSS, but properties are inherited from the parent in the scene, not from the base class in the java code.

JavaFX CSS Dynamic Styling

There are a handful of colors on which everything in modena is based. I had an example somewhere, which I can't find right now, but basically

  • -fx-base
  • -fx-accent
  • -fx-default-button
  • -fx-focus-color
  • -fx-faint-focus-color (same as -fx-focus-color but with opacity of 0x22)

So setting these on a root node will basically theme the entire root and its descendants.

In the end, there's no way around the fact that you will have to somehow update these when the user changes them on each root node, and you need to provide the wiring to do that. Using a CSS file is probably not a way to go, as it's going to be difficult to ensure that an updated file is reloaded as needed. I would probably wire things up so that the styleProperty() of the root node changes to define these colors when the user changes them.

You could consider creating a Theme class that encapsulates these:

public class Theme {

private final ObjectProperty<Color> base = new SimpleObjectProperty<>(Color.web("#ececec"));
private final ObjectProperty<Color> accent = new SimpleObjectProperty<>(Color.web("#0096c9"));
private final ObjectProperty<Color> defaultButton = new SimpleObjectProperty<>(Color.web("#abd8ed"));
private final ObjectProperty<Color> focusColor = new SimpleObjectProperty<>(Color.web("#039ed3"));
private final ObjectProperty<Color> faintFocusColor = new SimpleObjectProperty<>(Color.web("039ed322"));

public ObjectProperty<Color> baseProperty() {
return base ;
}

public final Color getBase() {
return baseProperty().get();
}

public final void setBase(Color base) {
baseProperty().set(base);
}

// etc etc

private final ReadOnlyStringWrapper css = new ReadOnlyStringWrapper() ;

public Theme() {
css.bind(Bindings.createStringBinding(() -> String.format(
"-fx-base: %s; "
+"-fx-accent: %s; "
+"-fx-default-button: %s; "
+"-fx-focus-color: %s ; "
+"-fx-faint-focus-color: %s ;",
toRgba(getBase()),
toRgba(getAccent()),
toRgba(getDefaultButton()),
toRgba(getFocusColor()),
toRgba(getFaintFocusColor())),
base, accent, defaultButton, focusColor, faintFocusColor));
}

private String toRgba(Color color) {
int r = (int) (255 * color.getRed());
int g = (int) (255 * color.getGreen());
int b = (int) (255 * color.getBlue());
int a = (int) (255 * color.getOpacity());
return String.format("#%02x%02x%02x%02x", r, g, b, a);
}

public ReadOnlyStringProperty cssProperty() {
return css.getReadOnlyProperty();
}

}

Then you can create a single Theme instance that you make available to you app, and bind all the root node's styleProperty to the cssProperty. Alternatively you could add a factory method to Theme for generating root nodes:

public <T extends Parent> T createThemedNode(Supplier<T> factory) {
T node = factory.get();
node.styleProperty().bind(cssProperty());
return node ;
}

which you use as, for example,

BorderPane root = theme.createThemedNode(BorderPane::new);

If you use FXML you could create similar types of factory methods for loading a FXML document and binding the style of the resulting node.

Finally, of course, you would just do something like

ColorPicker baseColorPicker = new ColorPicker();
baseColorPicker.valueProperty().bindBidirectional(theme.baseProperty());

etc, and when the user chooses a new color, everything is updated.



Related Topics



Leave a reply



Submit