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(因为 items
是 ObjectProperty<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 >
我正在尝试确定是否有一种方法可以在不包含 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(因为 items
是 ObjectProperty<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 >