使用构造函数的自定义JavaFX组件,它采用java.time.Duration

时间:2016-05-17 23:48:06

标签: java javafx javafx-8

我正在尝试在FXML中创建一个Spinner<Duration>对象。我创建了DurationSpinnerValueFactory,扩展了SpinnerValueFactory<Duration>。我已经为DurationSpinnerValueFactory定义了一个默认构造函数,以及一个获取最大允许值的构造函数。构造函数定义如下:

import java.time.Duration;
...

public class DurationSpinnerValueFactory extends SpinnerValueFactory<Duration> {

    public DurationSpinnerValueFactory() {
        this(null);
    }

    public DurationSpinnerValueFactory(@NamedArg("max") final Duration max) {
        ...
    }

}

在FXML中,以下工作正常(调用默认构造函数):

...
<?import myNamespace.DurationSpinnerValueFactory?>
...
<Spinner fx:id="mySpinner">
  <valueFactory>
    <DurationSpinnerValueFactory />
  </valueFactory>
</Spinner>
...

但是,当我尝试为max属性添加值,从而更改被调用的构造函数时,我收到错误。以下说明了FXML的变化:

<DurationSpinnerValueFactory max="PT10M" />

我得到的错误是:

javafx.fxml.LoadException: 
unknown path:23

    at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2601)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2579)
    at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2425)
    ...
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$162(LauncherImpl.java:863)
    at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$175(PlatformImpl.java:326)
    at com.sun.javafx.application.PlatformImpl.lambda$null$173(PlatformImpl.java:295)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$174(PlatformImpl.java:294)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191)
    at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.RuntimeException: java.lang.IllegalArgumentException: Unable to coerce PT10M to class java.time.Duration.
    at com.sun.javafx.fxml.builder.ProxyBuilder.createObjectFromDefaultConstructor(ProxyBuilder.java:340)
    at com.sun.javafx.fxml.builder.ProxyBuilder.build(ProxyBuilder.java:223)
    at javafx.fxml.FXMLLoader$ValueElement.processEndElement(FXMLLoader.java:763)
    at javafx.fxml.FXMLLoader.processEndElement(FXMLLoader.java:2823)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2532)
    ... 18 more
Caused by: java.lang.IllegalArgumentException: Unable to coerce PT10M to class java.time.Duration.
    at com.sun.javafx.fxml.BeanAdapter.coerce(BeanAdapter.java:496)
    at com.sun.javafx.fxml.builder.ProxyBuilder$Setter.invoke(ProxyBuilder.java:533)
    at com.sun.javafx.fxml.builder.ProxyBuilder.createObjectFromDefaultConstructor(ProxyBuilder.java:338)
    ... 22 more

我知道我可以将构造函数中max的类型更改为String,然后解析构造函数中的值。但是,我更愿意避免这样做,因为我知道max的类型应该是Duration。有没有办法让FXMLLoader.load(...)能够从FXML中解析Duration个对象?

2 个答案:

答案 0 :(得分:1)

<强>解决方案

尝试使用&#34; 10m&#34;而不是&#34; PT10M&#34;作为持续时间的字符串表示形式,并始终使用javafx.util.Duration来表示与主要用于服务JavaFX GUI的代码相关联的所有持续时间相关类型。

解释性背景资料

您对持续时间类型和格式感到困惑。

&#34; PT10M&#34;您提供的字符串是以下类型的字符串表示形式:

java.time.Duration

但是,您的DurationSpinnerValueFactory中使用的持续时间应该是以下类型:

javafx.util.Duration

FXMLLoader在查看代码时正在尝试调用valueOf上的静态Duration函数。例如,对于javafx.util.Duration

javafx.util.Duration.valueOf

当您查看该函数的文档时,它指出有效输入应为:

  

语法为&#34; [number] [ms | s | m | h]&#34;。

显然,这不是你正在使用的。相反,您使用的是java.time.Duration.parse()格式。

编写以下语句并运行它会引发异常:

javafx.util.Duration.valueOf("PT10M");

Exception in thread "main" java.lang.NumberFormatException: empty String
    at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1842)
    at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
    at java.lang.Double.parseDouble(Double.java:538)
    at javafx.util.Duration.valueOf(Duration.java:85)

但是,如果您只是按如下方式编写代码,则执行正常:

javafx.util.Duration.valueOf("10m");
  

我认为可以通过某种方式告诉FXMLLoader如何从FXML中的文本中解析自定义类型。

是的,你可以这样做,虽然它还没有正式记录,但我自己也没有尝试过。通过查看如何处理javafx.util.Duration,您可以提供实现静态valueOf函数的java.time.Duration的包装类型或子类。

作为另一种选择,我认为构建器类可以与某些类类型相关联,以允许在FXMLLoader的上下文中解析和解析它们。我没有查看构建器类型解决方案,但它可能依赖于反射或FXMLLoader中公开的某些API。例如,请参阅FXMLLoader APIs that take builder factories as parameters。据我所知,绝对没有关于此方法如何工作的文档,因此您可能需要依赖于检查FXMLLoader代码以及可能随JavaFX运行时一起提供的一些示例构建器,以支持在内部使用内置JavaFX控件FXML。

我建议您更改自定义组件以使用javafx.util.Duration而不是java.time.Duration,然后您不必开始编写其他代码来改进不同的持续时间类型,此外,你不会混淆自己和其他试图使用控件的人。

答案 1 :(得分:1)

感谢@jewelsea指出我的FXMLLoader代码。在查看代码之后,很明显没有办法在FXML加载过程中添加自定义解析器。但是,任何未知类型T的默认操作都是反射性地调用带有签名public static T valueOf(String value)的静态方法。因此,我只是为java.time.Duration创建了以下包装类:

public class DurationWrapper {

    private final Duration myValue;

    private DurationWrapper(final Duration value) {
        myValue = value;
    }

    public static DurationWrapper valueOf(final String value) {
        final Duration duration = Duration.parse(value);
        final DurationWrapper result = new DurationWrapper(duration);
        return result;
    }

    public Duration getValue() {
        return myValue;
    }

}

然后,我修改了DurationSpinnerValueFactory构造函数:

public class DurationSpinnerValueFactory extends SpinnerValueFactory<Duration> {

    public DurationSpinnerValueFactory(
            @NamedArg("max") final DurationWrapper max) {
        this(max.getValue());
    }

    public DurationSpinnerValueFactory(final Duration max) {
        ...
    }

}

我仍然需要验证FXMLLoader.load(...)是否可以保证选择DurationWrapper占用Duration的构造函数,但它适用于Windows 7 Enterprise SP1 x64运行JRE 8u77 x64。如果没有,那么我将只使得Duration对象非公开的构造函数。