JavaFX is finally delivering on it’s promise (made 5 years) to open source JavaFX. It started last December with the controls code. And now that the JavaFX 8 repos are open we can now look at the internals of JavaFX in a format much more readable than
javap decompilations. (The previous statement is not an admission, wink wink nod nod).
One of the questions I had was how the CSS styles are used in a Node. And after looking at some of the innards I realized that I didn’t really care how JavaFX does it, I care about how I can get my control stylized like the built in controls. To demonstrate the CSS stylings, I created
DemoTwo in my DeckControl project, allowing you to customize the control via CSS. Only the shelf variants in this demo use the custom properties, the others are just using the built in
-fx-skin style to do the switching.
A Word of Warning
These are all internal APIs, the kind that the java compiler ought to warn you about but doesn’t. These APIs will change before JavaFX 8 is released in some fashion (assuming that they can keep the schedule and get these APIs exposed properly). These changes may be as minor as package names but are likely to change in more substantive ways. So before you read further I want you to raise your right hand, place your left hand behind your back, cross your fingers, and swear to me that you will not whine, complain, or moan when Oracle changes things and ruins all your fun. Now pinky swear on it. Ok, now we can begin.
Make Your Property Styleable
First, you have to make the properties use the styleable types. The types you will want to use are the
com.sun.javafx.css.Styleable<Foo>Property classes. It’s not a find-and-replace option, you will need to use an inner class of some sort.The classes are abstract and three methods are not implemented:
getName(). The first method
getStyleableProperty() returns an instance of
StyleableProperty that describes this property. We will discuss this more in the next section. The other two methods (
getName()) actually come from the
javafx.beans.properties.ReadOnlyProperties interface, so their role is already a known quantity. You may also want to overload the
invalidated() method, which at this point provides less overhead than attaching an invalidated listener.
There are also some stylistic concerns with extending the property. You may want to use some lazy loading of the property to make overriding easier to detect. You can also return a default value from the getter if the property is null, and only initialize the getter in the property method. Here is an example from my Deck class of the styleable property.
Describe Your Styleable Property
The next step is to describe your styleable property. It’s kind of chicken and egg with the property instance itself since you have to reference the description in the property and the description needs to know how to get the property.
All of these descriptions are of type
com.sun.javafx.css.StyleableProperty and this class is (you guessed it) abstract. All of the concrete version of this class in the codebase I can find are all anonymous or implementation details.
The class is generically typed, with the first type
N being the type of the
Node you are creating the styleable property for, and the second type
V being the type of the property in the Java code, such as Number or Boolean.
Since this is an abstract class and not an interface we have to deal with the parent constructor. The two required variables are the CSS name of the property and an object responsible for the conversions of the CSS strings to the real type. Do yourself a favor and just use one of the converters in
com.sun.javafx.css.convrrters. There are constructors with more parameters, such as a default value, CSS inheritance flags, and information for marking the property as one that can be composed by other properties (such as font handling). I would anticipate that the two or three argument constructors being the most useful ones.
The two methods you have to implement are
getWritableValue(N). The first method returns a boolean flag that tells the CSS engine if it should try and set the property from CSS or not. If you don’t want to set the property under some circumstances, like it is bound to some other properties value, you will return false. This is where the first type parameter comes into play, it doesn’t have to exactly be your type, but it will be the type of the parameter of this and the next method. The second method is where you return your property object from the previous section. Since the declaration is shared among multiple controls, the particular instance is passed in as a parameter.
Remember, this object is more of a type declaration than the actual value of the property. We are telling the engine about the property and not becoming the property itself, that is what the previous section was about.
Enumerate Your Styleable Property
Finally we have to let the engine know about our CSS properties. The magic incantation in this case is to add a method
public List<StyleableProperty> impl_getStyleableProperties() to your control class. Note that this method is marked as deprecated and starts with
impl_. The combination of these two markings are how the JavaFX team tells us that this method will change and break in unexpected ways. But I think we are safe for JavaFX 2.2.x.
In this method simply return all of the
StyleableProperty objects that you want the CSS engine to set and alter. Remember, this is all of the properties, including the properties of the parent class. So be sure you return those (unless you explicitly want to hide them). You can be a good software engineer and use a lazy cache if you like. It will perform (slightly) better.
The Proof is in the Pudding
I am sure I violated some coding standards the JavaFX team uses to create their CSS properties, but at the end of the day the code works. I do have to say, there is quite a bit of Yak Shaving that goes on. Hopefully in JavaFX 8 they will come up with some amazingly simple means to mark properties as styleable (I have some ideas). But until that future day, you can do CSS styling on your custom controls now.