Обновление строки таблицы JavaFX - PullRequest
0 голосов
/ 26 сентября 2018

Сценарий, которого я пытаюсь достичь, это:

  1. Всякий раз, когда обновляется конкретный TableCell в TableRow, цвет строки меняется на красный, и через 3 секунды цвет долженавтоматически возвращаться к оригиналу.

ниже: MCVE ,

Основной класс

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.stage.Stage;

public class TestProjectWin10 extends Application {
    private final ObservableList<Element> data = FXCollections.observableArrayList();

    public final Runnable changeValues = () -> {
        int i = 0;
        while (i <= 100000) {
            if (Thread.currentThread().isInterrupted()) {
                break;
            }
            data.get(0).setOccurence(System.currentTimeMillis());
            data.get(0).count();
            i = i + 1;
        }
    };

    private final ExecutorService executor = Executors.newSingleThreadExecutor(runnable -> {
        Thread t = new Thread(runnable);
        t.setDaemon(true);
        return t;
    });

    @Override
    public void start(Stage primaryStage) {

        TableView<Element> table = new TableView<>();
        table.getStylesheets().add(this.getClass().getResource("tableColor.css").toExternalForm());
        table.setEditable(true);

        TableColumn<Element, String> nameCol = new TableColumn<>("Name");
        nameCol.setPrefWidth(200);
        nameCol.setCellValueFactory(cell -> cell.getValue().nameProperty());
        nameCol.setCellFactory((TableColumn<Element, String> param) -> new ColorCounterTableCellRenderer(table));
        table.getColumns().add(nameCol);

        this.data.add(new Element());
        table.setItems(this.data);

        this.executor.submit(this.changeValues);

        Scene scene = new Scene(table, 600, 600);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

Класс элемента:

import java.util.concurrent.atomic.AtomicReference;

import javafx.application.Platform;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

public class Element {
    int x = 0;

    private final StringProperty nameProperty = new SimpleStringProperty("");

    private final AtomicReference<String> name = new AtomicReference<>();

    private final DoubleProperty occurence = new SimpleDoubleProperty();

    public void count() {
        x = x + 1;
        if (name.getAndSet(Integer.toString(x)) == null) {
            Platform.runLater(() -> nameProperty.set(name.getAndSet(null)));
        }
    }

    public void setOccurence(double value) {
        occurence.set(value);
    }

    public String getName() {
        return nameProperty().get();
    }

    public void setName(String name) {
        nameProperty().set(name);
    }

    public StringProperty nameProperty() {
        return nameProperty;
    }

    double getOccurrenceTime() {
        return occurence.get();
    }
}

Код CellFactory:

import java.util.Timer;
import java.util.TimerTask;
import javafx.application.Platform;
import javafx.geometry.Pos;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;

public class ColorCounterTableCellRenderer extends TableCell<Element, String> {

    private final static long MAX_MARKED_TIME = 3000;
    private final static long UPDATE_INTERVAL = 1000;

    private static Timer t = null;
    private final String highlightedStyle = "highlightedRow";

    private final TableView tv;

    public ColorCounterTableCellRenderer(TableView tv) {
        this.tv = tv;
        createTimer();
        setAlignment(Pos.CENTER_RIGHT);
    }

    private void createTimer() {
        if (t == null) {
            t = new Timer("Hightlight", true);
            t.schedule(new TimerTask() {
                @Override
                public void run() {

                    final long currentTime = System.currentTimeMillis();

                    TableRow tr = getTableRow();
                    if (tr.getItem() != null) {

                        if (currentTime - ((Element) tr.getItem()).getOccurrenceTime() > MAX_MARKED_TIME) {
                            Platform.runLater(() -> {
                                tr.getStyleClass().remove(highlightedStyle);
                            });
                        }
                    }
                }
            }, 0, UPDATE_INTERVAL);
        }
    }

    @Override
    protected void updateItem(String item, boolean empty) {
        if (empty || getTableRow() == null || getTableRow().getItem() == null) {
            setText(null);
            return;
        }

        long currentTime = System.currentTimeMillis();

        TableRow<Element> row = getTableRow();
        Element elementRow = row.getItem();

        double occurrenceTime = elementRow.getOccurrenceTime();

        if (currentTime - occurrenceTime < MAX_MARKED_TIME) {
            if (!row.getStyleClass().contains(highlightedStyle)) {
                row.getStyleClass().add(highlightedStyle);
            }
        }

        super.updateItem(item, empty);
        setText(item + "");

    }
}

и файл css tableColor.css

.highlightedRow {
    -fx-background-color: rgba(255,0,0, 0.25);
    -fx-background-insets: 0, 1, 2;
    -fx-background: -fx-accent;
    -fx-text-fill: -fx-selection-bar-text;    
}

В чем проблема..?

  1. Я проверяю, меньше ли разница между текущим временем и временем обновления, меньше 3 секунд - цвет строки - красный (в методе ColorCounterTableCellRenderer - updateItem)

  2. в отдельном таймере (ColorCounterTableCellRenderer), я пытаюсь проверить, превышает ли разница между текущим временем и временем обновления более 3 секунд - Удаление красного цвета.

Но в коде таймера (createTimer - метод): tr.getItem() всегда null и, следовательно, не удаляет красный цвет.

Это правильный способ достичь того, чего я хочу?Почему tr.getItem() возвращает null.

Для проверки : я запустил код и дождался завершения кода executor и проверил, удаляется ли красный цвет через 3 секунды.

Ответы [ 2 ]

0 голосов
/ 27 сентября 2018

Я согласен с комментарием @kleopatra.Вы не можете выполнять сложную обработку данных внутри ячейки.В основном ваш Row / Cell / updateItem () должен больше фокусироваться на том, «как / что визуализировать». Я могу предложить вам несколько ключевых направлений, на которые вы можете взглянуть.

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

Установка ListChangeListener только для ObservableList приведет кне уведомлять вас о любых изменениях, произошедших «в пределах» свойств. Вы должны зарегистрировать свой ObservableList со свойствами, которые вас интересуют, и должны быть уведомлены при обновлении. Поэтому ObservableList, который вы собираетесь установить в TableView, должен быть зарегистрирован / объявленчто-то вроде ниже: *

    ObservableList<Person> persons = FXCollections.observableArrayList(e -> 
                                                      new Observable[]{e.pointsProperty()});

Приведенный выше код заставляет запускать ListChangeListener ObservableList при обновлении pointsProperty. Оттуда вы можете выполнить то, что вам нужно.

Ниже приведена быстро работающая демонстрациявыделять строку на 3 секунды при каждом обновлении столбца ОчкиМНБ.Я надеюсь, что эта демонстрация поможет вам решить вашу проблему.Это один из подходов.Могут быть более лучшие способы для достижения нижеуказанной функции.Вы все еще можете воспользоваться решением @fabian.

Примечание: Использовать тот же файл CSS, который вы указали.

import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.stage.Stage;
import javafx.util.Callback;
import javafx.util.Duration;
import java.security.SecureRandom;

public class TableRowUpdateHighlightDemo extends Application {
    private final SecureRandom rnd = new SecureRandom();

    @Override
    public void start(Stage primaryStage) throws Exception {
        ObservableList<Person> persons = FXCollections.observableArrayList(e -> new Observable[]{e.pointsProperty()});
        persons.add(new Person("Harry", "John"));
        persons.add(new Person("Mary", "King"));
        persons.add(new Person("Don", "Bon"));
        persons.add(new Person("Pink", "Wink"));

        TableView<Person> tableView = new TableView<>();
        TableColumn<Person, String> fnCol = new TableColumn<>("First Name");
        fnCol.setCellValueFactory(param -> param.getValue().firstNameProperty());

        TableColumn<Person, String> lnCol = new TableColumn<>("Last Name");
        lnCol.setCellValueFactory(param -> param.getValue().lastNameProperty());

        TableColumn<Person, Integer> pointsCol = new TableColumn<>("Points");
        pointsCol.setCellValueFactory(param -> param.getValue().pointsProperty().asObject());

        tableView.getStylesheets().add(this.getClass().getResource("tableColor.css").toExternalForm());
        tableView.getColumns().addAll(fnCol, lnCol, pointsCol);
        tableView.setItems(persons);
        tableView.getItems().addListener((ListChangeListener<Person>) c -> {
            if (c.next()) {
                if (c.wasUpdated()) {
                    tableView.getItems().get(c.getFrom()).setHightlight(true);
                    tableView.refresh();
                }
            }
        });
        tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);

        tableView.setRowFactory(new Callback<TableView<Person>, TableRow<Person>>() {
            @Override
            public TableRow<Person> call(TableView<Person> param) {
                return new TableRow<Person>() {
                    Timeline highlightTL;

                    @Override
                    protected void updateItem(Person item, boolean empty) {
                        super.updateItem(item, empty);
                        removeHighlight();
                        if (item != null && item.isHightlight()) {
                            getStyleClass().add("highlightedRow");
                            getHighlightTL().playFromStart();
                        }
                    }

                    private void removeHighlight() {
                        getHighlightTL().stop();
                        getStyleClass().removeAll("highlightedRow");
                    }

                    private Timeline getHighlightTL() {
                        if (highlightTL == null) {
                            // After 3 secs, the hightlight will be removed.
                            highlightTL = new Timeline(new KeyFrame(Duration.millis(3000), e -> {
                                getItem().setHightlight(false);
                                removeHighlight();
                            }));
                            highlightTL.setCycleCount(1);
                        }
                        return highlightTL;
                    }
                };
            }
        });

        Scene sc = new Scene(tableView);
        primaryStage.setScene(sc);
        primaryStage.show();

        // Updating points every 5 secs to a random person.
        Timeline tl = new Timeline(new KeyFrame(Duration.millis(5000), e -> {
            Person p = persons.get(rnd.nextInt(4));
            p.setPoints(p.getPoints() + 1);
        }));
        tl.setCycleCount(Animation.INDEFINITE);
        tl.play();

    }

    class Person {
        private StringProperty firstName = new SimpleStringProperty();
        private StringProperty lastName = new SimpleStringProperty();
        private IntegerProperty points = new SimpleIntegerProperty();
        private BooleanProperty hightlight = new SimpleBooleanProperty();

        public Person(String fn, String ln) {
            setFirstName(fn);
            setLastName(ln);
        }

        public String getFirstName() {
            return firstName.get();
        }

        public StringProperty firstNameProperty() {
            return firstName;
        }

        public void setFirstName(String firstName) {
            this.firstName.set(firstName);
        }

        public String getLastName() {
            return lastName.get();
        }

        public StringProperty lastNameProperty() {
            return lastName;
        }

        public void setLastName(String lastName) {
            this.lastName.set(lastName);
        }

        public int getPoints() {
            return points.get();
        }

        public IntegerProperty pointsProperty() {
            return points;
        }

        public void setPoints(int points) {
            this.points.set(points);
        }

        public boolean isHightlight() {
            return hightlight.get();
        }

        public BooleanProperty hightlightProperty() {
            return hightlight;
        }

        public void setHightlight(boolean hightlight) {
            this.hightlight.set(hightlight);
        }
    }
}

Update :: Если вам удастся обновить значение свойства «hightlight» извне (после 3 сек), тогда в RowFactory нет необходимости использовать временную шкалу.Простой вызов tableView.refresh () в ListChangeListener должен сделать свое дело:)

0 голосов
/ 26 сентября 2018

Любые обновления пользовательского интерфейса, даже если они запускаются через прослушиватели, должны выполняться из потока приложения.(Вы можете решить эту проблему, выполнив обновления с помощью Platform.runLater.)

Более того, вы не можете полагаться на одну и ту же ячейку, сохраняющую одну и ту же ячейку в течение полного времени, которое она должна отображаться как отмеченная.

Чтобы преодолеть эту проблему, вам необходимо сохранить информацию о помеченных ячейках либо в самом элементе, либо в некоторой наблюдаемой внешней структуре данных.

В следующем примере время последнего обновления хранится в ObservableMapи использует AnimationTimer для очистки просроченных записей с карты.Кроме того, он использует TableRow s для обновления псевдокласса на основе содержимого карты.

private static class Item {

    private final IntegerProperty value = new SimpleIntegerProperty();
}

private final ObservableMap<Item, Long> markTimes = FXCollections.observableHashMap();
private AnimationTimer updater;

private void updateValue(Item item, int newValue) {
    int oldValue = item.value.get();
    if (newValue != oldValue) {
        item.value.set(newValue);

        // update time of item being marked
        markTimes.put(item, System.nanoTime());

        // timer for removal of entry
        updater.start();
    }
}

@Override
public void start(Stage primaryStage) {
    Item item = new Item(); // the item that is updated
    TableView<Item> table = new TableView<>();
    table.getItems().add(item);

    // some additional items to make sure scrolling effects can be tested
    IntStream.range(0, 100).mapToObj(i -> new Item()).forEach(table.getItems()::add);

    TableColumn<Item, Number> column = new TableColumn<>();
    column.getStyleClass().add("mark-column");
    column.setCellValueFactory(cd -> cd.getValue().value);
    table.getColumns().add(column);

    final PseudoClass marked = PseudoClass.getPseudoClass("marked");

    table.setRowFactory(tv -> new TableRow<Item>() {

        final InvalidationListener reference = o -> {
            pseudoClassStateChanged(marked, !isEmpty() && markTimes.containsKey(getItem()));
        };
        final WeakInvalidationListener listener = new WeakInvalidationListener(reference);

        @Override
        protected void updateItem(Item item, boolean empty) {
            boolean wasEmpty = isEmpty();
            super.updateItem(item, empty);

            if (empty != wasEmpty) {
                if (empty) {
                    markTimes.removeListener(listener);
                } else {
                    markTimes.addListener(listener);
                }
            }

            reference.invalidated(null);
        }

    });

    Scene scene = new Scene(table);
    scene.getStylesheets().add("style.css");
    primaryStage.setScene(scene);
    primaryStage.show();

    updater = new AnimationTimer() {

        @Override
        public void handle(long now) {
            for (Iterator<Map.Entry<Item, Long>> iter = markTimes.entrySet().iterator(); iter.hasNext();) {
                Map.Entry<Item, Long> entry = iter.next();

                if (now - entry.getValue() > 2_000_000_000L) { // remove after 1 sec
                    iter.remove();
                }
            }

            // pause updates, if there are no entries left
            if (markTimes.isEmpty()) {
                stop();
            }
        }
    };

    final Random random = new Random();

    Thread t = new Thread(() -> {

        while (true) {
            try {
                Thread.sleep(4000);
            } catch (InterruptedException ex) {
                continue;
            }
            Platform.runLater(() -> {
                updateValue(item, random.nextInt(4));
            });
        }
    });
    t.setDaemon(true);
    t.start();
}

style.css

.table-row-cell:marked .table-cell.mark-column {
    -fx-background-color: red;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...