2

I have a set of custom controls implemented as standard pair of classes that extend javafx.scene.control.Control and javafx.scene.control.SkinBase. Essentially they are a set of composite controls with a label and one or more "data" controls such as a TextBox for value and ChoiceBox for unit of measure.

I have several properties that interact correctly with SceneBuilder, however two properties, value and min-label-width appear as read only in SceneBuilder. How do I make these properties editable in SceneBuilder? They work correctly when manipulated programmatically when the controls are used in an FXML controller.

I made the min-label-width skinnable with CssMetaData, but that made no difference. I have been looking at the controlsFX source code to get templates on how to do things, but haven't found the answer yet.

// --- min-label-width Defines the space for the label of the control so that
// controls can easily be lined up.

private DoubleProperty minLabelWidth;

public final void setMinLabelWidth(double val)
{
    minLabelWidthProperty().set(val);
}

public final Double getMinLabelWidth()
{
    return minLabelWidthProperty().get();
}

/**
 * lazy allocation of DoubleProperty minLabelWidth.
 *
 * @return the minLabelWidth
 */
public final DoubleProperty minLabelWidthProperty()
{
    if (minLabelWidth == null)
    {
        minLabelWidth = new StyleableDoubleProperty(100.0)
        {

            @Override
            public Object getBean()
            {
                return CustomControl.this;
            }

            @Override
            public String getName()
            {
                return "minLabelWidth"; //$NON-NLS-1$
            }

            @Override
            public CssMetaData<? extends Styleable, Number> getCssMetaData()
            {
                return StyleableProperties.MIN_LABEL_WIDTH;
            }
        };
    }
    return minLabelWidth;
}

 private static final CssMetaData<CustomControl, Number> MIN_LABEL_WIDTH =
            new CssMetaData<CustomControl, Number>("-min-label-width", SizeConverter.getInstance(), 100)
            {

                @Override
                public Number getInitialValue(CustomControl node)
                {
                    // A vertical Slider should remain vertical
                    return node.getMinLabelWidth();
                }

                @Override
                public StyleableProperty<Number> getStyleableProperty(CustomControl arg0)
                {
                    return (StyleableProperty<Number>) arg0.minLabelWidthProperty();
                }

                @Override
                public boolean isSettable(CustomControl arg0)
                {

                    return arg0.minLabelWidth == null || !arg0.minLabelWidth.isBound();
                }

            };

I expect to be able to edit the Min Label Width entry in the custom section of the properties inspector of Java, but the field is grey and read only.

José Pereda
  • 44,311
  • 7
  • 104
  • 132

2 Answers2

2

You are on the right track.

Like any CSSMetaData object, MIN_LABEL_WIDTH has to be created in this private StyleableProperties class (see for instance TextField), and then added to the STYLEABLES list that is returned by the control's getClassCssMetaData().

Also mind the adding the correct types: CssMetaData<CustomControl, Number>.

This works for me:

private static class StyleableProperties {

     private static final CssMetaData<CustomControl, Number> MIN_LABEL_WIDTH =
            new CssMetaData<>("-min-label-width", SizeConverter.getInstance(), 100.0) {

        @Override
        public Number getInitialValue(CustomControl node) {
            // A vertical Slider should remain vertical
            return node.getMinLabelWidth();
        }

        @Override
        public StyleableProperty<Number> getStyleableProperty(CustomControl control) {
            return (StyleableProperty<Number>) control.minLabelWidthProperty();
        }

        @Override
        public boolean isSettable(CustomControl control) {

            return control.minLabelWidth == null || !control.minLabelWidth.isBound();
        }

    };

    private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
    static {
        final List<CssMetaData<? extends Styleable, ?>> styleables = 
                new ArrayList<>(Control.getClassCssMetaData());
        styleables.add(MIN_LABEL_WIDTH);

        STYLEABLES = Collections.unmodifiableList(styleables);
    }
}

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

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

Compile and build your control, and import the jar to Scene Builder. It should be editable:

Styleable property

Make sure you use the correct version of Scene Builder. I'm building on 11/12, so I use Scene Builder 11.0.0.

José Pereda
  • 44,311
  • 7
  • 104
  • 132
  • Thank you, José. Unfortunately I had trouble seeing what was different in your code, but I'm sure it is something subtle and important :-) Fortunately I found the answer in an oracle presentation by using the StyleablePropertyFactory, which worked correctly on the first try. – Mark D Henning Jun 17 '19 at 18:09
  • Surely both solutions are pretty much the same, though I agree `StyleablePropertyFactory` is easier to use. It was added to JavaFX in 8u40, and therefore it wasn't used in the source code of the built-in controls, that we usually mimic as the valid reference. – José Pereda Jun 17 '19 at 18:20
1

After searching around with different search terms, I found a MUCH easier way to resolve my problem: the StyleablePropertyFactory.

Here are references to where I found the information: https://static.rainfocus.com/oracle/oow16/sess/1462484351438001p6a1/ppt/Custom%20Controls.pdf . Slide #88 copied and updated for my needs worked beautifully. However, the example did not show how to make a default value. This link has the proper call for a default value in a Number property.

https://docs.oracle.com/javase/8/javafx/api/javafx/css/StyleablePropertyFactory.html

Here is the much simplified properties set.

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

private final StyleableProperty<Number> titleWidth =
        FACTORY.createStyleableNumberProperty(this,"titleWidth", "title-width", s->s.titleWidth, 100.0);
private final StyleableProperty<String> title =
        FACTORY.createStyleableStringProperty(this,"title","title", s->s.title);

@SuppressWarnings("unchecked")
public final ObservableValue<Number> titleWidthProperty() {return (ObservableValue<Number>) titleWidth;}

public final Double getTitleWidth() { return titleWidth.getValue().doubleValue();}
public final void setTitleWidth(Double value) {titleWidth.setValue(value);}

public final StringProperty titleProperty() { return (StringProperty) title;}

public final String getTitle() {return title.getValue();}
public final void setTitle(String newTitle) {title.setValue(newTitle);}

private static final List> STYLEABLES; static { final List> styleables = new ArrayList<>(FACTORY.getCssMetaData()); styleables.add(StyleableProperties.ORIENTATION);

  STYLEABLES = Collections.unmodifiableList(styleables);

}

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