Failure to apply style to Custom StyleableObjectProperty on Subclass of Control

XMLWordPrintable

    • Type: Bug
    • Resolution: Unresolved
    • Priority: P5
    • tbd
    • Affects Version/s: jfx11, jfx17, jfx21, jfx25, jfx26
    • Component/s: javafx
    • generic
    • generic

      ADDITIONAL SYSTEM INFORMATION :
      - JavaFX Version: 25
      - Java Version: OpenJDK Runtime Environment Zulu25.28+85-CA (build 25+36-LTS)
      - Operating System: macOS Sequoia 15.6


      A DESCRIPTION OF THE PROBLEM :
      I'm experiencing a silent failure of the JavaFX CSS engine to apply an external stylesheet value to a custom StyleableObjectProperty on a subclass of Control, even when all standard best practices (correct CssMetaData, proper module access, high specificity, and the latest JavaFX version) are followed.


      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      1. Compile and run the provided minimal JavaFX application
      2. Observe the ToolBarSeparator control.


      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      The ToolBarSeparator line color should be RED, as defined by the external stylesheet (style.css), overriding the internal Java default of Color.BLUE.

      ACTUAL -
      The ToolBarSeparator line color remains BLUE, confirming that the CssMetaData default value is read, but the external CSS rule is silently rejected and not applied to the property.


      ---------- BEGIN SOURCE ----------
      module-info.java:

      module problem.demo {
          requires javafx.base;
          requires javafx.controls;
          requires javafx.fxml;

          requires transitive javafx.graphics;

          opens problem.demo to javafx.base, javafx.controls, javafx.graphics;

          exports problem.demo;
      }

      Launcher.java:

      package problem.demo;

      import javafx.application.Application;
      import javafx.scene.Scene;
      import javafx.scene.control.Button;
      import javafx.scene.layout.VBox;
      import javafx.stage.Stage;

      public class Launcher extends Application {
          public static void main (String[] args) {
              launch(args);
          }

          @Override
          public void start(Stage stage) {
              VBox root = new VBox();
              Scene scene = new Scene(root, 900, 600);
              scene.getStylesheets().add(getClass().getClassLoader().getResource("style.css").toExternalForm());
              stage.setScene(scene);
              ToolBarSeparator tbs = new ToolBarSeparator();
              Button button = new Button("Hello");
              root.getChildren().addAll(tbs, button);
              stage.show();
          }
      }

      ToolBarSeparator.java:

      package problem.demo;

      import java.util.ArrayList;
      import java.util.Collections;
      import java.util.List;

      import javafx.scene.layout.Region;
      import javafx.scene.paint.Color;
      import javafx.scene.paint.Paint;
      import javafx.css.CssMetaData;
      import javafx.css.SimpleStyleableObjectProperty;
      import javafx.css.Styleable;
      import javafx.css.StyleableObjectProperty;
      import javafx.css.StyleableProperty;
      import javafx.scene.canvas.Canvas;
      import javafx.scene.canvas.GraphicsContext;
      import javafx.scene.control.Control;
      import javafx.scene.control.SkinBase;

      public class ToolBarSeparator extends Control {
          private static final CssMetaData<ToolBarSeparator, Paint> STROKE_META_DATA =
          new CssMetaData<ToolBarSeparator, Paint>(
              "-my-app-separator-line-color",
              javafx.css.converter.PaintConverter.getInstance(),
              Color.GREEN,
              false
          ) {
              @Override
              public StyleableProperty<Paint> getStyleableProperty(ToolBarSeparator styleable) {
                  return (StyleableProperty<Paint>) styleable.strokeProperty();
              }
              @Override
              public boolean isSettable(ToolBarSeparator styleable) {
                  return true;
              }
          };

          private static final List<CssMetaData<? extends Styleable, ?>> CSS_META_DATA;

          static {
              List<CssMetaData<? extends Styleable, ?>> parentMetaData = Control.getClassCssMetaData();
              List<CssMetaData<? extends Styleable, ?>> customMetaData = new ArrayList<>(parentMetaData.size() + 1);
              customMetaData.addAll(parentMetaData);
              customMetaData.add(STROKE_META_DATA);
              CSS_META_DATA = Collections.unmodifiableList(customMetaData);
          }

          private final StyleableObjectProperty<Paint> stroke;

          public ToolBarSeparator() {
              this.stroke = new SimpleStyleableObjectProperty<>(
                  STROKE_META_DATA,
                  this,
                  "stroke"
              );

              setMinHeight(28);
              setPrefHeight(28);
              setMaxHeight(28);

              setMinWidth(5);
              setPrefWidth(5);
              setMaxWidth(5);

              setId("my-separator-unique-id");
          }

          public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
              return CSS_META_DATA;
          }

          @Override
          protected javafx.scene.control.Skin<?> createDefaultSkin() {
              applyCss();
              if (getStroke() == null) {
                  setStroke(Color.BLUE);
              }
              return new ToolBarSeparatorSkin(this);
          }

          @Override
          protected double computePrefWidth(double height) {
              return Region.USE_COMPUTED_SIZE;
          }

          public final StyleableObjectProperty<Paint> strokeProperty() {
              return stroke;
          }

          public final Paint getStroke() { return strokeProperty().get(); }
          public final void setStroke(Paint value) { strokeProperty().set(value); }

          public class ToolBarSeparatorSkin extends SkinBase<ToolBarSeparator> {
              private final Canvas canvas;

              public ToolBarSeparatorSkin(ToolBarSeparator control) {
                  super(control);
                  this.canvas = new Canvas();
                  getChildren().add(canvas);
                  control.widthProperty().addListener((obs, oldVal, newVal) -> {
                      draw(newVal.doubleValue(), control.getHeight());
                  });
                  control.heightProperty().addListener((obs, oldVal, newVal) -> {
                      draw(control.getWidth(), newVal.doubleValue());
                  });
                  control.strokeProperty().addListener((obs, oldVal, newVal) -> {
                      System.out.println("STYLE CHANGE");
                      draw(control.getWidth(), control.getHeight());
                  });
              }

              @Override
              protected void layoutChildren(double contentX, double contentY, double contentWidth, double contentHeight) {
                  super.layoutChildren(contentX, contentY, contentWidth, contentHeight);
                  canvas.setWidth(contentWidth);
                  canvas.setHeight(contentHeight);
                  draw(contentWidth, contentHeight);
              }

              public void draw(double width, double height) {
                  GraphicsContext gc = canvas.getGraphicsContext2D();
                  gc.clearRect(0, 0, width, height);

                  Color color = Color.GRAY;

                  Paint p = getSkinnable().getStroke();
                  if (p instanceof Color c) {
                      color = c;
                      System.out.println("*** p is Color ***");
                  } else {
                      System.out.println("*** p is NOT Color ***");
                  }
                  gc.setStroke(color);

                  gc.setLineWidth(1);

                  for (double i = 0; i < height; i +=3) {
                      gc.strokeLine(0, i, width, i);
                  }
              }
          }
      }

      style.css:

      .button {
          -fx-background-color: yellow;
      }

      #my-separator-unique-id {
          -my-app-separator-line-color: red !important;
      }


      ---------- END SOURCE ----------

        1. ToolBarSeparator.java
          5 kB
        2. style.css
          0.1 kB
        3. Screenshot.png
          Screenshot.png
          8 kB
        4. Launcher.java
          0.7 kB

            Assignee:
            Andy Goryachev
            Reporter:
            Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated: