2
votes

I have a JavaFX TableView with single cell selection enabled. When a user selects a cell the selection highlight will flicker when new data is added to the table

A small example that demonstrates the problem:

import java.util.Random;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.Callback;

public class SelectionBug extends Application
{
    public static void main(
        String[] args)
    {
        Application.launch(args);
    }

    @Override
    public void start(
        Stage primaryStage) throws Exception
    {
        final ObservableList<DummyData> list = FXCollections.observableArrayList();

        final TableView<DummyData> tableView = new TableView<>(list);
        tableView.getColumns().add(createColumn(item -> item.getColumn1()));
        tableView.getColumns().add(createColumn(item -> item.getColumn2()));
        tableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
        tableView.getSelectionModel().setCellSelectionEnabled(true);

        final Thread thread = new Thread(() -> 
        {
            while (true)
            {
                Platform.runLater(() -> list.add(new DummyData()));

                try
                {
                    Thread.sleep(1000);
                }
                catch (InterruptedException e)
                {
                    //do nothing
                }
            }
        });
        thread.setDaemon(true);
        thread.start();

        final BorderPane root = new BorderPane();
        root.setCenter(tableView);
        primaryStage.setScene(new Scene(root, 500, 500));
        primaryStage.show();
    }

    private TableColumn<DummyData, String> createColumn(
        final Callback<DummyData, String> dataGetter)
    {
        final TableColumn<DummyData, String> column = new TableColumn<>();
        column.setCellValueFactory(cellData -> new ReadOnlyStringWrapper(dataGetter.call(cellData.getValue())));
        return column;
    }

    private static class DummyData
    {
        private final String mColumn1;
        private final String mColumn2;

        public DummyData()
        {
            final Random ramdom = new Random();
            mColumn1 = Integer.toString(ramdom.nextInt(1000));
            mColumn2 = Integer.toString(ramdom.nextInt(1000));
        }

        public String getColumn1()
        {
            return mColumn1;
        }

        public String getColumn2()
        {
            return mColumn2;
        }
    }

}

If you run that and select a cell, you'll see the flickering.

My digging so far suggests it's to do with cell recycling in the table view: I changed the Cell Factory to assign and log out a unique ID and the cell's item for each cell object and found that the ID <-> item relationship is not constant; each cell object gets moved around the tableview showing different data with every update to the data model. This means that the selected property is modified on every update, causing the pseudoClassState to change. I suspect it's a subtle timing issue with when the cell is taken out of the tableview and when the cell's selected property is changed

Has anyone else seen this problem, and does anyone have any kind of workaround?

1

1 Answers

0
votes

Probably a bit late for you, but I had a similar problem and managed to solve it by wrapping the TableView in an extra AnchorPane.