JavaFx:如何正确触发TableCell中的updateItem

时间:2019-05-30 10:35:40

标签: java javafx tableview javafx-8 listener

我必须实现许多自定义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>

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

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

2 个答案:

答案 0 :(得分: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()的调用将防止内存泄漏。并且在绑定属性时,先前的绑定(如果有)将被删除。如果您确实很偏执,也可以在unbind()之前致电bind(...)。而且,如果您确实是真正的偏执狂,则可以将ObjectBinding存储在一个字段中,并在适当时调用dispose()(甚至将其清空)。

答案 1 :(得分:1)

我想你可以反过来做。

我将创建一个如下的color属性:

    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中更新此类属性。