JavaFx: как правильно запускать updateItem в TableCell

Мне нужно реализовать множество пользовательских 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 не вызывается, если оно изменяется, поэтому мне нужно как-то слушать. Мне нужно, чтобы updateItem срабатывал после изменения цвет. Это приведет к одному вызову содержимого слушателя.

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

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
0
1 814
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

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

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

    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"));

а затем в геттере и сеттере вашей модели обновите такое свойство.

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

Что-то я не до конца понимаю. Bindings.createObjectBinding(..) находится в блоке else, и в javadoc говорится, что привязка использует слабый прослушиватель. У кого есть ссылка на такую ​​привязку?

minus 30.05.2019 14:39

@minus Сам ObjectBinding хранится в строгой ссылке внутри свойства textFill. Поскольку TableCell имеет сильную ссылку на это свойство, привязка строго достижима. Однако слушатель, который привязка добавляет к зависимостям Observable, слаб, как и слушатель, который свойство добавляет к ObservableValue, переданному bind(...).

Slaw 30.05.2019 14:43

@minus Обратите внимание, что слабые прослушиватели предназначены для предотвращения хранения объект, который добавил слушателя в памяти только потому, что объект, к которому был добавлен слушатель строго доступен.

Slaw 30.05.2019 14:53

Сначала я не хотел идти по пути привязки, потому что в моем случае это сложнее, чем просто установить цвет, есть несколько проверок, и я использую TextFlow вместо Text, у которого много Text в дочерних элементах, но, наконец, я удалось пойти на это и теперь его совершенный ist не называют более чем необходимо. Я думаю, что приму решение минуса, так как он был первым, и в основном это то же самое предложение. Надеюсь, это нормально для вас ;)

Sunflame 30.05.2019 15:50

@Sunflame Конечно, нет проблем. Кроме того, если ваше представление начинает сильно усложняться, подумайте о том, чтобы переместить его в отдельный класс. Затем установите graphic как экземпляр этого класса. И обратите внимание, что ваш пример каждый раз создает новый экземпляр Text; рассмотрите возможность кэширования объектов представления, чтобы избежать создания и выбрасывания сотен из них при прокрутке пользователем.

Slaw 30.05.2019 16:11

Конечно, я не создаю каждый раз новый экземпляр в updateItem, но я его использую повторно, в моем примере я просто поторопился и не обратил на это должного внимания.

Sunflame 30.05.2019 16:33

@Sunflame Думаю, я упомяну об этом на всякий случай (может также помочь другим, кто придет).

Slaw 30.05.2019 16:41

Другие вопросы по теме