I've been messing around with javafx for a bit. and I came across something I cannot realy explain. I am trying to create an editable Tableview with string values. And everything worked, till I started to do biger tests. Before I tested with small lists, so it didn't exceed the list's visible limit. Though with the big test, the list was far more bigger than it could show in its window.
The problem is that I give a certain amount of cells at the begin of the Tableview another colour, which works perfectly, till you start scrolling down. it seems like the re-used cells aren't updated fast enough, since some have been given the wrong colour. When scrolling down, and thus updating more cells, the patern of 'wrong coloured cells' changes as well. The interesting part is, that these 'fails' are consistent!
(btw, Im checking the cells index by using this.getIndex()
after which I call this.setStyle()
with the proper colour syntax.)
The second thing I noticed, is when I start editing a cell, though dont stop editing and scroll down. There are multiple cells that are being edited at the same time. (also in a consistent patern) And its not a visual glitch!, I have debugged it, and it seems like the editing
property isn't updated well enough either.
One last side note, I update the TableCells' graphics in the Cell#updateItem()
method. (and for the editing part in the Cell#startEdit()
and Cell#cancelEdit()
as well.)
My question is, is this a bug? And if so, is there a workaround? Please leave a comment if you have any idea's/suggestions/solutions.
Thanks in advance!
EDIT 01:
First I need to appoligize, since the problem is ocuring with the tableView, not the listView. That being said, I made an example class which displays exactually the problem I tried to describe. If there are any suggestions/solutions, please let me know!
package testpacket;
import java.util.Arrays;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
public class TableViewTest extends Application
{
private static ObservableList<String> exampleList = FXCollections.observableArrayList();
private static String[] testArray = new String[50];
public static void main(String[] args)
{
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception
{
// basic ui setup
AnchorPane parent = new AnchorPane();
Scene scene = new Scene(parent);
primaryStage.setScene(scene);
//fill backinglist with data
Arrays.fill(testArray, 0, testArray.length, "Test String");
exampleList.setAll(Arrays.asList(testArray));
//create a basic tableView
TableView<String> listView = new TableView<String>();
TableColumn<String, String> column = new TableColumn<String, String>();
column.setCellFactory(E -> new TableCellTest<String, String>());
column.setCellValueFactory(E -> new SimpleStringProperty(E.getValue()));
// set listViews' backing list
listView.getItems().addAll(exampleList);
// listView.setItems(exampleList); it doesnt rely on the backing list, either way it is showing this bug.
listView.getColumns().clear();
listView.getColumns().add(column);
parent.getChildren().add(listView);
primaryStage.show();
}
public static class TableCellTest<S, T> extends TableCell<S, T>
{
@Override
protected void updateItem(T item, boolean empty)
{
super.updateItem(item, empty);
// dipslays cells' value
this.setText((String)this.getItem());
// checks cells index and set its color.
if(this.getIndex() < 12)
this.setStyle("-fx-background-color: rgba(253, 255, 150, 0.4);");
else this.setStyle("");
}
}
}
EDIT 02:
@James_D
many thanks for your answer, indeed the duplicated items are the problem. Somehow javafx is checking whether the content has changed or not. unfortunatly the solution:
testArray[i] = new String("Test String");
does not work, which is quit unfortunate because my system depends heavily on duplicated entries. Trying to make them unique would be almost impossible to handle perfectly.
So I really hope there is a possibility to overwrite the 'is equal check' somehow. I have been diging a bit, and it seems like all those methods/fields are private. So changing that is kinda impossible to do, unless I recreate a bunch of javafx classes.
So please if there are still suggestion/solutions for this one, feel free to post them!
Onto the next problem though, the editing property isnt updated well enough either. In the onUpdateItem()
method I check if the current cell is editing and keep updating that cell so it stays in editing mode, even if you scroll down (and thus scrolling the editing index out of the window). I made this example so you can see for yourself.
Note that when you remove the onUpdateItem()
part of the editing system, the cell will exit edit mode after it has been scrolled outside the viewWindow.
package testpacket;
import java.util.Arrays;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
public class TableViewTest extends Application
{
private static ObservableList<String> exampleList = FXCollections.observableArrayList();
private static String[] testArray = new String[50];
public static void main(String[] args)
{
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception
{
// basic ui setup
AnchorPane parent = new AnchorPane();
Scene scene = new Scene(parent);
primaryStage.setScene(scene);
//fill backinglist with data
for(int i = 0 ; i < testArray.length; i++)
testArray[i] = "Test String" + i;
// Arrays.fill(testArray, 0, testArray.length, new String("Test String"));
exampleList.setAll(Arrays.asList(testArray));
//create a basic tableView
TableView<String> listView = new TableView<String>();
TableColumn<String, String> column = new TableColumn<String, String>();
column.setCellFactory(E -> new TableCellTest<String, String>());
column.setCellValueFactory(E -> new SimpleStringProperty(E.getValue()));
column.setEditable(true);
// set listViews' backing list
listView.getItems().addAll(exampleList);
listView.setEditable(true);
// listView.setItems(exampleList); it doesnt rely on the backing list, either way it is showing this bug.
listView.getColumns().clear();
listView.getColumns().add(column);
parent.getChildren().add(listView);
primaryStage.show();
}
public static class TableCellTest<S, T> extends TableCell<S, T>
{
protected TextField textField;
@Override
public void startEdit()
{
super.startEdit();
if (!isEditable()
|| (this.getTableView() != null && !this.getTableView().isEditable())
|| (this.getTableColumn() != null && !this.getTableColumn().isEditable()))
return;
if(this.textField == null)
this.createTextField();
this.textField.setText((String)this.getItem());
this.setGraphic(this.textField);
this.textField.selectAll();
this.setText(null);
}
@Override
public void cancelEdit()
{
super.cancelEdit();
if (!isEditing())
return;
this.setText((String)this.getItem());
this.setGraphic(null);
}
@Override
protected void updateItem(T item, boolean empty)
{
super.updateItem(item, empty);
if(empty || item == null)
{
this.setText(null);
this.setGraphic(null);
}
else
{
// I tried this option to check whether a cell represents the correct item, though it doesnt work
if(this.isEditing())// && this.getTableView().getEditingCell() != null && this.getTableView().getEditingCell().equals(this))
{
if(this.textField != null)
this.textField.setText((String)this.getItem());
this.setText(null);
this.setGraphic(this.textField);
}
else
{
this.setText((String)this.getItem());
this.setGraphic(null);
}
}
}
@SuppressWarnings("unchecked")
public void createTextField()
{
this.textField = new TextField();
this.textField.setOnKeyReleased((EventHandler<? super KeyEvent>)E -> {
if(E.getCode() == KeyCode.ENTER)
{
this.setItem((T)this.textField.getText());
this.commitEdit(this.getItem());
}
else if(E.getCode() == KeyCode.ESCAPE)
this.cancelEdit();
});
}
}
}
Edit 03:
Unlike It mentioned in the class, there is a workaround for this one. Note that this works only for 'single cell editing mode'! (as it is the only mode that is supported by the TableView) The solution is as follows:
if(this.isEditing() && this.getTableView().getEditingCell() != null
&& this.getTableView().getEditingCell().getTableColumn().getCellData(this.getTableView().getEditingCell().getRow()).equals(this.getItem()))
{
... do stuff
}
Though unfortunatly it doesn't solve the index problem. This thread is still open for suggestions/solutions.