简体   繁体   English

带有java.time.Duration的构造方法的自定义JavaFX组件

[英]Custom JavaFX Component with Constructor That Takes a java.time.Duration

I am attempting to create a Spinner<Duration> object within FXML. 我正在尝试在FXML中创建Spinner<Duration>对象。 I have created a DurationSpinnerValueFactory that extends SpinnerValueFactory<Duration> . 我创建了一个DurationSpinnerValueFactory ,它扩展了SpinnerValueFactory<Duration> I have defined a default constructor for DurationSpinnerValueFactory , as well as a constructor that takes a maximum allowed value. 我已经为DurationSpinnerValueFactory定义了一个默认的构造函数,以及一个接受最大允许值的构造函数。 The constructors are defined as follows: 构造函数定义如下:

import java.time.Duration;
...

public class DurationSpinnerValueFactory extends SpinnerValueFactory<Duration> {

    public DurationSpinnerValueFactory() {
        this(null);
    }

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

}

Within the FXML, the following is working as expected (calling the default constructor): 在FXML中,以下各项按预期方式工作(调用默认构造函数):

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

However, when I try to add a value for the max property, thus changing the called constructor, I get an error. 但是,当我尝试为max属性添加一个值,从而更改调用的构造函数时,出现错误。 The following illustrates the FXML change: 下面说明了FXML的更改:

<DurationSpinnerValueFactory max="PT10M" />

The error I am getting is: 我得到的错误是:

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

I know that I could change the type of max in the constructor to a String , then parse the value within the constructor. 我知道我可以将构造函数中的max类型更改为String ,然后解析构造函数中的值。 However, I would prefer to avoid doing that since I know the type of max should be Duration . 但是,我宁愿避免这样做,因为我知道max的类型应为Duration Is there a way to get FXMLLoader.load(...) to be able to parse Duration objects from within the FXML? 有没有办法让FXMLLoader.load(...)能够从FXML内解析Duration对象?

Solution

Try using "10m" instead of "PT10M" as the string representation of your Duration and consistently using javafx.util.Duration to represent all of the duration related types that are associated with code primarily targeted to service a JavaFX GUI. 尝试使用“ 10m”代替“ PT10M”作为Duration的字符串表示形式,并始终使用javafx.util.Duration来表示与主要与服务JavaFX GUI的代码相关联的所有持续时间相关类型。

Explanatory Background Info 解释性背景信息

You are getting confused about your Duration types and formats. 您对持续时间类型和格式感到困惑。

The "PT10M" string that you supply is a string representation of the following type: 您提供的“ PT10M”字符串是以下类型的字符串表示形式:

java.time.Duration

However, the Duration used in your DurationSpinnerValueFactory should probably be the following type: 但是,在DurationSpinnerValueFactory中使用的Duration可能应该是以下类型:

javafx.util.Duration

What the FXMLLoader is doing, when you look at its code, is trying to invoke a static valueOf function on Duration . 当您查看FXMLLoader的代码时,它正在尝试在Duration上调用静态valueOf函数。 For example, for javafx.util.Duration : 例如,对于javafx.util.Duration

javafx.util.Duration.valueOf

When you look at the documentation for that function, it states that the valid input should be: 当您查看该功能的文档时,它指出有效的输入应为:

The syntax is "[number][ms|s|m|h]". 语法为“ [数字] [ms | s | m | h]”。

Which is clearly, not what you are using. 显然,这不是您所使用的。 Instead, you are using a java.time.Duration.parse() format. 相反,您使用的是java.time.Duration.parse()格式。

Coding up the following statement and running it throws an exception: 编写以下语句并运行它会引发异常:

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)

However, if you just code it as below, it executes fine: 但是,如果仅按以下方式进行编码,则可以很好地执行:

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

I would think there would be some way to tell an FXMLLoader how to parse custom types from text within the FXML. 我认为可以通过某种方式告诉FXMLLoader如何从FXML中的文本解析自定义类型。

Yes, you can do that, it's not officially documented anyway though and I haven't tried it myself. 是的,您可以执行此操作,尽管该文件尚未正式记录下来,但我自己也没有尝试过。 From looking at how javafx.util.Duration is handled, you could supply a wrapper type or subclass of java.time.Duration that implements a static valueOf function. 通过查看如何处理javafx.util.Duration,可以提供实现静态valueOf函数的java.time.Duration包装类型或子类。

As another option, I think a builder class can be associated with certain class types to allow them to be resolved and parsed within the context of an FXMLLoader. 作为另一种选择,我认为可以将构建器类与某些类类型相关联,以允许它们在FXMLLoader的上下文中进行解析和解析。 I haven't looked into the builder type solution, but it probably relies either on reflection or some APIs exposed in the FXMLLoader. 我没有研究过构建器类型的解决方案,但是它可能依赖于反射或FXMLLoader中公开的某些API。 See, for example, the FXMLLoader APIs that take builder factories as parameters . 例如,请参阅以构建器工厂为参数FXMLLoader API There is absolutely no documentation of how this approach works as far as I know, so you will probably need to rely on examining the FXMLLoader code and some example builders that may ship with the JavaFX runtime to support use of the in-built JavaFX controls within FXML. 据我所知,绝对没有关于这种方法如何工作的文档,因此您可能需要依靠检查FXMLLoader代码和JavaFX运行时可能附带的一些示例构建器来支持在内部使用内置JavaFX控件。 FXML。

My recommendation to you is to change your custom component to work with javafx.util.Duration rather than java.time.Duration, then you don't have to start writing additional code to retrofit different duration types and additionally, you don't end up confusing the heck out of yourself and anybody else trying to use the control. 我对您的建议是更改自定义组件以使其与javafx.util.Duration而不是与java.time.Duration一起使用,这样就不必开始编写其他代码来改型不同的持续时间类型,而且您不必结束弄乱了自己和其他尝试使用该控件的人。

Thanks to @jewelsea for pointing me to the FXMLLoader code. 感谢@jewelsea向我指出FXMLLoader代码。 After reviewing the code, it became apparent that there is no way to add custom parsers to the FXML load process. 在检查了代码之后,很明显没有办法向FXML加载过程添加自定义解析器。 However, the default action for any unknown type T is to reflectively call a static method with the signature public static T valueOf(String value) . 但是,任何未知类型T的默认操作是使用签名public static T valueOf(String value)反射性地调用静态方法。 Therefore, I simply created the following wrapper class for java.time.Duration : 因此,我只是为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;
    }

}

Then, I modified my DurationSpinnerValueFactory constructors: 然后,我修改了DurationSpinnerValueFactory构造函数:

public class DurationSpinnerValueFactory extends SpinnerValueFactory<Duration> {

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

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

}

I still need to verify that FXMLLoader.load(...) is guaranteed to select the constructor that takes a DurationWrapper over the one that takes a Duration , but it is working on Windows 7 Enterprise SP1 x64 running JRE 8u77 x64. 我仍然需要验证FXMLLoader.load(...)保证来选择需要的构造DurationWrapper在一个需要Duration ,但它正在开发Windows 7企业版SP1 64位运行JRE 8u77 64。 If not, then I will just make the constructor that takes a Duration object non-public. 如果不是,那么我将使采用Duration对象的构造函数成为非公共对象。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM