Import excel to tableview (javafx) - java

I want a tableview to take in values when end user copies data from excel and pastes it on the tableview..
I want to know the best approach so as of now there isn't any code to post... I want to use Clipboard class and manually add content from the clipboard to the table...
Is that the right approach?
If not. How to do it?
Are there any methods or classes which already implement this
functionality..?

You can do it like this. But you need to adapt the code to match your requirements (e. g. numberformatting, etc).
TableCopyPasteCellsDemo.java
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class TableCopyPasteCellsDemo extends Application {
private final ObservableList<Person> data = FXCollections.observableArrayList(new Person("Jacob", "Smith", 18), new Person("Isabella", "Johnson", 19), new Person("Ethan", "Williams", 20), new Person("Michael", "Brown", 21));
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
stage.setWidth(500);
stage.setHeight(550);
// create table columns
TableColumn<Person, String> firstNameCol = new TableColumn<Person, String>("First Name");
firstNameCol.setMinWidth(100);
firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
TableColumn<Person, String> lastNameCol = new TableColumn<Person, String>("Last Name");
lastNameCol.setMinWidth(100);
lastNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName"));
TableColumn<Person, Integer> ageCol = new TableColumn<Person, Integer>("Age");
ageCol.setMinWidth(60);
ageCol.setCellValueFactory(new PropertyValueFactory<Person, Integer>("age"));
TableView<Person> table = new TableView<>();
table.setPlaceholder(new Text("No content in table"));
table.setItems(data);
table.getColumns().addAll(firstNameCol, lastNameCol, ageCol);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10, 10, 10, 10));
BorderPane borderPane = new BorderPane();
borderPane.setCenter(table);
vbox.getChildren().addAll(borderPane);
vbox.getChildren().add( new Label( "Select cells and press CTRL+C. Paste the data into Excel or Notepad"));
Scene scene = new Scene(vbox);
stage.setScene(scene);
stage.show();
// enable multi-selection
table.getSelectionModel().setCellSelectionEnabled(true);
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
// enable copy/paste
TableUtils.installCopyPasteHandler(table);
}
public static class Person {
private final StringProperty firstName;
private final StringProperty lastName;
private final IntegerProperty age;
private Person(String fName, String lName, Integer age) {
this.firstName = new SimpleStringProperty(fName);
this.lastName = new SimpleStringProperty(lName);
this.age = new SimpleIntegerProperty(age);
}
public final StringProperty firstNameProperty() {
return this.firstName;
}
public final java.lang.String getFirstName() {
return this.firstNameProperty().get();
}
public final void setFirstName(final java.lang.String firstName) {
this.firstNameProperty().set(firstName);
}
public final StringProperty lastNameProperty() {
return this.lastName;
}
public final java.lang.String getLastName() {
return this.lastNameProperty().get();
}
public final void setLastName(final java.lang.String lastName) {
this.lastNameProperty().set(lastName);
}
public final IntegerProperty ageProperty() {
return this.age;
}
public final int getAge() {
return this.ageProperty().get();
}
public final void setAge(final int age) {
this.ageProperty().set(age);
}
}
}
TableUtils.java
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.StringTokenizer;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableView;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.KeyEvent;
public class TableUtils {
private static NumberFormat numberFormatter = NumberFormat.getNumberInstance();
/**
* Install the keyboard handler:
* + CTRL + C = copy to clipboard
* + CTRL + V = paste to clipboard
* #param table
*/
public static void installCopyPasteHandler(TableView<?> table) {
// install copy/paste keyboard handler
table.setOnKeyPressed(new TableKeyEventHandler());
}
/**
* Copy/Paste keyboard event handler.
* The handler uses the keyEvent's source for the clipboard data. The source must be of type TableView.
*/
public static class TableKeyEventHandler implements EventHandler<KeyEvent> {
KeyCodeCombination copyKeyCodeCompination = new KeyCodeCombination(KeyCode.C, KeyCombination.CONTROL_ANY);
KeyCodeCombination pasteKeyCodeCompination = new KeyCodeCombination(KeyCode.V, KeyCombination.CONTROL_ANY);
public void handle(final KeyEvent keyEvent) {
if (copyKeyCodeCompination.match(keyEvent)) {
if( keyEvent.getSource() instanceof TableView) {
// copy to clipboard
copySelectionToClipboard( (TableView<?>) keyEvent.getSource());
// event is handled, consume it
keyEvent.consume();
}
}
else if (pasteKeyCodeCompination.match(keyEvent)) {
if( keyEvent.getSource() instanceof TableView) {
// copy to clipboard
pasteFromClipboard( (TableView<?>) keyEvent.getSource());
// event is handled, consume it
keyEvent.consume();
}
}
}
}
/**
* Get table selection and copy it to the clipboard.
* #param table
*/
public static void copySelectionToClipboard(TableView<?> table) {
StringBuilder clipboardString = new StringBuilder();
ObservableList<TablePosition> positionList = table.getSelectionModel().getSelectedCells();
int prevRow = -1;
for (TablePosition position : positionList) {
int row = position.getRow();
int col = position.getColumn();
// determine whether we advance in a row (tab) or a column
// (newline).
if (prevRow == row) {
clipboardString.append('\t');
} else if (prevRow != -1) {
clipboardString.append('\n');
}
// create string from cell
String text = "";
Object observableValue = (Object) table.getColumns().get(col).getCellObservableValue( row);
// null-check: provide empty string for nulls
if (observableValue == null) {
text = "";
}
else if( observableValue instanceof DoubleProperty) { // TODO: handle boolean etc
text = numberFormatter.format( ((DoubleProperty) observableValue).get());
}
else if( observableValue instanceof IntegerProperty) {
text = numberFormatter.format( ((IntegerProperty) observableValue).get());
}
else if( observableValue instanceof StringProperty) {
text = ((StringProperty) observableValue).get();
}
else {
System.out.println("Unsupported observable value: " + observableValue);
}
// add new item to clipboard
clipboardString.append(text);
// remember previous
prevRow = row;
}
// create clipboard content
final ClipboardContent clipboardContent = new ClipboardContent();
clipboardContent.putString(clipboardString.toString());
// set clipboard content
Clipboard.getSystemClipboard().setContent(clipboardContent);
}
public static void pasteFromClipboard( TableView<?> table) {
// abort if there's not cell selected to start with
if( table.getSelectionModel().getSelectedCells().size() == 0) {
return;
}
// get the cell position to start with
TablePosition pasteCellPosition = table.getSelectionModel().getSelectedCells().get(0);
System.out.println("Pasting into cell " + pasteCellPosition);
String pasteString = Clipboard.getSystemClipboard().getString();
System.out.println(pasteString);
int rowClipboard = -1;
StringTokenizer rowTokenizer = new StringTokenizer( pasteString, "\n");
while( rowTokenizer.hasMoreTokens()) {
rowClipboard++;
String rowString = rowTokenizer.nextToken();
StringTokenizer columnTokenizer = new StringTokenizer( rowString, "\t");
int colClipboard = -1;
while( columnTokenizer.hasMoreTokens()) {
colClipboard++;
// get next cell data from clipboard
String clipboardCellContent = columnTokenizer.nextToken();
// calculate the position in the table cell
int rowTable = pasteCellPosition.getRow() + rowClipboard;
int colTable = pasteCellPosition.getColumn() + colClipboard;
// skip if we reached the end of the table
if( rowTable >= table.getItems().size()) {
continue;
}
if( colTable >= table.getColumns().size()) {
continue;
}
// System.out.println( rowClipboard + "/" + colClipboard + ": " + cell);
// get cell
TableColumn tableColumn = table.getColumns().get(colTable);
ObservableValue observableValue = tableColumn.getCellObservableValue(rowTable);
System.out.println( rowTable + "/" + colTable + ": " +observableValue);
// TODO: handle boolean, etc
if( observableValue instanceof DoubleProperty) {
try {
double value = numberFormatter.parse(clipboardCellContent).doubleValue();
((DoubleProperty) observableValue).set(value);
} catch (ParseException e) {
e.printStackTrace();
}
}
else if( observableValue instanceof IntegerProperty) {
try {
int value = NumberFormat.getInstance().parse(clipboardCellContent).intValue();
((IntegerProperty) observableValue).set(value);
} catch (ParseException e) {
e.printStackTrace();
}
}
else if( observableValue instanceof StringProperty) {
((StringProperty) observableValue).set(clipboardCellContent);
} else {
System.out.println("Unsupported observable value: " + observableValue);
}
System.out.println(rowTable + "/" + colTable);
}
}
}
}

Related

JavaFX TableView Undo functionality

I have a table, and I create the columns in the following way:
#FXML
TableView<Row> tableView = new TableView<>();
private void createColumns(int numberOfColumns, Row firstRow, boolean errorsDisplayed) {
for (int i = 0; i < numberOfColumns; i++) {
int colNum = i;
TableColumn<Row, String> column = new TableColumn<>(firstRow.getCell(i).toString());
column.setCellValueFactory(param -> {
int index = param.getTableView().getColumns().indexOf(param.getTableColumn());
return new SimpleStringProperty(param.getValue().getLastCellNum() > index
? param.getValue().getCell(index).toString() : null);
});
if (!errorsDisplayed || (colNum != numberOfColumns - 1 && colNum != numberOfColumns - 2)) {
column.setCellFactory(TextFieldTableCell.forTableColumn());
column.setOnEditCommit(param -> param.getTableView().getItems().get(param.getTablePosition()
.getRow()).getCell(colNum).setCellValue(param.getNewValue()));
}
tableView.getColumns().add(column);
}
}
Is there a way I could store different states of the table in order to restore them when the undo button being pressed?
I create an app that can hopefully help. It is probably full of traps and pitfalls so don't attempt to use the code as it. I got the Undo/Redo ideas from here. Two Stack are used to implement the ideas. I got the code for the TableView from here. Changes are committed on focus lost.
Main
/*
Altered code from the following!
1. https://docs.oracle.com/javafx/2/ui_controls/table-view.htm
2. https://www.geeksforgeeks.org/implement-undo-and-redo-features-of-a-text-editor/
*/
import com.mycompany.javafxsimpletest.MyAction.MyActionType;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.Callback;
public class App extends Application {
final private UndoRedo undoRedo = new UndoRedo();
final private TableView<Person> table = new TableView();
final private HBox hb = new HBox();
private ObservableList<Person> data;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
data = FXCollections.observableArrayList(
new Person(GenerateUniqueId.getUniqueId(), "Jacob", "Smith", "jacob.smith#example.com"),
new Person(GenerateUniqueId.getUniqueId(), "Isabella", "Johnson", "isabella.johnson#example.com"),
new Person(GenerateUniqueId.getUniqueId(), "Ethan", "Williams", "ethan.williams#example.com"),
new Person(GenerateUniqueId.getUniqueId(), "Emma", "Jones", "emma.jones#example.com"),
new Person(GenerateUniqueId.getUniqueId(), "Michael", "Brown", "michael.brown#example.com"));
//Since I added these Persons via code and not manually, I added that action here!
data.forEach((newPerson) -> {
undoRedo.addAction(new MyAction(MyActionType.ADD, null, newPerson.copy()));
System.out.println("Add Undo Action - \n\tActionType: " + MyActionType.ADD + "\n\tOld Person: " + null + "\n\tnew Person: " + newPerson);
});
Scene scene = new Scene(new Group());
stage.setTitle("Table View Sample");
stage.setWidth(450);
stage.setHeight(550);
final Label label = new Label("Address Book");
label.setFont(new Font("Arial", 20));
table.setEditable(true);
Callback<TableColumn, TableCell> cellFactory = (TableColumn p) -> new EditingCell();
TableColumn firstNameCol = new TableColumn("First Name");
firstNameCol.setMinWidth(100);
firstNameCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("firstName"));
firstNameCol.setCellFactory(cellFactory);
firstNameCol.setOnEditCommit(
new EventHandler<CellEditEvent<Person, String>>() {
#Override
public void handle(CellEditEvent<Person, String> t) {
Person oldPerson = t.getTableView().getItems().get(t.getTablePosition().getRow());
Person newPerson = oldPerson.copy();
newPerson.setFirstName(t.getNewValue());
undoRedo.addAction(new MyAction(MyActionType.EDIT, oldPerson, newPerson));
System.out.println("Add Undo Action - \n\tActionType: " + MyActionType.EDIT + "\n\tOld Person: " + oldPerson + "\n\tnew Person: " + newPerson);
}
}
);
TableColumn lastNameCol = new TableColumn("Last Name");
lastNameCol.setMinWidth(100);
lastNameCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("lastName"));
lastNameCol.setCellFactory(cellFactory);
lastNameCol.setOnEditCommit(
new EventHandler<CellEditEvent<Person, String>>() {
#Override
public void handle(CellEditEvent<Person, String> t) {
Person oldPerson = t.getTableView().getItems().get(t.getTablePosition().getRow());
Person newPerson = oldPerson.copy();
newPerson.setLastName(t.getNewValue());
undoRedo.addAction(new MyAction(MyActionType.EDIT, oldPerson, newPerson));
System.out.println("Add Undo Action - \n\tActionType: " + MyActionType.EDIT + "\n\tOld Person: " + oldPerson + "\n\tnew Person: " + newPerson);
}
}
);
TableColumn emailCol = new TableColumn("Email");
emailCol.setMinWidth(200);
emailCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("email"));
emailCol.setCellFactory(cellFactory);
emailCol.setOnEditCommit(
new EventHandler<CellEditEvent<Person, String>>() {
#Override
public void handle(CellEditEvent<Person, String> t) {
Person oldPerson = t.getTableView().getItems().get(t.getTablePosition().getRow());
Person newPerson = oldPerson.copy();
newPerson.setEmail(t.getNewValue());
undoRedo.addAction(new MyAction(MyActionType.EDIT, oldPerson, newPerson));
System.out.println("Add Undo Action - \n\tActionType: " + MyActionType.EDIT + "\n\tOld Person: " + oldPerson + "\n\tnew Person: " + newPerson);
}
}
);
table.setItems(data);
table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
final TextField addFirstName = new TextField();
addFirstName.setPromptText("First Name");
addFirstName.setMaxWidth(firstNameCol.getPrefWidth());
final TextField addLastName = new TextField();
addLastName.setMaxWidth(lastNameCol.getPrefWidth());
addLastName.setPromptText("Last Name");
final TextField addEmail = new TextField();
addEmail.setMaxWidth(emailCol.getPrefWidth());
addEmail.setPromptText("Email");
final Button addButton = new Button("Add");
addButton.setOnAction((ActionEvent e) -> {
Person newPerson = new Person(GenerateUniqueId.getUniqueId(),addFirstName.getText(), addLastName.getText(), addEmail.getText());
data.add(newPerson);
addFirstName.clear();
addLastName.clear();
addEmail.clear();
undoRedo.addAction(new MyAction(MyActionType.ADD, null, newPerson.copy()));
System.out.println("Add Undo Action - ActionType: " + MyActionType.ADD + "\tPerson: " + newPerson);
});
Button btnUndo = new Button("<");
btnUndo.setOnAction((t) -> {
if(!undoRedo.isUndoEmpty())
{
MyAction myUndoAction = undoRedo.getUndo();
Person oldPerson = (Person)myUndoAction.getOldAction();
Person newPerson = (Person)myUndoAction.getNewAction();
System.out.println("Add Undo Action - \n\tActionType: " + myUndoAction.getActionType() + "\n\tOld Person: " + oldPerson + "\n\tnew Person: " + newPerson);
if(myUndoAction.getActionType() == MyActionType.ADD)
{
data.remove(data.indexOf(data.stream().filter((z) -> z.getId() == newPerson.getId()).findFirst().get()));
}
else if(myUndoAction.getActionType() == MyActionType.EDIT)
{
data.set(data.indexOf(data.stream().filter((z) -> z.getId() == oldPerson.getId()).findFirst().get()), oldPerson);
}
}
});
Button btnRedo = new Button(">");
btnRedo.setOnAction((t) -> {
if(!undoRedo.isRedoEmpty())
{
MyAction myRedoAction = undoRedo.getRedo();
Person oldPerson = (Person)myRedoAction.getOldAction();
Person newPerson = (Person)myRedoAction.getNewAction();
System.out.println("Add Undo Action - \n\tActionType: " + myRedoAction.getActionType() + "\n\tOld Person: " + oldPerson + "\n\tnew Person: " + newPerson);
if(myRedoAction.getActionType() == MyActionType.ADD)
{
data.add(newPerson.getId(), newPerson);
}
else if(myRedoAction.getActionType() == MyActionType.EDIT)
{
data.set(data.indexOf(data.stream().filter((z) -> z.getId() == newPerson.getId()).findFirst().get()), newPerson);
}
}
});
hb.getChildren().addAll(addFirstName, addLastName, addEmail, addButton, btnUndo, btnRedo);
hb.setSpacing(3);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10, 0, 0, 10));
vbox.getChildren().addAll(label, table, hb);
((Group) scene.getRoot()).getChildren().addAll(vbox);
stage.setScene(scene);
stage.show();
}
class EditingCell extends TableCell<Person, String> {
private TextField textField;
public EditingCell() {
}
#Override
public void startEdit() {
if (!isEmpty()) {
super.startEdit();
createTextField();
setText(null);
setGraphic(textField);
textField.selectAll();
}
}
#Override
public void cancelEdit() {
super.cancelEdit();
setText((String) getItem());
setGraphic(null);
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
if (isEditing()) {
if (textField != null) {
textField.setText(getString());
}
setText(null);
setGraphic(textField);
} else {
setText(getString());
setGraphic(null);
}
}
}
private void createTextField() {
textField = new TextField(getString());
textField.setMinWidth(this.getWidth() - this.getGraphicTextGap()* 2);
textField.focusedProperty().addListener(new ChangeListener<Boolean>(){
#Override
public void changed(ObservableValue<? extends Boolean> arg0,
Boolean arg1, Boolean arg2) {
if (!arg2) {
commitEdit(textField.getText());
}
}
});
}
private String getString() {
return getItem() == null ? "" : getItem().toString();
}
}
}
Person
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Person {
private final IntegerProperty id;
private final StringProperty firstName;
private final StringProperty lastName;
private final StringProperty email;
public Person(int id, String fName, String lName, String email) {
this.id = new SimpleIntegerProperty(id);
this.firstName = new SimpleStringProperty(fName);
this.lastName = new SimpleStringProperty(lName);
this.email = new SimpleStringProperty(email);
}
public String getFirstName() {
return firstName.get();
}
public void setFirstName(String fName) {
firstName.set(fName);
}
public String getLastName() {
return lastName.get();
}
public void setLastName(String fName) {
lastName.set(fName);
}
public String getEmail() {
return email.get();
}
public void setEmail(String fName) {
email.set(fName);
}
public int getId()
{
return id.get();
}
#Override public String toString()
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("id: ").append(this.id.get())
.append("\tName:").append(this.firstName.get()).append(" ").append(this.lastName.get())
.append("\temail: ").append(this.email.get());
return stringBuilder.toString();
}
public Person copy()
{
Person copyPerson = new Person(this.id.get(), this.firstName.get(), this.lastName.get(), this.email.get());
return copyPerson;
}
}
MyAction
/**
*
* #author blj0011(sedj601)
* #param <T>
*/
public class MyAction<T>
{
public enum MyActionType {
ADD,
DELETE,
EDIT
}
private final T oldAction;
private final T newAction;
private final MyActionType myActionType;
public MyAction(MyActionType actionType, T oldAction, T newAction) {
this.oldAction = oldAction;
this.newAction = newAction;
this.myActionType = actionType;
}
public T getOldAction()
{
return this.oldAction;
}
public T getNewAction()
{
return this.newAction;
}
public MyActionType getActionType()
{
return this.myActionType;
}
}
UndoRedo
import java.util.Stack;
/**
*
* author 2. https://www.geeksforgeeks.org/implement-undo-and-redo-features-of-a-text-editor/
*
*/
public class UndoRedo {
private final Stack<MyAction> undo = new Stack();
private final Stack<MyAction> redo = new Stack();
public void addAction(MyAction myAction)
{
undo.push(myAction);
}
// Function to perform
// "UNDO" operation
public MyAction getUndo()
{
MyAction myAction = undo.peek();
undo.pop();
redo .push(myAction);
return myAction;
}
// Function to perform
// "REDO" operation
public MyAction getRedo()
{
MyAction myAction = redo.peek();
redo.pop();
undo.push(myAction);
return myAction;
}
//Check if stack is empty before attempting to do use getUndo()!
public boolean isUndoEmpty()
{
return undo.empty();
}
//Check if stack is empty before attempting to do use getRedo()!
public boolean isRedoEmpty()
{
return redo.empty();
}
}
**GenerateUniqueId
/**
*
* #author blj0011(sedj601)
*/
public class GenerateUniqueId {
static AtomicInteger uniqueIdGenerator = new AtomicInteger();
public static int getUniqueId()
{
return uniqueIdGenerator.getAndIncrement();
}
}

JavaFX Set table rowfactory of table view on Button click

i am writing a table drag and drop rearrage form application in javafx. In this application i intend to edit the table order by drag and drop, i have formally written the drag and drop for my Table from my previous searching but what is bugging me is, i would only want to set the drag and drop tablerowfactory only when a button is clicked
Here is my Code:
'''
import java.util.function.Function;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DataFormat;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TableViewDragRows extends Application {
private static final DataFormat SERIALIZED_MIME_TYPE = new DataFormat("application/x-java-serialized-object");
public TableView<Person> tableView;
#Override
public void start(Stage primaryStage) {
tableView = new TableView<>();
tableView.getColumns().add(createCol("First Name", Person::firstNameProperty, 150));
tableView.getColumns().add(createCol("Last Name", Person::lastNameProperty, 150));
tableView.getColumns().add(createCol("Email", Person::emailProperty, 200));
tableView.getItems().addAll(
new Person("Jacob", "Smith", "jacob.smith#example.com","1"),
new Person("Isabella", "Johnson", "isabella.johnson#example.com","2"),
new Person("Ethan", "Williams", "ethan.williams#example.com","3"),
new Person("Emma", "Jones", "emma.jones#example.com","4"),
new Person("Michael", "Brown", "michael.brown#example.com","5")
);
Button order = new Button("Set Order");
order.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
tableView.setRowFactory(tv -> {
TableRow<Person> row = new TableRow<>();
row.setOnDragDetected(event -> {
if (! row.isEmpty()) {
Integer index = row.getIndex();
Dragboard db = row.startDragAndDrop(TransferMode.MOVE);
db.setDragView(row.snapshot(null, null));
ClipboardContent cc = new ClipboardContent();
cc.put(SERIALIZED_MIME_TYPE, index);
db.setContent(cc);
event.consume();
}
});
row.setOnDragOver(event -> {
Dragboard db = event.getDragboard();
if (db.hasContent(SERIALIZED_MIME_TYPE)) {
if (row.getIndex() != ((Integer)db.getContent(SERIALIZED_MIME_TYPE)).intValue()) {
event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
event.consume();
}
}
});
row.setOnDragDropped(event -> {
Dragboard db = event.getDragboard();
if (db.hasContent(SERIALIZED_MIME_TYPE)) {
int draggedIndex = (Integer) db.getContent(SERIALIZED_MIME_TYPE);
Person draggedPerson = tableView.getItems().remove(draggedIndex);
int dropIndex ;
if (row.isEmpty()) {
dropIndex = tableView.getItems().size() ;
} else {
dropIndex = row.getIndex();
}
tableView.getItems().add(dropIndex, draggedPerson);
event.setDropCompleted(true);
tableView.getSelectionModel().select(dropIndex);
event.consume();
}
});
return row ;
});
}
});
order.pressedProperty().addListener((obs, wasFocused, isNowPressed) -> {
if (isNowPressed) {
Platform.runLater(() -> tableView.setRowFactory(tv -> {
TableRow<Person> row = new TableRow<>();
row.setOnDragDetected(event -> {
if (! row.isEmpty()) {
Integer index = row.getIndex();
Dragboard db = row.startDragAndDrop(TransferMode.MOVE);
db.setDragView(row.snapshot(null, null));
ClipboardContent cc = new ClipboardContent();
cc.put(SERIALIZED_MIME_TYPE, index);
db.setContent(cc);
event.consume();
}
});
row.setOnDragOver(event -> {
Dragboard db = event.getDragboard();
if (db.hasContent(SERIALIZED_MIME_TYPE)) {
if (row.getIndex() != ((Integer)db.getContent(SERIALIZED_MIME_TYPE)).intValue()) {
event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
event.consume();
}
}
});
row.setOnDragDropped(event -> {
Dragboard db = event.getDragboard();
if (db.hasContent(SERIALIZED_MIME_TYPE)) {
int draggedIndex = (Integer) db.getContent(SERIALIZED_MIME_TYPE);
Person draggedPerson = tableView.getItems().remove(draggedIndex);
int dropIndex ;
if (row.isEmpty()) {
dropIndex = tableView.getItems().size() ;
} else {
dropIndex = row.getIndex();
}
tableView.getItems().add(dropIndex, draggedPerson);
event.setDropCompleted(true);
tableView.getSelectionModel().select(dropIndex);
event.consume();
}
});
return row ;
}));
}
});
Button button = new Button("Get Order");
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
for(Person p: tableView.getItems()){
System.out.println(p.getid());
} }
});
VBox vbox = new VBox();
vbox.getChildren().addAll(tableView, button, order);
Scene scene = new Scene(new BorderPane(vbox), 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
private TableColumn<Person, String> createCol(String title,
Function<Person, ObservableValue<String>> mapper, double size) {
TableColumn<Person, String> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> mapper.apply(cellData.getValue()));
col.setPrefWidth(size);
return col ;
}
public class Person {
private final StringProperty firstName = new SimpleStringProperty(this, "firstName");
private final StringProperty lastName = new SimpleStringProperty(this, "lastName");
private final StringProperty email = new SimpleStringProperty(this, "email");
private final StringProperty id = new SimpleStringProperty(this, "id");
public Person(String firstName, String lastName, String email, String id) {
this.firstName.set(firstName);
this.lastName.set(lastName);
this.email.set(email);
this.id.set(id);
}
public final StringProperty idProperty() {
return this.id;
}
public final String getid() {
return this.idProperty().get();
}
public final StringProperty firstNameProperty() {
return this.firstName;
}
public final String getFirstName() {
return this.firstNameProperty().get();
}
public final void setFirstName(final String firstName) {
this.firstNameProperty().set(firstName);
}
public final StringProperty lastNameProperty() {
return this.lastName;
}
public final String getLastName() {
return this.lastNameProperty().get();
}
public final void setLastName(final String lastName) {
this.lastNameProperty().set(lastName);
}
public final StringProperty emailProperty() {
return this.email;
}
public final String getEmail() {
return this.emailProperty().get();
}
public final void setEmail(final String email) {
this.emailProperty().set(email);
}
}
public static void main(String[] args) {
launch(args);
}
}
'''
The problem is the button order doesn't invoke the rowfactory

TableView edit method returns wrong row [duplicate]

We are running a JavaFX application that contains some editable table views. A new requested feature is: a button that adds a new row below the currently selected one and immediately starts to edit the first cell of the row.
We implemented this feature, which was not so complicated, but we experience a very strange behavior and after a couple of days investigating the issue we still have no idea what goes wrong.
What happens is that when one clicks the button it adds a new row but starts to edit to first cell not of the newly created row but on an arbitrary other row. Unfortunately, this issue is not 100% reproduceable. Sometimes it's working as expected but most often the row below the newly added row gets edited, but sometimes even completely different rows before and after the currently selected one.
Below you can find the source code of a stripped down version of a JavaFX TableView that can be used to see the issue. As already mentioned it is not 100% reproduceable. To see the issue you have to add a new row multiple times. Some times the issue occurs more often when scrolling the table up and down a couple of times.
Any help is appreciated.
Hint: we already played around with Platform.runlater() a lot, by placing the action implementation of the button inside a runlater(), but although the issue occurs less often then, it never disappeared completely.
The TableView:
package tableview;
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.Callback;
#SuppressWarnings({ "rawtypes", "unchecked" })
public class SimpleTableViewTest extends Application {
private final ObservableList<Person> data = FXCollections.observableArrayList(createData());
private final TableView table = new TableView();
public static void main(String[] args) {
launch(args);
}
private static List<Person> createData() {
List<Person> data = new ArrayList<>();
for (int i = 0; i < 100; i++) {
data.add(new Person("Jacob", "Smith", "jacob.smith_at_example.com", "js_at_example.com"));
}
return data;
}
#Override
public void start(Stage stage) {
Scene scene = new Scene(new Group());
stage.setTitle("Table View Sample");
stage.setWidth(700);
stage.setHeight(550);
final Label label = new Label("Address Book");
label.setFont(new Font("Arial", 20));
// Create a customer cell factory so that cells can support editing.
Callback<TableColumn, TableCell> cellFactory = (TableColumn p) -> {
return new EditingCell();
};
// Set up the columns
TableColumn firstNameCol = new TableColumn("First Name");
firstNameCol.setMinWidth(100);
firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
firstNameCol.setCellFactory(cellFactory);
TableColumn lastNameCol = new TableColumn("Last Name");
lastNameCol.setMinWidth(100);
lastNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName"));
lastNameCol.setCellFactory(cellFactory);
lastNameCol.setEditable(true);
TableColumn primaryEmailCol = new TableColumn("Primary Email");
primaryEmailCol.setMinWidth(200);
primaryEmailCol.setCellValueFactory(new PropertyValueFactory<Person, String>("primaryEmail"));
primaryEmailCol.setCellFactory(cellFactory);
primaryEmailCol.setEditable(false);
TableColumn secondaryEmailCol = new TableColumn("Secondary Email");
secondaryEmailCol.setMinWidth(200);
secondaryEmailCol.setCellValueFactory(new PropertyValueFactory<Person, String>("secondaryEmail"));
secondaryEmailCol.setCellFactory(cellFactory);
// Add the columns and data to the table.
table.setItems(data);
table.getColumns().addAll(firstNameCol, lastNameCol, primaryEmailCol, secondaryEmailCol);
table.setEditable(true);
// --- Here comes the interesting part! ---
//
// A button that adds a row below the currently selected one
// and immediatly starts editing it.
Button addAndEdit = new Button("Add and edit");
addAndEdit.setOnAction((ActionEvent e) -> {
int idx = table.getSelectionModel().getSelectedIndex() + 1;
data.add(idx, new Person());
table.getSelectionModel().select(idx);
table.edit(idx, firstNameCol);
});
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.getChildren().addAll(label, table, addAndEdit);
vbox.setPadding(new Insets(10, 0, 0, 10));
((Group) scene.getRoot()).getChildren().addAll(vbox);
stage.setScene(scene);
stage.show();
}
}
The editable Table Cell:
package tableview;
import javafx.event.EventHandler;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
public class EditingCell extends TableCell<Person, String> {
private TextField textField;
public EditingCell() {
}
#Override
public void cancelEdit() {
super.cancelEdit();
setText(getItem());
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
#Override
public void startEdit() {
super.startEdit();
if (textField == null) {
createTextField();
}
setGraphic(textField);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
if (isEditing()) {
if (textField != null) {
textField.setText(getString());
}
setGraphic(textField);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
} else {
setText(getString());
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
}
}
private void createTextField() {
textField = new TextField(getString());
textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
textField.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent t) {
if (t.getCode() == KeyCode.ENTER) {
commitEdit(textField.getText());
} else if (t.getCode() == KeyCode.ESCAPE) {
cancelEdit();
}
}
});
}
private String getString() {
return getItem() == null ? "" : getItem().toString();
}
}
A Data Bean:
package tableview;
import javafx.beans.property.SimpleStringProperty;
public class Person {
private final SimpleStringProperty firstName;
private final SimpleStringProperty lastName;
private final SimpleStringProperty primaryEmail;
private final SimpleStringProperty secondaryEmail;
public Person() {
this(null, null, null, null);
}
public Person(String firstName, String lastName, String primaryEmail, String secondaryEmail) {
this.firstName = new SimpleStringProperty(firstName);
this.lastName = new SimpleStringProperty(lastName);
this.primaryEmail = new SimpleStringProperty(primaryEmail);
this.secondaryEmail = new SimpleStringProperty(secondaryEmail);
}
public SimpleStringProperty firstNameProperty() {
return firstName;
}
public String getFirstName() {
return firstName.get();
}
public String getLastName() {
return lastName.get();
}
public String getPrimaryEmail() {
return primaryEmail.get();
}
public SimpleStringProperty getPrimaryEmailProperty() {
return primaryEmail;
}
public String getSecondaryEmail() {
return secondaryEmail.get();
}
public SimpleStringProperty getSecondaryEmailProperty() {
return secondaryEmail;
}
public SimpleStringProperty lastNameProperty() {
return lastName;
}
public void setFirstName(String firstName) {
this.firstName.set(firstName);
}
public void setLastName(String lastName) {
this.lastName.set(lastName);
}
public void setPrimaryEmail(String primaryEmail) {
this.primaryEmail.set(primaryEmail);
}
public void setSecondaryEmail(String secondaryEmail) {
this.secondaryEmail.set(secondaryEmail);
}
}
The correct code of the buttons action implementation has to look like below. The important line to fix the described issue is 'table.layout()'.
Many thanks to fabian!
addAndEdit.setOnAction((ActionEvent e) -> {
int idx = table.getSelectionModel().getSelectedIndex() + 1;
data.add(idx, new Person());
table.getSelectionModel().select(idx);
table.layout();
table.edit(idx, firstNameCol);
});

JavaFX: How to get more "dummy/empty Rows"

I have a Javafx TableView where I can add new Rows by double Click on an empty Row at the End of my "filled" / Textfield filled Rows.
My Problem is,if i add some Rows ,Java don't give me more of the empty Rows I could double click to add some Rows.
Edit:removed some unnessary log
To see what i mean, here is the Code:
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TextArea;
import javafx.util.Callback;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
interface inside_table
{
public void Select_Row_by_Col(int index);
}
public class Supermain extends Application {
ObservableList<myTextRow> data;
#Override
public void start(Stage primaryStage) {
ArrayList myindizes=new ArrayList();
final TableView<myTextRow> table = new TableView<>();
table.setEditable(true);
table.setStyle("-fx-text-wrap: true;");
//Table columns
TableColumn<myTextRow, String> clmID = new TableColumn<>("ID");
clmID.setMinWidth(160);
clmID.setCellValueFactory(new PropertyValueFactory<>("ID"));
TableColumn<myTextRow, String> clmtext = new TableColumn<>("Text");
clmtext.setMinWidth(160);
clmtext.setCellValueFactory(new PropertyValueFactory<>("text"));
clmtext.setCellFactory(new TextFieldCellFactory("text"));
TableColumn<myTextRow, String> clmtext2 = new TableColumn<>("Text2");
clmtext2.setMinWidth(160);
clmtext2.setCellValueFactory(new PropertyValueFactory<>("text2"));
clmtext2.setCellFactory(new TextFieldCellFactory("text2"));
//Add data
data = FXCollections.observableArrayList(
new myTextRow(5, "Lorem","bla"),
new myTextRow(2, "Ipsum","bla")
);
table.getColumns().addAll(clmID, clmtext,clmtext2);
table.setItems(data);
table.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
if (mouseEvent.getButton().equals(MouseButton.PRIMARY)) {
if (mouseEvent.getClickCount() == 2 && mouseEvent.getY()>24) {
data.add(new myTextRow(td_get_biggest_ID() + 1,"",""));
table.selectionModelProperty().get().select(data.size()-1);
}
}
}
});
HBox hBox = new HBox();
hBox.setSpacing(5.0);
hBox.setPadding(new Insets(5, 5, 5, 5));
Button btn = new Button();
btn.setText("Get Data");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
for (myTextRow data1 : data) {
System.out.println("data:" + data1.getText2());
}
}
});
hBox.getChildren().add(btn);
BorderPane pane = new BorderPane();
pane.setTop(hBox);
pane.setCenter(table);
primaryStage.setScene(new Scene(pane, 640, 480));
primaryStage.show();
class I_table implements inside_table{
#Override
public void Select_Row_by_Col(int index) {
table.getSelectionModel().select(index);
}
}
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
public static class TextFieldCellFactory
implements Callback<TableColumn<myTextRow, String>, TableCell<myTextRow, String>> {
private String ColumnName;
public TextFieldCellFactory(String ColumnName){
this.ColumnName=ColumnName;
}
#Override
public TableCell<myTextRow, String> call(TableColumn<myTextRow, String> param) {
TextFieldCell textFieldCell = new TextFieldCell(this.ColumnName);
return textFieldCell;
}
public static class TextFieldCell extends TableCell<myTextRow, String> {
private TextArea textField;
private StringProperty boundToCurrently = null;
private String last_text;
private String ColumnName;
public TextFieldCell(String cname) {
textField = new TextArea();
textField.setWrapText(true);
textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
last_text="";
this.ColumnName=cname;
this.setGraphic(textField);
textField.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
if(this.ColumnName=="text2"){
if(isNowFocused){last_text=textField.getText();System.out.println("NOW focus "+last_text);}
if (! isNowFocused && ! isValid(textField.getText())) {
textField.setText(last_text);
//textField.setText("00:00:00:00");
textField.selectAll();
System.out.println("blur");
}
}
});
}
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
// Show the Text Field
this.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
// myindizes.add(getIndex());
// Retrieve the actual String Property that should be bound to the TextField
// If the TextField is currently bound to a different StringProperty
// Unbind the old property and rebind to the new one
ObservableValue<String> ov = getTableColumn().getCellObservableValue(getIndex());
SimpleStringProperty sp = (SimpleStringProperty) ov;
if (this.boundToCurrently == null) {
this.boundToCurrently = sp;
this.textField.textProperty().bindBidirectional(sp);
} else if (this.boundToCurrently != sp) {
this.textField.textProperty().unbindBidirectional(this.boundToCurrently);
this.boundToCurrently = sp;
this.textField.textProperty().bindBidirectional(this.boundToCurrently);
}
double height = real_lines_height(textField.getText(), this.getWidth(), 30, 22);
textField.setPrefHeight(height);
textField.setMaxHeight(height);
textField.setMaxHeight(Double.MAX_VALUE);
// if height bigger than the biggest height in the row
//-> change all heights of the row(textfields ()typeof textarea) to this height
// else leave the height as it is
//System.out.println("item=" + item + " ObservableValue<String>=" + ov.getValue());
//this.textField.setText(item); // No longer need this!!!
} else {
this.setContentDisplay(ContentDisplay.TEXT_ONLY);
}
}//update
private boolean isValid(String s){
String splitArray[] = s.split(":");
if (splitArray.length != 4) {
System.out.println("false");
return false;
}
for (int i = 0; i < splitArray.length; i++) {
if (splitArray[i].length() != 2) {
System.out.println("false");
return false;
}
if (!splitArray[i].substring(0, 1).matches("[0-9]")) {
System.out.println("no number1");
return false;
}
if (!splitArray[i].substring(1, 2).matches("[0-9]")) {
System.out.println("no number2");
return false;
}
if (i < 3) {
int itest = Integer.parseInt(splitArray[i]);
if (itest > 59) {
System.out.println(itest + " ist zu groß!");
return false;
}
} else {
int itest2 = Integer.parseInt(splitArray[i]);
if (itest2 > Math.floor(25)) {
System.out.println(itest2 + " ist zu groß!");
return false;
}
//framerate!!!!!
}
System.out.println("splits: " + splitArray[i]);
//if( el.charAt(0).)
}
return true;
}
}
}
public class myTextRow {
private final SimpleIntegerProperty ID;
private final SimpleStringProperty text;
private final SimpleStringProperty text2;
public myTextRow(int ID, String text,String text2) {
this.ID = new SimpleIntegerProperty(ID);
this.text = new SimpleStringProperty(text);
this.text2 = new SimpleStringProperty(text2);
}
//setter
public void setID(int id) {
this.ID.set(id);
}
public void setText(String text) {
this.text.set(text);
}
public void setText2(String text) {
this.text2.set(text);
}
//getter
public int getID() {
return ID.get();
}
public String getText() {
return text.get();
}
public String getText2() {
return text2.get();
}
//properties
public StringProperty textProperty() {
return text;
}
public StringProperty text2Property() {
return text2;
}
public IntegerProperty IDProperty() {
return ID;
}
}
private static double real_lines_height(String s, double width, double heightCorrector, double widthCorrector) {
HBox h = new HBox();
Label l = new Label("Text");
h.getChildren().add(l);
Scene sc = new Scene(h);
l.applyCss();
double line_height = l.prefHeight(-1);
int new_lines = s.replaceAll("[^\r\n|\r|\n]", "").length();
// System.out.println("new lines= "+new_lines);
String[] lines = s.split("\r\n|\r|\n");
// System.out.println("line count func= "+ lines.length);
int count = 0;
//double rest=0;
for (int i = 0; i < lines.length; i++) {
double text_width = get_text_width(lines[i]);
double plus_lines = Math.ceil(text_width / (width - widthCorrector));
if (plus_lines > 1) {
count += plus_lines;
//rest+= (text_width / (width-widthCorrector)) - plus_lines;
} else {
count += 1;
}
}
//count+=(int) Math.ceil(rest);
count += new_lines - lines.length;
return count * line_height + heightCorrector;
}
private static double get_text_width(String s) {
HBox h = new HBox();
Label l = new Label(s);
l.setWrapText(false);
h.getChildren().add(l);
Scene sc = new Scene(h);
l.applyCss();
// System.out.println("FXMLDocumentController.get_text_width(): "+l.prefWidth(-1));
return l.prefWidth(-1);
}
public int td_get_biggest_ID() {
int biggest = 0;
for (int i = 0; i < data.size(); i++) {
if (((myTextRow) data.get(i)).getID() > biggest) {
biggest = ((myTextRow) data.get(i)).getID();
}
}
return biggest;
}
}
Just click anywhere else on the TableView but make sure it's at least 24 pixels from the top; This will work since you've added the event handler is added to the TableView...
If you only want to use the last row, then use a custom rowFactory and handle the events there.
Add a placeholder item to the TableView items that marks the row that is used for adding new elements (for some reason the selection model doesn't like null):
final myTextRow addPlaceHolder = new myTextRow(Integer.MIN_VALUE, null, null);
...
//Add data
data = FXCollections.observableArrayList(
new myTextRow(5, "Lorem", "bla"),
new myTextRow(2, "Ipsum", "bla"),
addPlaceHolder
);
make sure your TextFieldCells treat null values as empty rows:
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (!empty && item != null) {
// Show the Text Field
this.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
...
make sure the first column does not display anything for the placeholder
//Table columns
TableColumn<myTextRow, Number> clmID = new TableColumn<>("ID");
clmID.setMinWidth(160);
clmID.setCellValueFactory(cdf -> {
myTextRow item = cdf.getValue();
return item == addPlaceHolder ? Bindings.createObjectBinding(() -> null) : item.IDProperty();
});
and use the following rowFactory to handle adding the items (you don't need the updateItem part unless you need to add a style class to the TableRow; you need not extend TableRow in this case)
table.setRowFactory(tv -> new TableRow<myTextRow>() {
{
setOnMouseClicked(mouseEvent -> {
if (mouseEvent.getButton() == MouseButton.PRIMARY
&& mouseEvent.getClickCount() == 2
&& !isEmpty()
&& getItem() == addPlaceHolder) {
data.add(data.size() - 1, new myTextRow(td_get_biggest_ID() + 1, "", ""));
table.selectionModelProperty().get().select(data.size() - 1);
mouseEvent.consume();
}
});
}
#Override
protected void updateItem(myTextRow item, boolean empty) {
super.updateItem(item, empty);
// add style class for row containing addPlaceHolder
List<String> classes = getStyleClass();
final String clazz = "add-row";
if (item == addPlaceHolder) {
if (!classes.contains(clazz)) {
classes.add(clazz);
}
} else {
classes.remove(clazz);
}
}
});

JavaFX - how to recognize the position of ScrollBar in a TableView

I have written this little example application:
package application;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Main extends Application {
public class Person {
private StringProperty firstName = new SimpleStringProperty();
private StringProperty lastName = new SimpleStringProperty();
public Person(String firstName, String lastName) {
this.firstName.set(firstName);
this.lastName.set(lastName);
}
public String getFirstName() {
return firstName.get();
}
public String getLastName() {
return lastName.get();
}
public StringProperty firstNameProperty() {
return firstName;
}
public StringProperty lastNameProperty() {
return lastName;
}
}
#Override
public void start(Stage primaryStage) {
try {
StackPane root = new StackPane();
TableView<Person> tv = new TableView<>();
TableColumn<Person, String> col = new TableColumn<Person, String>("FirstName");
col.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
tv.getColumns().add(col);
tv.setEditable(true);
col = new TableColumn<Person, String>("LastName");
col.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName"));
col.setCellFactory(TextFieldTableCell.forTableColumn());
col.setOnEditCommit(new EventHandler<TableColumn.CellEditEvent<Person, String>>() {
#Override
public void handle(CellEditEvent<Person, String> event) {
System.out.println(tv.getItems().get(1).getLastName());
}
});
tv.getColumns().add(col);
for (int i = 0; i < 30; i++) {
tv.getItems().add(new Person("Test" + i, "Test" + i));
}
root.getChildren().add(tv);
Scene scene = new Scene(root, 400, 200);
primaryStage.setScene(scene);
primaryStage.show();
tv.addEventFilter(MouseEvent.MOUSE_RELEASED, event -> {
// ...
});
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
I want to perform action when the ScrollBar has reached the bottom. Then I want to reload more datas from the database. But only then, when the user has seen all the already loaded datas (= scrollbar on the bottom). Do you have nice suggestions to solve this issue?
My first idea was to catch the MOUSE_RELEASED event (when the users drags the bar) of the TableView and then to check the position of the ScrollBar:
- getValue() gets the position of the bar
- getMax() the maximum value (=bottom).
But I can't find a way (without using the css-selector via this method) to get the ScrollBar from a given TableView. So I can't check the position of it in a certain TableView.
Do you have any ideas??
I am excited. Thanks for your help.
The only way to get the scroll bar is via a lookup, which is a bit of a hack, but it will work as long as you do it after the table has been rendered on the scene. You need
ScrollBar verticalBar = (ScrollBar) table.lookup(".scroll-bar:vertical");
Note that there's no need to mess with user events: you can just observe the scroll bar's value property directly:
verticalBar.valueProperty().addListener((obs, oldValue, newValue) -> {
if (newValue.doubleValue() >= verticalBar.getMax()) {
// add more data...
}
});
SSCCE:
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class AddMoreTableDataOnScrollToBottom extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Item> table = new TableView<>();
table.getColumns().add(column("Item", Item::nameProperty));
table.getColumns().add(column("Value", Item::valueProperty));
addMoreData(table, 20);
Scene scene = new Scene(new BorderPane(table), 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
ScrollBar verticalBar = (ScrollBar) table.lookup(".scroll-bar:vertical");
verticalBar.valueProperty().addListener((obs, oldValue, newValue) -> {
if (newValue.doubleValue() >= verticalBar.getMax()) {
addMoreData(table, 20);
}
});
}
private void addMoreData(TableView<Item> table, int numItems) {
Task<List<Item>> dataRetrieveTask = new Task<List<Item>>() {
#Override
public List<Item> call() throws Exception {
// mimic connect to db:
Thread.sleep(500);
List<Item> items = new ArrayList<>();
int nextItem = table.getItems().size() + 1 ;
for (int i = nextItem; i < nextItem + numItems; i++ ){
items.add(new Item("Item "+i, i));
}
return items ;
}
};
dataRetrieveTask.setOnSucceeded(e -> table.getItems().addAll(dataRetrieveTask.getValue()));
new Thread(dataRetrieveTask).start();
}
private <S,T> TableColumn<S,T> column(String title, Function<S, ObservableValue<T>> prop) {
TableColumn<S,T> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> prop.apply(cellData.getValue()));
return col ;
}
public static class Item {
private final StringProperty name = new SimpleStringProperty();
private final IntegerProperty value = new SimpleIntegerProperty();
public Item(String name, int value) {
setName(name);
setValue(value);
}
public final StringProperty nameProperty() {
return this.name;
}
public final String getName() {
return this.nameProperty().get();
}
public final void setName(final String name) {
this.nameProperty().set(name);
}
public final IntegerProperty valueProperty() {
return this.value;
}
public final int getValue() {
return this.valueProperty().get();
}
public final void setValue(final int value) {
this.valueProperty().set(value);
}
}
public static void main(String[] args) {
launch(args);
}
}

Categories