I want to add a custom CSS integer property (in this example I use -fx-foo
) to my custom Label. This is my code:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.css.CssMetaData;
import javafx.css.Styleable;
import javafx.css.StyleableIntegerProperty;
import javafx.css.converter.SizeConverter;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class NewMain extends Application {
public static class FooLabel extends Label {
private static final CssMetaData<FooLabel, Number> FOO_PROPERTY = new CssMetaData<FooLabel, Number>("-fx-foo",
SizeConverter.getInstance(), 10) {
@Override
public boolean isSettable(FooLabel label) {
return true;
}
@Override
public StyleableIntegerProperty getStyleableProperty(FooLabel label) {
return (StyleableIntegerProperty) label.fooProperty();
}
};
private static final List<CssMetaData<? extends Styleable, ?>> CSS_META_DATA;
static {
List<CssMetaData<? extends Styleable, ?>> list = new ArrayList<>(Label.getClassCssMetaData());
list.add(FOO_PROPERTY);
CSS_META_DATA = Collections.unmodifiableList(list);
}
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
return CSS_META_DATA;
}
private final StyleableIntegerProperty foo = new StyleableIntegerProperty(10) {
@Override
public CssMetaData getCssMetaData() {
return FOO_PROPERTY;
}
@Override
public Object getBean() {
return FooLabel.this;
}
@Override
public String getName() {
return "foo";
}
};
public FooLabel() {
super();
foo.addListener((observable, oldValue, newValue) -> {
System.out.println("NEW VALUE:" + newValue);
});
}
public IntegerProperty fooProperty() {
return foo;
}
public void setFoo(int foo) {
this.foo.set(foo);
}
public int getFoo() {
return foo.get();
}
}
/**************** MAIN APP *****************/
@Override
public void start(Stage primaryStage) {
var fooLabel = new FooLabel();
fooLabel.getStyleClass().add("test");
fooLabel.setText("abc");
VBox root = new VBox(fooLabel);
root.getStylesheets().add(NewMain.class.getResource("test.css").toExternalForm());
Scene scene = new Scene(root, 100, 100);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
and CSS:
.test {
-fx-foo: 100;
-fx-background-color: yellow;
}
The code is compiled and when it works it doesn't throw any exceptions. The label is yellow. However, the foo
property never changes, it seems that -fx-foo
is just ignored. Could anyone say how to fix it?
Types that are styleable from CSS implement Styleable
. In order for the CSS engine to know which properties are styleable, the Styleable
must report its CSS metadata. It does this via its getCssMetaData()
method. Note that's an instance method. If you have your own types with their own CSS metadata, then you have to override that method. The returned list should contain the CSS metadata for both the supertypes and your own type if all styleable properties are to continue to work properly.
That's where the static getClassCssMetaData()
methods defined by most styleable classes comes into play. It's not strictly required by the system, and in fact cannot be enforced by the compiler due to the method being static (there's no way in Java to say a type must have a static method). But it's basically part of the expected API and provides at least two functions:
It makes it easy for subtypes to get the metadata of their supertype in a static way without reflection (note since CSS metadata is shared between all instances of a class, it makes sense to define it statically).
It allows tools to inspect the CSS metadata without having to instantiate the class.
The getCssMetaData()
implementation typically delegates to the static method.
However, you actually want to override Control#getControlCssMetaData()
for types that inherit from Control
. It serves the same function as Styleable#getCssMetaData()
, but Control
already provides a final implementation of the latter. That implementation combines the CSS metadata from Control#getControllCssMetaData()
and SkinBase#getCssMetaData()
, which allows skins to add their own separate styleable properties.
So, you should just have to add the following to your FooLabel
class:
@Override
public List<CssMetaData<?, ?>> getControlCssMetaData() {
return getClassCssMetaData();
}
Note: The method is protected
in Control
, but the Labeled
class makes it public
.
FooLabel.java
Implemented similarly to how the standard controls implement styleable properties. Though note it makes use of StyleablePropertyFactory for creating the CssMetaData
.
package com.example;
import java.util.List;
import javafx.beans.property.IntegerProperty;
import javafx.css.CssMetaData;
import javafx.css.SimpleStyleableIntegerProperty;
import javafx.css.StyleableIntegerProperty;
import javafx.css.StyleablePropertyFactory;
import javafx.scene.Node;
import javafx.scene.control.Label;
public class FooLabel extends Label {
private final StyleableIntegerProperty foo = new SimpleStyleableIntegerProperty(Css.FOO, this, "foo");
public final void setFoo(int foo) { this.foo.set(foo); }
public final int getFoo() { return foo.get(); }
public final IntegerProperty fooProperty() { return foo; }
public FooLabel() {
init();
}
public FooLabel(String text) {
super(text);
init();
}
public FooLabel(String text, Node graphic) {
super(text, graphic);
init();
}
private void init() {
getStyleClass().add("foo-label");
}
public static List<CssMetaData<?, ?>> getClassCssMetaData() {
return Css.META_DATA;
}
@Override
public List<CssMetaData<?, ?>> getControlCssMetaData() {
return getClassCssMetaData();
}
private static class Css {
private static final CssMetaData<FooLabel, Number> FOO;
private static final List<CssMetaData<?, ?>> META_DATA;
static {
var factory = new StyleablePropertyFactory<FooLabel>(Label.getClassCssMetaData());
FOO = factory.createSizeCssMetaData("-fx-foo", s -> s.foo);
META_DATA = List.copyOf(factory.getCssMetaData());
}
}
}
Main.java
package com.example;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Main extends Application {
private static final String STYLESHEET =
"""
.foo-label {
-fx-foo: 100;
}
""";
@Override
public void start(Stage primaryStage) throws Exception {
var label = new FooLabel("Hello, World!");
label.fooProperty().subscribe(value -> System.out.printf("foo = %d%n", value));
var scene = new Scene(new StackPane(label), 500, 300);
scene.getStylesheets().add("data:text/css," + STYLESHEET);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(Main.class);
}
}
foo = 0
foo = 100