简体   繁体   English

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

[英]JavaFx: How to properly fire updateItem in a TableCell

I have to implement a lot of custom TableCell which behavior relies on the model's change. 我必须实现许多自定义TableCell,其行为取决于模型的更改。 I could manage to get somehow the expected result, but I think in many cases it was a workaround rather a really good solution. 我可以设法以某种方式获得预期的结果,但是我认为在许多情况下,这是一个解决方法,但却是一个非常好的解决方案。 I have used bindings/listeners to achieve the expected result, but the problem I face is that I may add the listeners/bind the properties multiple times and it can create memory leaks. 我已经使用绑定/侦听器来达到预期的结果,但是我面临的问题是我可能多次添加侦听器/绑定属性,这会导致内存泄漏。

Here is an example what I mean. 这是我的意思的例子。

Controller: 控制器:

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: 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>

Since the color property isn't directly observed by the cell the updateItem is not called if it changes, so I have to listen to somehow. 由于单元格未直接观察到color属性,因此如果更改了updateItem,则不会调用它,因此我必须以某种方式进行监听。 I would need that the updateItem to be triggered after the color is changed. 我需要在更改颜色后触发updateItem This would result a single call to the content of the listener. 这将导致对侦听器内容的单个调用。

Is there any way to listen to another change of the model in the same cell, or call the update item somehow, so the change gets rendered. 有什么方法可以监听同一单元格中模型的另一个更改,或以某种方式调用更新项,以便呈现更改。

Using listeners and bindings won't cause any issues so long as you remember to remove them when they're no longer needed. 只要您记得在不再需要它们时将其删除,则使用侦听器和绑定不会引起任何问题。 To make it even safer, you should use weak listeners (bindings use weak listeners). 为了使其更加安全,您应该使用弱监听器(绑定使用弱监听器)。 As you want to change the cell's text's color based on a different property of the row's item, I think using a binding will be easier. 当您想根据行项目的不同属性更改单元格文本的颜色时,我认为使用绑定会更容易。 Note that TableCell inherits from Labeled which means it has a textFill property; 注意, TableCell继承自Labeled ,这意味着它具有textFill属性。 there's no need to create a Text node to change the color of the text. 无需创建Text节点即可更改Text的颜色。

Here's an example: 这是一个例子:

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()
            ));
        }
    }

}

The call to textFillProperty().unbind() will prevent a memory leak. 调用textFillProperty().unbind()将防止内存泄漏。 And when binding a property the previous binding, if any, will be removed. 并且在绑定属性时,先前的绑定(如果有)将被删除。 If you're really paranoid you can call unbind() before bind(...) as well. 如果您真的很偏执,也可以在bind(...)之前调用unbind() And if you're really, really paranoid then you can store the ObjectBinding in a field and call dispose() when appropriate (and even null it out). 而且,如果您确实非常偏执,则可以将ObjectBinding存储在字段中,并在适当时调用dispose() (甚至将其ObjectBinding )。

I guess you could do it the other way around. 我想你可以反过来做。

I would create a color property like this: 我将创建一个color属性,如下所示:

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

Then I would bind the property like that: 然后,我将像这样绑定属性:

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

It would be even simpler if you just had: 如果您只有:

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

and then in getter and setter of your model update such property. 然后在模型的getter和setter中更新此类属性。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM