JDK-8177945 : Single cell selection flickers when adding data to TableView
  • Type: Bug
  • Component: javafx
  • Sub-Component: controls
  • Affected Version: 8u20,9
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: generic
  • CPU: generic
  • Submitted: 2017-03-31
  • Updated: 2022-07-07
  • Resolved: 2020-11-19
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 8 Other
8-poolUnresolved openjfx11.0.10Fixed
Related Reports
Relates :  
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.8.0_121"
Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) Client VM (build 25.121-b13, mixed mode, sharing)

ADDITIONAL OS VERSION INFORMATION :
Darwin Sams-MacBook-Pro.local 16.5.0 Darwin Kernel Version 16.5.0: Fri Mar  3 16:52:33 PST 2017; root:xnu-3789.51.2~3/RELEASE_X86_64 x86_64

macOS 10.12
Windows 10

A DESCRIPTION OF THE PROBLEM :
In a JavaFX TableView with Single Cell Selection enabled, the currently selected cell will flicker when data is added to the TableView

It seems to only affect Single Cell Selection, when this is disabled there's no flickering. I think it's to do with the cell objects being recycled and moved around the TableView

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the included test case, and select a cell. The test case periodically adds data, and you'll see the flickering

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The currently selected cell remains selected with no flickering
ACTUAL -
The currently selected cell flickers

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
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;
        }
    }

}

---------- END SOURCE ----------


Comments
Changeset: a128952b Author: Jose Pereda <jpereda@openjdk.org> Date: 2020-11-19 15:20:12 +0000 URL: https://github.com/openjdk/jfx/commit/a128952b
19-11-2020

While trying to find a fix for this issue, I've noticed some inconsistencies in the use of VirtualContainerBase::requestRebuildCells from VirtualContainerBase::updateItemCount() implemented in the different skin classes for virtualised controls: - ListViewSkin calls: @Override protected void updateItemCount() { ... if (newCount != oldCount) { requestRebuildCells(); } else { needCellsReconfigured = true; } } - TableViewSkin calls: @Override protected void updateItemCount() { ... if (newCount != oldCount) { // FIXME updateItemCount is called _a lot_. Perhaps we can make rebuildCells // smarter. Imagine if items has one million items added - do we really // need to rebuildCells a million times? Maybe this is better now that // we do rebuildCells instead of recreateCells. requestRebuildCells(); } else { needCellsReconfigured = true; } } - TreeViewSkin calls: @Override protected void updateItemCount() { ... // if this is not called even when the count is the same, we get a // memory leak in VirtualFlow.sheet.children. This can probably be // optimised in the future when time permits. requestRebuildCells(); } - TreeTableViewSkin calls: @Override protected void updateItemCount() { ... if (newCount != oldCount) { // The following line is (perhaps temporarily) disabled to // resolve two issues: JDK-8155798 and JDK-8147483. // A unit test exists in TreeTableViewTest to ensure that // the performance issue covered in JDK-8147483 doesn't regress. // requestRebuildCells(); } else { needCellsReconfigured = true; } } Only TreeTableViewSkin doesn't use it, and there is a comment that justifies why it was commented out (see JDK-8147483). There has been a recent issue filed that was possibly caused because that fix (JDK-8244826), however, after fixing JDK-8252811, this issue is no longer happening. The comment in TreeViewSkin is quite similar to the comment removed after fixing JDK-8252811, so probably is no longer valid. Finally, the comment in TableViewSkin mentions that calling requestRebuildCells on every change of items should be optimised. After fixing JDK-8252811, simply removing the call to requestRebuildCells in updateItemCount fixes the current issue (JDK-8177945) when a cell is selected and new items are added to the TableView control. My proposal will be applying the same for the 4 controls, removing the call to requestRebuildCells: @Override protected void updateItemCount() { ... if (newCount == oldCount) { needCellsReconfigured = true; } }
23-10-2020

Issue is reproducible in all 3 OS and its a regression , introduced in 8u20 of windows. windows 10: --------------------- 8b132 : pass 8u5 : pass 8u11 : pass 8u20 : fail <----- Introduced here 8u25 : fail 8u27 : fail 8u40 : fail 8u121 : fail 9-ea+159 : fail 9-ea+161 : fail 9-ea+163 : fail macOS Sierra 10.12.3 : ------------------------- 8u121 : fail 8u152 : fail 9-ea+155 : fail 9-ea+160 : fail 9-ea+163 : fail Ubuntu 14.04.1 -------------- 8u121 : fail 9-ea+159 : fail ALso looks similar to JDK-8087902 [Open] Raising priority to P3 Moving this to JDK for the development team's evaluation.
03-04-2017