kleopatra
kleopatra

Reputation: 51525

StyleableProperty: how to change value programatically at runtime?

My use-case:

A custom StyleableProperty looks like a perfect match to implement the requirement. Below is an example that implements (taken without change from the class javadoc of StyleablePropertyFactory).

All is fine except for the last requirement: on applyCss, the default value from the stylesheet is reapplied. To reproduce:

The reason for falling back to true (the value set via style), can be traced to applyCss which happens on state changes ... which is understandable and might be the correct thingy-to-do most of the times, but not in my context.

So the questions:

The example:

public class StyleableButtonDriver extends Application {

    /**
     * example code from class doc of StyleablePropertyFactory.
     */
    private static class MyButton extends Button {

        private static final StyleablePropertyFactory<MyButton> FACTORY 
            = new StyleablePropertyFactory<>(Button.getClassCssMetaData());

        MyButton(String labelText) {
            super(labelText);
            getStyleClass().add("my-button");
            setStyle("-my-selected: true");
        }

        // Typical JavaFX property implementation
        public ObservableValue<Boolean> selectedProperty() { return (ObservableValue<Boolean>)selected; }
        public final boolean isSelected() { return selected.getValue(); }
        public final void setSelected(boolean isSelected) { selected.setValue(isSelected); }

        // StyleableProperty implementation reduced to one line
        private final StyleableProperty<Boolean> selected =
             FACTORY.createStyleableBooleanProperty(
                    this, "selected", "-my-selected", s -> s.selected);

        @Override
        public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
            return FACTORY.getCssMetaData();
        }

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

    }
    private Parent createContent() {
        MyButton button = new MyButton("styleable button");
        button.setOnAction(e ->  {
            // does not work: reset on applyCss
            boolean isSelected = button.isSelected();
            button.setSelected(!isSelected);
        });

        CheckBox box = new CheckBox("button selected");
        box.selectedProperty().bind(button.selectedProperty());

        Button toggle = new Button("toggle button");
        toggle.setOnAction(e -> {
            boolean isSelected = button.isSelected();
            button.setSelected(!isSelected);
        });


        BorderPane content = new BorderPane(button);
        content.setBottom(new HBox(10, box, toggle));
        return content;
    }

    @Override
    public void start(Stage stage) throws Exception {
        stage.setScene(new Scene(createContent(), 300, 200));
        //same behavior as setting the style directly
//        URL uri = getClass().getResource("xstyleable.css");
//        stage.getScene().getStylesheets().add(uri.toExternalForm());
        // not useful: would have to override all
//        Application.setUserAgentStylesheet(uri.toExternalForm());
        stage.setTitle(FXUtils.version());
        stage.show();
    }

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

    @SuppressWarnings("unused")
    private static final Logger LOG = Logger
            .getLogger(StyleableButtonDriver.class.getName());

}

Upvotes: 1

Views: 180

Answers (1)

fabian
fabian

Reputation: 82461

You are on the right track, but since you need to override the default priority of the style origins (user agent stylesheet < programmatically assigned < css stylesheet < Node.style property), you cannot use SyleablePropertyFactory for creating this property. You need to create a CssMetaData object that indicates a property as non-setable, if the property was programatically assigned.

private static class MyButton extends Button {

    private static final List<CssMetaData<? extends Styleable, ?>> CLASS_CSS_METADATA;
    private static final CssMetaData<MyButton, Boolean> SELECTED;

    static {
        SELECTED = new CssMetaData<MyButton, Boolean>("-my-selected", StyleConverter.getBooleanConverter()) {

            @Override
            public boolean isSettable(MyButton styleable) {
                // not setable, if bound or set by user
                return styleable.selected.getStyleOrigin() != StyleOrigin.USER  && !styleable.selected.isBound();
            }

            @Override
            public StyleableProperty<Boolean> getStyleableProperty(MyButton styleable) {
                return styleable.selected;
            }

        };

        // copy list of button css metadata to list and add new metadata object
        List<CssMetaData<? extends Styleable, ?>> buttonData = Button.getClassCssMetaData();
        List<CssMetaData<? extends Styleable, ?>> mybuttonData = new ArrayList<>(buttonData.size()+1);
        mybuttonData.addAll(buttonData);
        mybuttonData.add(SELECTED);
        CLASS_CSS_METADATA = Collections.unmodifiableList(mybuttonData);
    }

    MyButton(String labelText) {
        super(labelText);
        getStyleClass().add("my-button");
        setStyle("-my-selected: true");
    }

    // Typical JavaFX property implementation
    public ObservableValue<Boolean> selectedProperty() { return selected; }
    public final boolean isSelected() { return selected.get(); }
    public final void setSelected(boolean isSelected) { selected.set(isSelected); }

    // StyleableProperty implementation reduced to one line
    private final SimpleStyleableBooleanProperty selected = new SimpleStyleableBooleanProperty(SELECTED, this, "selected");

    @Override
    public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
        return CLASS_CSS_METADATA;
    }

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

}

Upvotes: 2

Related Questions