Issue
This is related very much to this question of Neil Benn.
When I build a StyleableProperty using the StyleablePropertyFactory following the steps Neil provided, I get a property that can be set from the fxml file, but which ignores the css completely.
His solution was to listen to any changes of the css and redraw his object when that happens (at least that's my understanding so far).
Since I can't get this to work I would appreciate any tip on what to add to this (somewhat minimal) example to make it work with its css file.
Minimal Working example
My custom JavaFX Control: StyleablePane.java
package projects.styleableproperty;
import javafx.beans.value.ObservableValue;
import javafx.css.StyleableProperty;
import javafx.css.StyleablePropertyFactory;
import javafx.geometry.Insets;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
public class StyleablePane extends Pane {
private static final StyleablePropertyFactory<StyleablePane> FACTORY = new StyleablePropertyFactory<>(
StyleablePane.getClassCssMetaData());
public StyleablePane() {
// keeping background color same as fill color for testing purpose
fillProperty().addListener((observable, oldValue, newValue) -> {
setBackground(new Background(new BackgroundFill(newValue, new CornerRadii(0), new Insets(0))));
});
}
// Fill Property //
private final StyleableProperty<Color> fill = FACTORY
.createStyleableColorProperty(this, "fill", "-fx-fill", svgPane -> svgPane.fill);
public Color getFill() {
return fill.getValue();
}
public void setFill(Color fill) {
this.fill.setValue(fill);
}
@SuppressWarnings("unchecked")
public ObservableValue<Color> fillProperty() {
return (ObservableValue<Color>) fill;
}
}
An FXML file using this custom control: Styleable.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.AnchorPane?>
<?import projects.styleableproperty.StyleablePane?>
<?import java.lang.String?>
<AnchorPane prefHeight="300.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1">
<children>
<StyleablePane prefHeight="200.0" prefWidth="200.0" styleClass="a-green-one"/>
</children>
<stylesheets>
<String fx:value="/css/Styleable.css"/>
</stylesheets>
</AnchorPane>
A simple style sheet: Styleable.css
.a-green-one {
-fx-fill: aquamarine;
}
Small application to run the whole bunch:
package projects.styleableproperty;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
import java.io.IOException;
public class StyleableExperiment extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
AnchorPane rootPane = null;
try {
rootPane = FXMLLoader.load(getClass().getResource("Styleable.fxml"));
Scene scene = new Scene(rootPane);
primaryStage.setScene(scene);
primaryStage.show();
} catch (IOException e) {
e.printStackTrace();
}
}
}
What I would like to see:
What I currently see:
Solution
You also need to override the getCssMetaData()
method in StyleablePane
:
@Override
public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
return FACTORY.getCssMetaData();
}
While it's not necessary to make it work, it's good practice to provide a static getClassCssMetaData()
method returning the same list (hat-tip to @slaw for this):
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
return FACTORY.getCssMetaData();
}
From the documentation:
The purpose of
getClassCssMetaData()
is to allow sub-classes to easily include theCssMetaData
of some ancestor.
Note that your instantiation of the FACTORY
is not quite correct. From the documentation for StyleablePropertyFactory
:
The constructor is passed the
CssMetaData
of the parent class of<S>
(my emphasis), so you should have
private static final StyleablePropertyFactory<StyleablePane> FACTORY = new StyleablePropertyFactory<>(
Pane.getClassCssMetaData());
The complete class should be
import java.util.List;
import javafx.beans.value.ObservableValue;
import javafx.css.CssMetaData;
import javafx.css.Styleable;
import javafx.css.StyleableProperty;
import javafx.css.StyleablePropertyFactory;
import javafx.geometry.Insets;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
public class StyleablePane extends Pane {
private static final StyleablePropertyFactory<StyleablePane> FACTORY = new StyleablePropertyFactory<>(
Pane.getClassCssMetaData());
public StyleablePane() {
// keeping background color same as fill color for testing purpose
fillProperty().addListener((observable, oldValue, newValue) -> {
setBackground(new Background(new BackgroundFill(newValue, new CornerRadii(0), new Insets(0))));
});
}
@Override
public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
return FACTORY.getCssMetaData();
}
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
return FACTORY.getCssMetaData();
}
// Fill Property //
private final StyleableProperty<Color> fill = FACTORY
.createStyleableColorProperty(this, "fill", "-fx-fill", svgPane -> svgPane.fill);
public Color getFill() {
return fill.getValue();
}
public void setFill(Color fill) {
this.fill.setValue(fill);
}
@SuppressWarnings("unchecked")
public ObservableValue<Color> fillProperty() {
return (ObservableValue<Color>) fill;
}
}
Answered By - James_D