2

I am trying to create a custom JavaFX element for use in FXML, but when FXMLLoader tries to parse it, it throws an exception that states:

javafx.fxml.LoadException: Element does not define a default property.

However, after doing some research, I believe that I am defining the default property properly.

If I include the nestedCellValueFactory tags, everything works as expected.

Java

@DefaultProperty("nestedCellValueFactory")
public class NestablePropertyValueFactory<S, T> extends PropertyValueFactory<S, T> {

    private ObjectProperty<PropertyValueFactory<?, ?>> nestedCellValueFactory;

    public NestablePropertyValueFactory(
            @NamedArg("property") String property,
            @NamedArg("nestedCellValueFactory") PropertyValueFactory<?, ?> nestedCellValueFactory) {
        super(property);
        this.nestedCellValueFactory = new SimpleObjectProperty<>(this, "nestedCellValueFactory", nestedCellValueFactory);
    }

    public final ObjectProperty<PropertyValueFactory<?, ?>> nestedCellValueFactoryProperty() {
        return nestedCellValueFactory;
    }

    public final PropertyValueFactory<?, ?> getNestedCellValueFactory() {
        return nestedCellValueFactoryProperty().get();
    }

    public final void setNestedCellValueFactory(PropertyValueFactory<?, ?> nestedCellValueFactory) {
        nestedCellValueFactoryProperty().set(nestedCellValueFactory);
    }

}

FXML

<NestablePropertyValueFactory property="outer">
    <NestablePropertyValueFactory property="inner">
        <PropertyValueFactory property="property"/>
    </NestablePropertyValueFactory>
</NestablePropertyValueFactory>
Andy Senn
  • 649
  • 5
  • 13
  • Also, I know that the recommendations of an IDE are not a reputable gauge of implementation accuracy, but IntellJ keeps suggesting that I remove the elements because it is the default property, so I have to believe that I'm at least on the right track in some regards. – Andy Senn Apr 17 '17 at 22:21
  • I was originally running this with JDK 1.8.0_92, but I switched to 1.8.0_121 and got the same result. – Andy Senn Apr 17 '17 at 22:41

1 Answers1

4

After some debugging, I have a possible explanation for the error, but sadly not a solution for it.

If you run without the <nestedCellValueFactory> tags, as you already reported, you will get this exception:

Caused by: javafx.fxml.LoadException: Element does not define a default property.
at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2597)
at javafx.fxml.FXMLLoader.access$100(FXMLLoader.java:103)
at javafx.fxml.FXMLLoader$Element.set(FXMLLoader.java:180)
at javafx.fxml.FXMLLoader$ValueElement.processEndElement(FXMLLoader.java:790)
at javafx.fxml.FXMLLoader.processEndElement(FXMLLoader.java:2823)

So going to the FXMLLoader sources to track the exception you will find:

  • Line 790: parent.set(value);, where parent is an instance of FXMLLoader$InstanceDeclarationElement, with type <your.package.name>. NestablePropertyValueFactory, and value is a PropertyValueFactory object.

  • Lines 177-183:

    public void set(Object value) throws LoadException {
        ...  
        Class<?> type = this.value.getClass();
        DefaultProperty defaultProperty = type.getAnnotation(DefaultProperty.class);
        if (defaultProperty == null) {
            throw constructLoadException("Element does not define a default property.");
        }
    
        getProperties().put(defaultProperty.value(), value);
    }
    

One could expect that defaultProperty.value() should be "nestedCellValueFactory", and the value will be assigned, but the exception thrown clearly shows that this is not the case, so defaultProperty should be null.

What happens can be explained by inspecting Class<?> type:

 type: class com.sun.javafx.fxml.builder.ProxyBuilder

So type is not your NestablePropertyValueFactory class, but a ProxyBuilder.

Now, if you check that class there is no @DefaultProperty annotation, hence defaultProperty is null and the exception is thrown.

This proxy is created in javafx.fxml.JavaFXBuilderFactory::getBuilder, based on the real type:

    if (scanForConstructorAnnotations(type)) {
        builder = new ProxyBuilder(type);
    }

but it returns the ProxyBuilder instead, and in the process the DefaultProperty annotation gets lost.

The proxy builder seems intended for classes with constructors annotated with @NamedArg, but not for the @DefaultProperty annotation.

While this could be the case of your class, there should be a better explanation than the short exception message.

Anyway, as the builder works fine when you add the tags and remove the default annotation, I would just consider removing @DefaultProperty.

José Pereda
  • 44,311
  • 7
  • 104
  • 132
  • Thank you for your very detailed response! That makes total sense. I can live with specifying the property name - I just really wanted to know why this didn't work, knowing that I will likely try to use it again in the future. Thanks again for the response! – Andy Senn Apr 18 '17 at 20:10