JavaFX - 无法将对象强制转换为 SimpleListProperty

JavaFX - Unable to coerce Object to SimpleListProperty

我正在尝试确定是否有一种方法可以在不包含 FXCollections 层的情况下从 fxml 加载 ListProperty,例如 SimpleListProperty<InteractionDefinition>。有什么方法可以让案例 1 像案例 2 一样运行吗?

public class AppMenuItem extends MenuItem {
    private SimpleListProperty<InteractionDefinition> interactions = new SimpleListProperty<>();

    public void setInteractions(ObservableList<InteractionDefintion> interactionsTmp);
}

案例一

<AppMenuItem id="AppMenuItem2" menuText="View 2"  fx:id="asdDefaultView2MenuItem">
   <interactions>
        <InteractionDefinition action="actionName" button="ACTION" device="MOUSE" event="CLICK" />
        <InteractionDefinition action="actionName" button="ACTION" device="MOUSE" event="CLICK" />
        <InteractionDefinition action="actionName" button="ACTION" device="MOUSE" event="CLICK" />
   </interactions>
</AppMenuItem >

案例二

<AppMenuItem id="AppMenuItem2" menuText="View 2"  fx:id="asdDefaultView2MenuItem">
    <interactions>
        <FXCollections fx:factory="observableArrayList">
            <InteractionDefinition action="actionName" button="ACTION" device="MOUSE" event="CLICK" />
            <InteractionDefinition action="actionName" button="ACTION" device="MOUSE" event="CLICK" />
            <InteractionDefinition action="actionName" button="ACTION" device="MOUSE" event="CLICK" />
        </FXCollections>
    </interactions>
</AppMenuItem >

回答

问题是 setter 方法的存在。在 FXML 简介 文档的 Property Elements 部分,您将看到:

Property Elements

Elements whose tag names begin with a lowercase letter represent object properties. A property element may represent one of the following:

  • A property setter
  • A read-only list property
  • A read-only map property

Property Setters

If an element represents a property setter, the contents of the element (which must be either a text node or a nested class instance element) are passed as the value to the setter for the property.

...

Read-Only List Properties

A read-only list property is a Bean property whose getter returns an instance of java.util.List and has no corresponding setter method. The contents of a read-only list element are automatically added to the list as they are processed.

...

FXMLLoader 处理 <interactions> 标签时,它将尝试使用标签中定义的对象更新相应的 属性。它如何更新 属性 受上述文档中指定的规则的约束——其中一些已在上面引用。由于您有一个 setter 方法(以 setInteractions(ObservableList) 的形式),它将尝试将标记中定义的对象强制转换为 ObservableList 并使用 setter 方法;这是因为 属性 元素被确定为 "property setter" 而不是 "read-only list property"。因此,案例 1 失败,因为您没有定义 ObservableList,而是定义了多个 InteractionDefinition

如果您想使用 案例 1,您需要将 iteractions 属性 设为只读并添加 getter方法。 属性 只需要从 FXMLLoader 的角度来看是只读的。换句话说,只要您没有遵循标准 Java Bean 约定的 setter 方法,FXMLLoader 就会假定 属性 是只读的。

至少,将您的代码更改为以下内容将允许您使用案例 1

public class AppMenuItem extends MenuItem {

    private SimpleListProperty<InteractionDefinition> interactions = new SimpleListProperty<>();

    public ObservableList<InteractionDefinition> getInteractions() {
        return iteractions.get();
    }

}

如果您决定将 属性 设为完全只读,您可以使用 ReadOnlyListWrapper。或者,如果您不需要完整的 JavaFX 属性,您可以直接使用 ObservableList


矛盾?

然而,有些事情似乎与刚才所说的一切相矛盾。例如,如果您定义了一个 ListView,在 FXML 中您还可以为其 items 属性 定义元素。此 属性 不是 "read-only list property",因为它定义了 setter(因为 itemsObjectProperty<ObservableList<T>>)。尽管如此,以下仍然有效:

<ListView>
    <String fx:value="Item #1"/>
</ListView>

您可能会遇到强制转换错误,因为存在 setter 并且需要 ObservableList,而不是 String。有趣的是,如果您稍微更改 FXML,您 得到错误。

<ListView>
    <items>
        <String fx:value="Item #1"/>
    </items>
</ListView>

现在抛出一个错误。那么给出了什么?我能发现的唯一区别是 @DefaultProperty 的使用。如果指定了默认 属性 并且您 在 FXML 文件中显式使用元素标记(例如 <items>),那么它将表现为 "read-only list property"(如果默认 属性 是或包含 List)而不是 "property setter"。一旦您 do 明确使用元素标签(例如 <items>),那么它的行为就像本答案第一部分中描述的那样。 我找不到明确描述此行为的文档,这可能意味着它是一个错误。另外,请注意我只在 Java 10.0.2 上尝试过此操作。

如果你想依赖这个,并且由于 MenuItem 没有 DefaultProperty,你可以尝试使用以下内容:

Java代码:

@DefaultProperty("interactions")
public class AppMenuItem extends MenuItem {

    private final ListProperty<InteractionDefintion> interactions = new SimpleListProperty<>(this, "interactions");

    public final void setInteractions(ObservableList<InteractionDefinition> interactions) {
        this.interactions.set(interactions);
    }

    public final ObservableList<InteractionDefinition> getInteractions() {
        return interactions.get();
    }

    public final ListProperty<InteractionDefinition> interactionsProperty() {
        return interactions;
    }

}

FXML 文件:

<AppMenuItem id="AppMenuItem2" menuText="View 2"  fx:id="asdDefaultView2MenuItem">
    <InteractionDefinition action="actionName" button="ACTION" device="MOUSE" event="CLICK" />
    <InteractionDefinition action="actionName" button="ACTION" device="MOUSE" event="CLICK" />
    <InteractionDefinition action="actionName" button="ACTION" device="MOUSE" event="CLICK" />
</AppMenuItem >