JavaFX Custom Control CSS PseudoClass 状态改变描边颜色

JavaFX Custom Control CSS PseudoClass state change stroke color

我创建了一个带有两个 CSS 样式属性和一个 CSS 状态伪类的 JavaFX 自定义控件。

我不确定我遗漏了什么,但更改状态不会更新描边颜色。

Reference.java

import java.util.List;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.BooleanPropertyBase;
import javafx.beans.property.ObjectProperty;
import javafx.css.CssMetaData;
import javafx.css.PseudoClass;
import javafx.css.Styleable;
import javafx.css.StyleableObjectProperty;
import javafx.css.StyleableProperty;
import javafx.css.StyleablePropertyFactory;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;
import javafx.scene.paint.Color;

public class Reference extends Control {

    private static final StyleablePropertyFactory<Reference> FACTORY = new StyleablePropertyFactory<>(Control.getClassCssMetaData());

    private static final CssMetaData<Reference, Color>       STROKE_COLOR = FACTORY.createColorCssMetaData("-stroke-color", s -> s.strokeColor, Color.BLACK, false);
    private        final StyleableProperty<Color>            strokeColor;

    private static final CssMetaData<Reference, Number>      STROKE_WIDTH = FACTORY.createSizeCssMetaData("-stroke-width", s -> s.strokeWidth, Double.valueOf(5.0), false);
    private        final StyleableProperty<Number>           strokeWidth;

    private static final PseudoClass ACTIVE_PSEUDO_CLASS = PseudoClass.getPseudoClass("active");

    private BooleanProperty active;
    private boolean activeValue = false;

    public Reference() {
        getStyleClass().add("reference");

        strokeColor = new StyleableObjectProperty<Color>(STROKE_COLOR.getInitialValue(Reference.this)) {
            @Override protected void invalidated() {}
            @Override public Object getBean() { return Reference.this; }
            @Override public String getName() { return "strokeColor"; }
            @Override public CssMetaData<? extends Styleable, Color> getCssMetaData() { return STROKE_COLOR; }
        };
        // strokeColor = new SimpleStyleableObjectProperty<>(STROKE_COLOR, this, "strokeColor", STROKE_COLOR.getInitialValue(Reference.this));

        strokeWidth = new StyleableObjectProperty<Number>(STROKE_WIDTH.getInitialValue(Reference.this)) {
            @Override protected void invalidated() {}
            @Override public Object getBean() { return Reference.this; }
            @Override public String getName() { return "strokeWidth"; }
            @Override public CssMetaData<? extends Styleable, Number> getCssMetaData() { return STROKE_WIDTH; }
        };
    }

    public final boolean isActive() {
        return null == active ? activeValue : active.get();
    }
    public final void setActive(final boolean active) {
        activeProperty().set(active);
    }
    public final BooleanProperty activeProperty() {
        if (null == active) {
            active = new BooleanPropertyBase(activeValue) {
                @Override protected void invalidated() { pseudoClassStateChanged(ACTIVE_PSEUDO_CLASS, get()); }
                @Override public Object getBean() { return Reference.this; }
                @Override public String getName() { return "active"; }
            };
        }
        return active;
    }

    public Color getStrokeColor() { return strokeColor.getValue(); }
    public void setStrokeColor(final Color color) { strokeColor.setValue(color); }
    public ObjectProperty<Color> strokeColorProperty() { return (ObjectProperty<Color>) strokeColor; }

    public Double getStrokeWidth() { return strokeWidth.getValue().doubleValue(); }
    public void setStrokeColor(final Double number) { strokeWidth.setValue(number); }
    public ObjectProperty<Number> strokeWidthProperty() { return (ObjectProperty<Number>) strokeWidth; }

    /* (non-Javadoc)
     * @see javafx.scene.control.Control#createDefaultSkin()
     */
    @Override
    protected Skin<?> createDefaultSkin() {
        return new ReferenceSkin(this);
    }

    /* (non-Javadoc)
     * @see javafx.scene.layout.Region#getUserAgentStylesheet()
     */
    @Override
    public String getUserAgentStylesheet() {
        return getClass().getResource(getClass().getSimpleName().toLowerCase() + ".css").toExternalForm();
    }

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

    /* (non-Javadoc)
     * @see javafx.scene.control.Control#getControlCssMetaData()
     */
    @Override
    public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
        return getClassCssMetaData();
    }

}

ReferenceSkin.java

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.HPos;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.control.SkinBase;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Line;

public class ReferenceSkin extends SkinBase<Reference> {

    private ObjectProperty<Paint> strokeColorProperty;

    private ObjectProperty<Number> strokeWidthProperty;

    public ReferenceSkin(Reference control) {
        super(control);
        strokeColorProperty = new SimpleObjectProperty<>(control.getStrokeColor());
        strokeWidthProperty = new SimpleObjectProperty<>(control.getStrokeWidth());
        initGraphics();
        registerListeners();
    }

    /**
     * Initialize graphics.
     */
    private void initGraphics() {
        final double startX = 0.0;
        final double startY = 0.0;
        final double endX = 200.;
        final double endY = 0.0;

        final Line line = new Line(startX, startY, endX, endY);
        line.strokeProperty().bind(strokeColorProperty);
        line.strokeWidthProperty().bind(strokeWidthProperty);

        getChildren().add(line);
    }

    private void registerListeners() {
        getSkinnable().activeProperty().addListener(observable -> handleControlPropertyChanged("ACTIVE"));
    }

    protected void handleControlPropertyChanged(final String PROPERTY) {
        if ("ACTIVE".equals(PROPERTY)) {
            if (getSkinnable().isActive()) {
                System.out.println("isActive");
            } else {
                System.out.println("isInactive");
            }
        }
    }

    /* (non-Javadoc)
     * @see javafx.scene.control.SkinBase#layoutChildren(double, double, double, double)
     */
    @Override
    protected void layoutChildren(double contentX, double contentY, double contentWidth, double contentHeight) {
        for (Node child : getChildren()) {
            if (child.isManaged()) {
                layoutInArea(child, contentX, contentY, contentWidth, contentHeight, 10, HPos.RIGHT, VPos.TOP);
            }
        }
    }

}

ReferenceApplication.java

import java.util.Timer;
import java.util.TimerTask;

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;

public class ReferenceApplication extends Application {

    public static void main(String[] args) {
        ReferenceApplication.launch(args);
    }

    @Override
    public void start(Stage stage) throws Exception {
        AnchorPane pane = new AnchorPane();
        pane.setPadding(new Insets(10, 10, 10, 10));

        Reference reference1 = new Reference();
        reference1.setPadding(Insets.EMPTY);
        reference1.setLayoutX(100);
        reference1.setLayoutY(50);
        pane.getChildren().add(reference1);

        Reference reference2 = new Reference();
        reference2.setPadding(Insets.EMPTY);
        reference2.setLayoutX(100);
        reference2.setLayoutY(80);
        pane.getChildren().add(reference2);

        Scene scene = new Scene(pane, 400, 200);
        scene.setFill(null);
        stage.setScene(scene);

        stage.setTitle("ReferenceApplication");
        stage.show();

        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                reference2.setActive(!reference2.isActive());
            }
        };

        new Timer().scheduleAtFixedRate(timerTask, 5000, 500);
    }

}

reference.css

.reference {
    -stroke-color: BLACK;
    -stroke-width: 25.0;
}

.reference:active {
    -stroke-color: GREEN;
}

如果我删除 strokeColor 和 strokeWidth 属性并改用以下 CSS 改变状态确实会改变笔触颜色。

.reference > Line {
    -fx-stroke: BLACK;
    -fx-stroke-width: 25.0;
}

.reference:active > Line {
    -fx-stroke: GREEN;
}

但是我不希望那些使用自定义控件的人必须知道内部表示是一条线。也许我想在未来改变它。因此,我创建了自己的 CSS 属性。

您只能在创建皮肤时应用 color/stoke 宽度。
您需要 listen/bind 到可设置样式的属性:

strokeColorProperty = control.strokeColorProperty();
strokeWidthProperty = control.strokeWidthProperty();