【问题标题】:JavaFx: How to properly fire updateItem in a TableCellJavaFx:如何在 TableCell 中正确触发 updateItem
【发布时间】:2019-10-16 00:13:38
【问题描述】:

我必须实现很多自定义的 TableCell 行为依赖于模型的变化。我可以设法以某种方式获得预期的结果,但我认为在很多情况下这是一种解决方法,而不是一个非常好的解决方案。 我已经使用绑定/监听器来达到预期的结果,但我面临的问题是我可能会多次添加监听器/绑定属性,这会造成内存泄漏。

这是我的意思的一个例子。

控制器:

public class Controller implements Initializable {

    @FXML private TableView<Model> table;
    @FXML private TableColumn<Model, String> column;
    @FXML private Button change;

    @Override
    public void initialize(URL location, ResourceBundle resources) {

        column.setCellValueFactory(data -> data.getValue().text);
        column.setCellFactory(cell -> new ColoredTextCell());

        Model apple = new Model("Apple", "#8db600");

        table.getItems().add(apple);
        table.getItems().add(new Model("Banana", "#ffe135"));

        change.setOnAction(event -> apple.color.setValue("#ff0800"));

    }

    @Getter
    private class Model {
        StringProperty text;
        StringProperty color;

        private Model(String text, String color) {
            this.text = new SimpleStringProperty(text);
            this.color = new SimpleStringProperty(color);
        }
    }

    private class ColoredTextCell extends TableCell<Model, String> {

        @Override
        protected void updateItem(String item, boolean empty) {
            super.updateItem(item, empty);
            if (empty || getTableRow() == null || getTableRow().getItem() == null) {
                setGraphic(null);
                return;
            }
            Model model = (Model) getTableRow().getItem();
            Text text = new Text(item);
            text.setFill(Color.web(model.getColor().getValue()));

            // This way I add the listener evey item updateItem is called.
            model.getColor().addListener((observable, oldValue, newValue) -> {
                if (newValue != null) {
                    text.setFill(Color.web(newValue));
                } else {
                    text.setFill(Color.BLACK);
                }
            });
            setGraphic(text);
        }
    }

}

FXML:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Button?>
<AnchorPane xmlns="http://javafx.com/javafx"
            xmlns:fx="http://javafx.com/fxml"
            fx:controller="stackoverflow.tabpane.Controller">
    <VBox>
        <Button fx:id="change" text="Change color"/>
        <TableView fx:id="table">
            <columns>
                <TableColumn fx:id="column" prefWidth="200"/>
            </columns>
        </TableView>
    </VBox>
</AnchorPane>

由于单元格没有直接观察到颜色属性,因此如果它发生更改,则不会调用 updateItem,因此我必须以某种方式聆听。 我需要在 color 更改后触发updateItem。这将导致对侦听器内容的一次调用。

有什么方法可以在同一个单元格中监听模型的另一个变化,或者以某种方式调用更新项,以便呈现变化。

【问题讨论】:

    标签: java javafx tableview javafx-8 listener


    【解决方案1】:

    我猜你可以反过来做。

    我会创建一个这样的颜色属性:

        ObjectBinding<Paint> colorProperty = Bindings.createObjectBinding(()->{
            String color = model.getColor().get();
            return Paint.valueOf(color==null?"BLACK":color);
        } , model.getColor());
    

    然后我会像这样绑定属性:

    text.fillProperty().bind(model.colorProperty);
    

    如果你有的话会更简单:

        SimpleObjectProperty<Paint> textColor = new SimpleObjectProperty<Paint>(Paint.valueOf("BLACK"));
    

    然后在模型的 getter 和 setter 中更新此类属性。

    【讨论】:

      【解决方案2】:

      使用侦听器和绑定不会导致任何问题,只要您记得在不再需要它们时删除它们。为了使其更安全,您应该使用弱侦听器(绑定使用弱侦听器)。由于您想根据行项目的不同属性更改单元格文本的颜色,我认为使用绑定会更容易。请注意,TableCell 继承自 Labeled,这意味着它具有 textFill 属性;无需创建Text 节点来更改文本颜色。

      这是一个例子:

      import javafx.beans.binding.Bindings;
      import javafx.scene.control.TableCell;
      import javafx.scene.paint.Color;
      
      public class ColoredTextCell extends TableCell<Model, String> {
      
          @Override
          protected void updateItem(String item, boolean empty) {
              super.updateItem(item, empty);
      
              /*
               * I was getting a NullPointerException without the "getTableRow() == null"
               * check. I find it strange that a TableCell's "updateItem" method would be
               * invoked before it was part of a TableRow... but the added null check seems
               * to solve the problem (at least when only having two items in the table and
               * no scrolling).
               */
              if (empty || item == null || getTableRow() == null) {
                  setText(null);
                  textFillProperty().unbind();
              } else {
                  setText(item);
      
                  Model rowItem = getTableRow().getItem();
                  textFillProperty().bind(Bindings.createObjectBinding(
                          () -> Color.valueOf(rowItem.getColor()),
                          rowItem.colorProperty()
                  ));
              }
          }
      
      }
      

      textFillProperty().unbind() 的调用将防止内存泄漏。并且在绑定属性时,之前的绑定(如果有)将被删除。如果你真的很偏执,你也可以在bind(...) 之前调用unbind()。如果你真的,真的偏执,那么你可以将ObjectBinding存储在一个字段中,并在适当的时候调用dispose()(甚至将其清空)。

      【讨论】:

      • 有些东西我不太明白。 Bindings.createObjectBinding(..) 的范围在 else 块中,javadoc 说绑定使用弱侦听器。谁拥有此类绑定的参考资料?
      • @minus ObjectBinding 本身存储在textFill 属性内的强引用中。由于TableCell 对该属性具有强引用,因此绑定是强可达的。但是,绑定添加到Observable 依赖项的侦听器很弱,属性添加到ObservableValue 的侦听器传递给bind(...)
      • @minus 请注意,弱侦听器旨在防止 添加侦听器的对象仅仅因为添加侦听器的对象而保留在内存中 i> 是强可达的。
      • @Sunflame 当然,没问题。此外,如果您的视图开始变得相当复杂,请考虑将其移至自己的类中。然后将graphic 设置为该类的实例。请注意,您的示例每次都会创建一个新的 Text 实例;考虑缓存视图对象以避免在用户滚动时创建和丢弃数百个视图对象。
      • @Sunflame 以为我会提到它以防万一(也可能会帮助其他人)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-12-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-24
      • 1970-01-01
      相关资源
      最近更新 更多