There’s been some confusion about styling scenes and what it means to have a CSS rule that uses “.scene” as the selector. For example, the caspian.css file that’s included in JavaFX, the JavaFX tutorial Applying CSS to UI Controls, and Dean Iverson’s article Into the Background all include style rules that look something like this:
.scene { -fx-background: cornflowerblue; /* ... */ }
There have been a bunch of questions about this, for example in the forum threads here and here, and in comments at the bottom of the Applying CSS to UI Controls tutorial. People are tinkering with this rule and it’s not working the way they expect. The pieces of this part of the system fit together in funny ways, so I certainly can see why there’s lots of confusion. Let’s dive in.
First, let’s create a simple scene:
Stage { scene: Scene { width: 150 height: 150 stylesheets: "{__DIR__}styles.css" content: [ Rectangle { x: 50 y: 50 width: 50 height: 50 } ] } }
Now let’s try to style the scene. The various examples use the -fx-background and -fx-fill properties. Which one is correct? Let’s try both, using different colors so that we can tell which one took effect. Place the following into the styles.css file:
.scene { -fx-background: cornflowerblue; -fx-fill: orchid; }
What happens? Basically, nothing. We get a black rectangle on a white background, just as if we hadn’t specified any CSS at all.
The main problem is that Scene objects don’t have any CSS-settable attributes. There are a couple things on Scene that seem like they ought to be settable from CSS, such as the fill and cursor variables. But they don’t work, and that’s a bug. See JIRA issue RT-8653. If you want to set the background color of a Scene, you have to use a rectangle or ScrollView and style that instead. See Dean Iverson’s article Into the Background which covers this pretty well.
If a style rule for “.scene” doesn’t apply any properties to the Scene, what does it do? Let’s first examine an assumption here, which is that a style rule with “.scene” actually applies to Scene objects. This is a reasonable assumption because we’ve set things up for the controls so that “.button” styles apply to Button, “.slider” styles apply to Slider, and so forth. This convention that applies to the controls, but it doesn’t apply everywhere. You can’t, for instance, declare a style rule for “.rectangle” and expect it to apply to all Rectangle objects. Try it! It doesn’t work. To style a Rectangle, you have to give it a styleClass value and then use that in your CSS. For example:
// Main.fx Rectangle { styleClass: "myrect" ... } // styles.css .myrect { -fx-fill: dodgerblue; }
gives the following result:
Now back to Scene. If the “.scene” styles don’t apply to Scene objects, what do they apply to?
Let’s examine a little puzzle in the scene graph API. The scene graph is a tree of nodes, and all trees have a root. Every Node has a parent variable, which is of type Parent (a subclass of Node). If you walk up the scene graph following the parent variable, you’ll eventually get to the root. That’s the Scene, right? But it can’t be, because a Scene isn’t a Node. What is the root Node then? Let’s find out.
var rect:Rectangle; ... Stage { scene: Scene { ... content: [ rect = Rectangle { styleClass: "myrect" x: 50 y: 50 width: 50 height: 50 } ] } } println("parent={rect.parent}"); // and while we're at it println("styleClass={rect.parent.styleClass}");
The output will be something like this:
parent=Scene$Root@27d314cc styleClass=scene
Whoa, what’s that? Inside of every Scene, there’s a hidden node called Scene$Root that’s the parent of every Node in the Scene’s content sequence. It’s a subclass of Parent. It’s the actual root node of the scene graph. And, most importantly for this discussion, it handles CSS on behalf of the Scene. Indeed, its styleClass is “scene”. So, style rules that use “.scene” as their selector are really applying styles to this hidden Scene$Root object.
We’re a little closer, since we now know that “.scene” styles apply to the Scene$Root object and not the Scene object itself. But Scene$Root doesn’t actually have any properties that are affected by CSS. What good does it do to set properties on Scene$Root if they don’t actually affect it?
To understand what’s going on, we have to talk about two features of CSS. First, a feature of JavaFX CSS is that colors can be specified not only using standard color names such as “dodgerblue” or by using RGB syntax like “#0093ff”, but they also can also be looked up by name using properties defined by styles above them in the hierarchy. Second, as in standard CSS, some properties are inherited. Let’s examine these features in more detail.
Color Lookup
Continuing with the simple rectangle example from above, let’s define style rule for .scene that defines some new color names:
.scene { -smarks-special-fill-color: #40d080; -smarks-special-border-color: #d02040; }
I just made up the property names -smarks-special-fill-color and -smarks-special-border color. They don’t exist anywhere else, and merely setting these properties won’t have any effect on anything. However, the property names and values are applied to Scene$Root, so we can put them to use. Define
.myrect { -fx-fill: -smarks-special-fill-color; -fx-stroke: -smarks-special-border-color; -fx-stroke-width: 5; }
(I also made the stroke width thicker so that it’s easier to see the border.) The result is as follows:
This works because in JavaFX CSS, you can use an arbitrary property name as the value for a color property. This property name is looked up on all ancestor nodes, and if a value is found, that color is used in its place.
Now, I could have specified those colors directly in the .myrect rule and it would have worked just fine. However, if I had several different style rules (say, for different shapes) that wanted to use the same colors, I’d have those color values spread around in a bunch of different rules in my style sheet. If I wanted to change a color, I’d have to change it in several places. Since the Scene$Root node is the ancestor of every node that appears in any Scene, and since its styleClass is “scene”, I can place some color definitions in the “.scene” style rule and they’ll be visible to be looked up from any node in the scene graph. The “.scene” style is thus about as close as we can get to a global repository of color properties. Indeed, if you look at the “.scene” rule from the caspian.css file, just about all if its declarations are for color properties.
Inheritance
Let’s change the scene’s content to have some Text nodes in order to show inheritance of font properties:
content: [ Text { x: 30 y: 60 content: "Hello, JavaFX!" } Text { x: 30 y: 100 content: "CSS is fun!" } ]
This will use the default font, which will give a result like this:
Now let’s modify the .scene style rule as follows:
.scene { -fx-font: 18pt "Times-Italic"; }
The result will change to the following:
Why did this have an effect? The reason is that, as in standard CSS, font properties are inherited down the hierarchy. More precisely, if a font value isn’t provided for a particular node, the font property is looked up in ancestor nodes until a value is found. Since Scene$Root is at the root of the hierarchy, and the “.scene” properties have been applied to it, the font defined there ends up being used by the Text nodes. Of course, I could override the font by specifying a font value directly in the code, or by creating a style with a font property and arranging for it to apply to the Text nodes or to an ancestor. Since I didn’t do that, the font property from the “.scene” rule applies. It’s effectively a specification of the default font to be used everywhere in the scene graph.
In summary, the “.scene” style rule doesn’t apply properties to the Scene itself (though maybe it should). Instead, it stores properties on the Scene$Root node. These properties aren’t used directly, but can be inherited (for fonts) and can be looked up (for colors) by nodes anywhere in the scene graph. Thus, the “.scene” style rule is where global color and font properties can be defined, which makes it easier to develop and maintain consistent style sheets.
“This is a reasonable assumption because we’ve set things up for the controls so that “.button” styles apply to Button, “.slider” styles apply to Slider, and so forth.”
I noticed yesterday that the styleClass of a custom control is not initialized per default. Is it consider good practice to override-and-initialize it explicitly for a custom control?
Good question. It would indeed be useful to initialize the styleClass variable for a CustomNode subclass, for a similar reason as I described in this post for Scene$Root and the “.scene” style rule.
CustomNode itself has no styleable attributes, but it can serve as a repository of style properties that can be inherited or looked up by its contents. So you could have a CustomNode with a styleClass of “my-custom-node” and then provide a style rule for “.my-custom-node” that contains font and color properties. These would apply to anything inside that CustomNode but not to anything outside. Could be quite useful.
[…] of Stuart Marks, he has blogged with some useful details related to applying styles to scenes. This is a must-read for anyone using CSS in […]