FXML 中的动态属性值?

Dynamic attribute values in FXML?

this JavaFX tutorial中,建议创建地址簿应用程序。可以删除已注册的人,但是在删除之前,必须在 table 视图中选择此人。

There will be an ArrayIndexOutOfBoundsException because it could not remove a person item at index -1. The index -1 was returned by getSelectedIndex() - which means that there was no selection.

To ignore such an error is not very nice, of course. We should let the user know that he/she must select a person before deleting. (Even better would be if we disabled the button so that the user doesn’t even have the chance to do something wrong.)

作者"Even better would be if we disabled the button ..."完全正确,但他选择了第一种方式。我想,按钮状态的操作是 JavaFX 应用程序开发的基本技能,所以我尝试实现更好的解决方案。

当然也可以这样:

peopleTable.getSelectionModel().selectedItemProperty().addListener(
  (observable, oldValue, newValue) -> {

    showPersonDetails(newValue);

    boolean somebodySelected = peopleTable.getSelectionModel().getSelectedIndex() >= 0;
    button.setDisable(!somebodySelected);
  }
);

我怎么对另一种方式感兴趣:对按钮使用动态属性值:

<Button
  mnemonicParsing="false"
  onAction="#handleDeletePerson"
  text="Delete"
  disable="disableDeleteButtonFlag"
/>

如果可以使用动态属性值,则不再需要显式调用 button.setDisable()。但是,下面的代码不起作用。

@FXML
private boolean disableDeleteButtonFlag = true;

// ...


@FXML
private void initialize() {


  // ...

  peopleTable.getSelectionModel().selectedItemProperty().addListener(
      (observable, oldValue, newValue) -> {
        showPersonDetails(newValue);
        disableDeleteButtonFlag = peopleTable.getSelectionModel().getSelectedIndex() < 0;
      }
  );
}

首先,我不确定以这种方式引用 boolean 字段是否会让 FXML 文件加载(您没有具体说明它是如何 "doesn't work")。但是忽略这一点,字段在 Java 中是不可观察的,这意味着更新字段不会自动导致 Buttondisable 属性 被更新。这就是 JavaFX 具有 [ReadOnly]Property 接口并引入“JavaFX Bean”([= 的扩展)的原因52=] Bean 添加了一个"property getter");它允许观察者看到对象属性的变化。请注意,您可以将 bind a writable property 转换为 ObservableValue,因此它始终具有相同的值。

现在,我原以为 expression binding 会是您要查找的内容,但以下内容:

<ListView fx:id="list"/>
<Button disable="${list.selectionModel.selectedIndex == -1}"/>

似乎不​​起作用——我得到一个异常(使用 JavaFX 12.0.1):

Caused by: java.lang.ClassCastException: class java.lang.Long cannot be cast to class java.lang.Integer (java.lang.Long and java.lang.Integer are in module java.base of loader 'bootstrap')
    at java.base/java.lang.Integer.compareTo(Integer.java:64)
    at javafx.fxml/com.sun.javafx.fxml.expression.Expression.lambda$equalTo(Expression.java:1105)
    at javafx.fxml/com.sun.javafx.fxml.expression.BinaryExpression.evaluate(BinaryExpression.java:55)
    at javafx.fxml/com.sun.javafx.fxml.expression.ExpressionValue.getValue(ExpressionValue.java:192)
    at javafx.base/com.sun.javafx.binding.ExpressionHelper.addListener(ExpressionHelper.java:53)
    at javafx.base/javafx.beans.value.ObservableValueBase.addListener(ObservableValueBase.java:55)
    at javafx.fxml/com.sun.javafx.fxml.expression.ExpressionValue.addListener(ExpressionValue.java:201)
    at javafx.base/javafx.beans.binding.BooleanBinding.bind(BooleanBinding.java:106)
    at javafx.base/javafx.beans.property.BooleanPropertyBase$ValueWrapper.<init>(BooleanPropertyBase.java:254)
    at javafx.base/javafx.beans.property.BooleanPropertyBase.bind(BooleanPropertyBase.java:168)
    at javafx.fxml/javafx.fxml.FXMLLoader$Element.processPropertyAttribute(FXMLLoader.java:326)
    at javafx.fxml/javafx.fxml.FXMLLoader$Element.processInstancePropertyAttributes(FXMLLoader.java:242)
    at javafx.fxml/javafx.fxml.FXMLLoader$ValueElement.processEndElement(FXMLLoader.java:775)
    at javafx.fxml/javafx.fxml.FXMLLoader.processEndElement(FXMLLoader.java:2838)
    at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2557)
    ... 17 more

使用 selectedItem 属性 代替:

<Button disable="${list.selectionModel.selectedItem == null}"/>

给出不同的异常:

Caused by: java.lang.NullPointerException
    at javafx.fxml/com.sun.javafx.fxml.expression.Expression.lambda$equalTo(Expression.java:1105)
    at javafx.fxml/com.sun.javafx.fxml.expression.BinaryExpression.evaluate(BinaryExpression.java:55)
    at javafx.fxml/com.sun.javafx.fxml.expression.ExpressionValue.getValue(ExpressionValue.java:192)
    at javafx.base/com.sun.javafx.binding.ExpressionHelper.addListener(ExpressionHelper.java:53)
    at javafx.base/javafx.beans.value.ObservableValueBase.addListener(ObservableValueBase.java:55)
    at javafx.fxml/com.sun.javafx.fxml.expression.ExpressionValue.addListener(ExpressionValue.java:201)
    at javafx.base/javafx.beans.binding.BooleanBinding.bind(BooleanBinding.java:106)
    at javafx.base/javafx.beans.property.BooleanPropertyBase$ValueWrapper.<init>(BooleanPropertyBase.java:254)
    at javafx.base/javafx.beans.property.BooleanPropertyBase.bind(BooleanPropertyBase.java:168)
    at javafx.fxml/javafx.fxml.FXMLLoader$Element.processPropertyAttribute(FXMLLoader.java:326)
    at javafx.fxml/javafx.fxml.FXMLLoader$Element.processInstancePropertyAttributes(FXMLLoader.java:242)
    at javafx.fxml/javafx.fxml.FXMLLoader$ValueElement.processEndElement(FXMLLoader.java:775)
    at javafx.fxml/javafx.fxml.FXMLLoader.processEndElement(FXMLLoader.java:2838)
    at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2557)
    ... 17 more

由于这两个异常都来自 JavaFX 代码(即不是我们的代码),因此这种行为可能是一个错误。那,或者我错误地使用了表达式绑定——在这种情况下,我希望有人纠正我。由于尝试在 FXML 中绑定 属性 无效,因此解决方法是在代码中绑定 属性。

<VBox xmlns="http://javafx.com/" xmlns:fx="http://javafx.com/fxml" fx:controller="Controller">
    <ListView fx:id="list"/>
    <Button fx:id="button"/>
</VBox>
public class Controller {

    @FXML private ListView<YourType> list;
    @FXML private Button button;

    @FXML
    private void initialize() {
        button.disableProperty()
                .bind(list.getSelectionModel().selectedIndexProperty().isEqualTo(-1));
    }

}

只要 selectedIndex 属性 成立 -1isEqualTo 方法就会 return 一个值为 trueBooleanBinding . disable 属性 然后绑定到这个 BooleanBinding 所以它总是有相同的值。您可以阅读 Using JavaFX Properties and Binding 了解更多信息。