JavaFx: как правильно запустить updateItem в TableCell - PullRequest
0 голосов
/ 30 мая 2019

Мне нужно реализовать множество пользовательских таблиц, поведение которых зависит от изменения модели. Мне удалось каким-то образом получить ожидаемый результат, но я думаю, что во многих случаях это был обходной путь, а скорее действительно хорошее решение. Я использовал привязки / прослушиватели для достижения ожидаемого результата, но проблема, с которой я сталкиваюсь, заключается в том, что я могу добавлять прослушиватели / привязывать свойства несколько раз, и это может привести к утечкам памяти.

Вот пример того, что я имею в виду.

Контроллер:

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 ]

2 голосов
/ 30 мая 2019

Использование слушателей и привязок не вызовет никаких проблем, если вы не забудете их удалить, когда они больше не нужны. Чтобы сделать его еще более безопасным, вы должны использовать слабых слушателей (привязки используют слабых слушателей). Поскольку вы хотите изменить цвет текста ячейки, основываясь на другом свойстве элемента строки, я думаю, что использовать привязку будет проще. Обратите внимание, что 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 голос
/ 30 мая 2019

Полагаю, вы могли бы сделать это наоборот.

Я бы создал свойство цвета следующим образом:

    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 вашей модели обновите такое свойство.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...