Ok, after much troubleshooting and trying to learn the JavaFX Application architecture, I have come to a TableView wrapper class.
Here is a list of working functions it can perform:
1) When editing a cell, you can transverse to the next cell by pressing TAB (the SHIFT key toggles forwards and backwards)
2) By selecting a cell, holding SHIFT, and selecting different cell, the TableView disables CellSelection and selects all involved rows
Here is a list of functions I have not attempted to implement:
1) Transverse cells using arrow keys while editing a cell
2) Add/Delete row functionality
Things to look at:
*Uses of the "updateItems()" method. Where in other code samples it is used much less, it is critical in my instance for making sure the cells do not come up blank.
*EDIT: Look at how I must create a property in my TableView wrapper class to keep track of rows. This is because the EditingCell cannot update its row index before the TAB functions are executed.
Here is a list of functions I have implemented that respond with bugs and errors and how to duplicate:
Test 1
1) Click the cell in the second column, first row, to enter cell editing ("Smith"). Hold SHIFT and click the cell in the second column, last row to select multiple rows ("Brown").
2) Click the cell in the first column, first row ("Jacob"). RESULT: Selects the cell but does not enter editing state.
3) Repeat step 1. Click the second column, first row. RESULT: Sometimes it enters an editing state, but ignores cell border styling. Most times it gives the results in step 2.
Test 2
1) Click the rows below the last row.
Test 3
1) Restart the program.
2) Click the "First Name" column title to sort.
3) Click all cells in the first column starting from top to bottom, toggling the edit state on all of them.
4) Repeat steps 2 and 3. RESULT: Inconsistent/duplicating values
Additional concerns:
1) The results of Test 3 could be due to the fact that I do not have the columns binded to a controller class, but then how would I build an API to programmatically do this, allowing dynamic adding of columns? If each column must be binded to a property, how do you dynamically add properties?
2) Although form follows function, I need JavaFX because it looks good. I'll probably backtrack until I have isolated all the functions that work, then add more TableView functions incrementally. TableView's display features work like a charm, but when it comes to editing its items, it seems things get very complicated, and I need it to behave similar to Excel as that is what my customers use as supplemental software to their daily operations. I do not want to confuse them by having them switch back and forth between two different types of essentially "spreadsheets" that have completely different functionality.
----"stylesheet.css"----
.text-field {
-fx-focus-color: white;
-fx-background-color: white;
-fx-padding: 0px;
}
.table-row-cell {
-fx-cell-size: 23px;
}
.table-cell:selected {
-fx-background-color: white;
-fx-text-fill: black;
-fx-border-width: 1px;
-fx-border-color: black;
}
----"TableViewCustomFix.java"----
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TableView.TableViewSelectionModel;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.Callback;
public class TableViewCustomFix extends Application {
private TableViewWrapper table = new TableViewWrapper();
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) {
Scene scene = new Scene(new Group());
scene.getStylesheets().add("stylesheet.css");
stage.setTitle("Table View Test");
//stage.setWidth(450);
//stage.setHeight(550);
final Label label = new Label("Address Book");
label.setFont(new Font("Arial", 20));
ObservableList<TableColumn> columns = FXCollections.observableArrayList(
new TableColumn("First Name"),
new TableColumn("Last Name"),
new TableColumn("Email"));
table = new TableViewWrapper(columns);
String[][] data = new String[][]{
{"Jacob", "Smith", "jacob.smith@example.com"},
{"Isabella", "Johnson", "isabella.johnson@example.com"},
{"Ethan", "Williams", "ethan.williams@example.com"},
{"Emma", "Jones", "emma.jones@example.com"},
{"Michael", "", "michael.brown@example.com"}
};
table.setItemsStringArray(data);
/*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) t.getTableView().getItems()
.get(t.getTablePosition().getRow()))
.setFirstName(t.getNewValue());
}
});
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) t.getTableView().getItems()
.get(t.getTablePosition().getRow()))
.setLastName(t.getNewValue());
}
});
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) t.getTableView().getItems()
.get(t.getTablePosition().getRow())).setEmail(t
.getNewValue());
}
});*/
//table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10, 0, 0, 10));
vbox.getChildren().addAll(label, table);
((Group) scene.getRoot()).getChildren().addAll(vbox);
stage.setScene(scene);
stage.show();
}
public static class Person {
private final SimpleStringProperty firstName;
private final SimpleStringProperty lastName;
private final SimpleStringProperty email;
private Person(String fName, String lName, String email) {
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 class TableViewWrapper extends TableView {
public SimpleIntegerProperty rowIndexTracker = new SimpleIntegerProperty(0);
public void setItemsStringArray(String[][] genericData) {
ObservableList<ObservableList> data = FXCollections.observableArrayList();
for(String[] genericRow : genericData) {
ObservableList<String> row = FXCollections.observableArrayList();
for(String item : genericRow) {
System.out.println("Added "+item);
row.add(item);
}
data.add(row);
}
setItems(data);
}
public void initDefaultSettings() {
getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
getSelectionModel().setCellSelectionEnabled(true);
setEditable(true);
}
public TableViewWrapper() {
super();
initDefaultSettings();
}
public TableViewWrapper(ObservableList<TableColumn> columns) {
super();
initDefaultSettings();
for(int i=0; i<columns.size(); i++) {
final int j = i;
Callback<TableColumn, TableCell> cellFactory = new Callback<TableColumn, TableCell>() {
public TableCell call(TableColumn p) {
return new EditingCell();
}
};
TableColumn col = columns.get(j);
col.setCellFactory(cellFactory);
col.setMinWidth(200);
col.setCellValueFactory(new Callback<CellDataFeatures<ObservableList,String>,ObservableValue<String>>(){
public ObservableValue<String> call(CellDataFeatures<ObservableList, String> param) {
return new SimpleStringProperty(param.getValue().get(j).toString());
}
});
col.setOnEditCommit(new EventHandler<CellEditEvent<ObservableList, String>>() {
@Override
public void handle(CellEditEvent<ObservableList, String> t) {
TableViewSelectionModel tableSelectionModel = getSelectionModel();
if(tableSelectionModel.getSelectedCells().size() == 1 && tableSelectionModel.isCellSelectionEnabled()) {
TablePosition cell = (TablePosition) tableSelectionModel.getSelectedCells().get(0);
t.getTableView().getItems().get(j).set(cell.getColumn(),t.getNewValue());
}
}
});
getColumns().addAll(col);
}
}
}
class EditingCell extends TableCell<Person, String> {
private TextField textField;
public EditingCell() {
addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
TableViewSelectionModel tableSelectionModel = getTableView().getSelectionModel();
System.out.println("Mouse event happening at all\tEditing: "+isEditing()+"\tSelectionSize: "+tableSelectionModel.getSelectedCells().size());
if (tableSelectionModel.getSelectedCells().size() == 1) {
TablePosition cell = (TablePosition) tableSelectionModel.getSelectedCells().get(0);
System.out.println("Single CellSelectionEnabled: "+tableSelectionModel.isCellSelectionEnabled());
if(!tableSelectionModel.isCellSelectionEnabled()) {
table.getSelectionModel().setCellSelectionEnabled(true);
tableSelectionModel.clearSelection();
}
tableSelectionModel.select(cell.getRow(), getTableColumn());
getTableView().edit(cell.getRow(), cell.getTableColumn());
((TableViewWrapper)getTableView()).rowIndexTracker.set(cell.getRow());
updateItem(textField.textProperty().get(), false);
} else if(tableSelectionModel.getSelectedCells().size() > 1) {
ArrayList<Integer> selectedRows = new ArrayList<Integer>();
TablePosition cell = (TablePosition) tableSelectionModel.getSelectedCells().get(0);
for(Object cellObject : tableSelectionModel.getSelectedCells()) {
cell = (TablePosition) cellObject;
selectedRows.add(cell.getRow());
System.out.println("adding row to selection: "+cell.getRow());
System.out.println("Multi CellSelectionEnabled: "+tableSelectionModel.isCellSelectionEnabled());
}
tableSelectionModel.clearSelection();
table.getSelectionModel().setCellSelectionEnabled(false);
for(Integer row : selectedRows) {
System.out.println("selecting row: "+row);
tableSelectionModel.select(row, cell.getTableColumn());
}
}
}
});
}
@Override
public void startEdit() {
TableViewSelectionModel tableSelectionModel = getTableView().getSelectionModel();
System.out.println("StartEdit CellSelectionEnabled: "+tableSelectionModel.isCellSelectionEnabled());
if (!isEmpty()) {
super.startEdit();
if (textField == null) {
createTextField();
}
TablePosition cell = (TablePosition) tableSelectionModel.getSelectedCells().get(0);
System.out.println("started editing row "+cell.getRow());
((TableViewWrapper)getTableView()).rowIndexTracker.set(cell.getRow());
setGraphic(textField);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
Platform.runLater(new Runnable() {
@Override
public void run() {
textField.requestFocus();
}
});
}
}
@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());
}
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.focusedProperty().addListener(
new ChangeListener<Boolean>() {
@Override
public void changed(
ObservableValue<? extends Boolean> arg0,
Boolean arg1, Boolean arg2) {
if (!arg2) {
commitEdit(textField.getText());
}
}
});
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();
} else if (t.getCode() == KeyCode.TAB) {
commitEdit(textField.getText());
TableColumn nextColumn = getNextColumn(!t.isShiftDown());
int nextRowIndex = getNextRowIndex(!t.isShiftDown());
if (nextColumn != null) {
getTableView().edit(nextRowIndex,
nextColumn);
TableViewSelectionModel tableSelectionModel = getTableView().getSelectionModel();
tableSelectionModel.clearSelection();
tableSelectionModel.select(nextRowIndex, nextColumn);
((TableViewWrapper)getTableView()).rowIndexTracker.set(nextRowIndex);
}
}
}
});
textField.focusedProperty().addListener(new ChangeListener<Boolean>(){
@Override
public void changed(ObservableValue<? extends Boolean> arg0,
Boolean arg1, Boolean arg2) {
if (!arg2) {
System.out.println("changed");
commitEdit(textField.textProperty().get());
updateItem(textField.textProperty().get(), false);
}
}
});
}
private String getString() {
return getItem() == null ? "" : getItem().toString();
}
private TableColumn<Person, ?> getNextColumn(boolean forward) {
List<TableColumn<Person, ?>> columns = new ArrayList<>();
for (TableColumn<Person, ?> column : getTableView().getColumns()) {
columns.addAll(getLeaves(column));
}
// There is no other column that supports editing.
if (columns.size() < 2) {
return null;
}
int currentIndex = columns.indexOf(getTableColumn());
int nextIndex = currentIndex;
if (forward) {
nextIndex++;
if (nextIndex > columns.size() - 1) {
nextIndex = 0;
}
} else {
nextIndex--;
if (nextIndex < 0) {
nextIndex = columns.size() - 1;
}
}
return columns.get(nextIndex);
}
private int getNextColumnIndex(boolean forward) {
List<TableColumn<Person, ?>> columns = new ArrayList<>();
for (TableColumn<Person, ?> column : getTableView().getColumns()) {
columns.addAll(getLeaves(column));
}
// There is no other column that supports editing.
if (columns.size() < 2) {
return 0;
}
int currentIndex = columns.indexOf(getTableColumn());
int nextIndex = currentIndex;
if (forward) {
nextIndex++;
if (nextIndex > columns.size() - 1) {
nextIndex = 0;
}
} else {
nextIndex--;
if (nextIndex < 0) {
nextIndex = columns.size() - 1;
}
}
return nextIndex;
}
private int getNextRowIndex(boolean forward) {
int nextColIndex = getNextColumnIndex(forward);
System.out.println("current row="+((TableViewWrapper)getTableView()).rowIndexTracker.get());
if(forward) {
if (nextColIndex == 0) { // If was at last column and reset, then
System.out.println("was at last column and jumped to first column in next row");
if (((TableViewWrapper)getTableView()).rowIndexTracker.get() == getTableView().getItems().size()-1) { // If current row is the last row, then
System.out.println("last row");
return 0; // Select first row, first column
} else { // Else
System.out.println("not last row");
return ((TableViewWrapper)getTableView()).rowIndexTracker.get()+1; // Select next row
}
} else {
System.out.println("not last column");
return ((TableViewWrapper)getTableView()).rowIndexTracker.get(); // Select next column
}
} else {
if (nextColIndex == getTableView().getColumns().size()-1) { // If was at first column and reset, then
System.out.println("was at first column and jumped to last column in previous row");
if (((TableViewWrapper)getTableView()).rowIndexTracker.get() == 0) { // If current row is the first row, then
System.out.println("first row");
return getTableView().getItems().size()-1; // Select last row, last column
} else { // Else
System.out.println("not first row");
return ((TableViewWrapper)getTableView()).rowIndexTracker.get()-1; // Select previous row
}
} else {
System.out.println("not first column");
return ((TableViewWrapper)getTableView()).rowIndexTracker.get(); // Select next column
}
}
}
private List<TableColumn<Person, ?>> getLeaves(
TableColumn<Person, ?> root) {
List<TableColumn<Person, ?>> columns = new ArrayList<>();
if (root.getColumns().isEmpty()) {
// We only want the leaves that are editable.
if (root.isEditable()) {
columns.add(root);
}
return columns;
} else {
for (TableColumn<Person, ?> column : root.getColumns()) {
columns.addAll(getLeaves(column));
}
return columns;
}
}
}
}
Here is a list of working functions it can perform:
1) When editing a cell, you can transverse to the next cell by pressing TAB (the SHIFT key toggles forwards and backwards)
2) By selecting a cell, holding SHIFT, and selecting different cell, the TableView disables CellSelection and selects all involved rows
Here is a list of functions I have not attempted to implement:
1) Transverse cells using arrow keys while editing a cell
2) Add/Delete row functionality
Things to look at:
*Uses of the "updateItems()" method. Where in other code samples it is used much less, it is critical in my instance for making sure the cells do not come up blank.
*EDIT: Look at how I must create a property in my TableView wrapper class to keep track of rows. This is because the EditingCell cannot update its row index before the TAB functions are executed.
Here is a list of functions I have implemented that respond with bugs and errors and how to duplicate:
Test 1
1) Click the cell in the second column, first row, to enter cell editing ("Smith"). Hold SHIFT and click the cell in the second column, last row to select multiple rows ("Brown").
2) Click the cell in the first column, first row ("Jacob"). RESULT: Selects the cell but does not enter editing state.
3) Repeat step 1. Click the second column, first row. RESULT: Sometimes it enters an editing state, but ignores cell border styling. Most times it gives the results in step 2.
Test 2
1) Click the rows below the last row.
Test 3
1) Restart the program.
2) Click the "First Name" column title to sort.
3) Click all cells in the first column starting from top to bottom, toggling the edit state on all of them.
4) Repeat steps 2 and 3. RESULT: Inconsistent/duplicating values
Additional concerns:
1) The results of Test 3 could be due to the fact that I do not have the columns binded to a controller class, but then how would I build an API to programmatically do this, allowing dynamic adding of columns? If each column must be binded to a property, how do you dynamically add properties?
2) Although form follows function, I need JavaFX because it looks good. I'll probably backtrack until I have isolated all the functions that work, then add more TableView functions incrementally. TableView's display features work like a charm, but when it comes to editing its items, it seems things get very complicated, and I need it to behave similar to Excel as that is what my customers use as supplemental software to their daily operations. I do not want to confuse them by having them switch back and forth between two different types of essentially "spreadsheets" that have completely different functionality.
----"stylesheet.css"----
.text-field {
-fx-focus-color: white;
-fx-background-color: white;
-fx-padding: 0px;
}
.table-row-cell {
-fx-cell-size: 23px;
}
.table-cell:selected {
-fx-background-color: white;
-fx-text-fill: black;
-fx-border-width: 1px;
-fx-border-color: black;
}
----"TableViewCustomFix.java"----
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TableView.TableViewSelectionModel;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.Callback;
public class TableViewCustomFix extends Application {
private TableViewWrapper table = new TableViewWrapper();
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) {
Scene scene = new Scene(new Group());
scene.getStylesheets().add("stylesheet.css");
stage.setTitle("Table View Test");
//stage.setWidth(450);
//stage.setHeight(550);
final Label label = new Label("Address Book");
label.setFont(new Font("Arial", 20));
ObservableList<TableColumn> columns = FXCollections.observableArrayList(
new TableColumn("First Name"),
new TableColumn("Last Name"),
new TableColumn("Email"));
table = new TableViewWrapper(columns);
String[][] data = new String[][]{
{"Jacob", "Smith", "jacob.smith@example.com"},
{"Isabella", "Johnson", "isabella.johnson@example.com"},
{"Ethan", "Williams", "ethan.williams@example.com"},
{"Emma", "Jones", "emma.jones@example.com"},
{"Michael", "", "michael.brown@example.com"}
};
table.setItemsStringArray(data);
/*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) t.getTableView().getItems()
.get(t.getTablePosition().getRow()))
.setFirstName(t.getNewValue());
}
});
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) t.getTableView().getItems()
.get(t.getTablePosition().getRow()))
.setLastName(t.getNewValue());
}
});
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) t.getTableView().getItems()
.get(t.getTablePosition().getRow())).setEmail(t
.getNewValue());
}
});*/
//table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10, 0, 0, 10));
vbox.getChildren().addAll(label, table);
((Group) scene.getRoot()).getChildren().addAll(vbox);
stage.setScene(scene);
stage.show();
}
public static class Person {
private final SimpleStringProperty firstName;
private final SimpleStringProperty lastName;
private final SimpleStringProperty email;
private Person(String fName, String lName, String email) {
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 class TableViewWrapper extends TableView {
public SimpleIntegerProperty rowIndexTracker = new SimpleIntegerProperty(0);
public void setItemsStringArray(String[][] genericData) {
ObservableList<ObservableList> data = FXCollections.observableArrayList();
for(String[] genericRow : genericData) {
ObservableList<String> row = FXCollections.observableArrayList();
for(String item : genericRow) {
System.out.println("Added "+item);
row.add(item);
}
data.add(row);
}
setItems(data);
}
public void initDefaultSettings() {
getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
getSelectionModel().setCellSelectionEnabled(true);
setEditable(true);
}
public TableViewWrapper() {
super();
initDefaultSettings();
}
public TableViewWrapper(ObservableList<TableColumn> columns) {
super();
initDefaultSettings();
for(int i=0; i<columns.size(); i++) {
final int j = i;
Callback<TableColumn, TableCell> cellFactory = new Callback<TableColumn, TableCell>() {
public TableCell call(TableColumn p) {
return new EditingCell();
}
};
TableColumn col = columns.get(j);
col.setCellFactory(cellFactory);
col.setMinWidth(200);
col.setCellValueFactory(new Callback<CellDataFeatures<ObservableList,String>,ObservableValue<String>>(){
public ObservableValue<String> call(CellDataFeatures<ObservableList, String> param) {
return new SimpleStringProperty(param.getValue().get(j).toString());
}
});
col.setOnEditCommit(new EventHandler<CellEditEvent<ObservableList, String>>() {
@Override
public void handle(CellEditEvent<ObservableList, String> t) {
TableViewSelectionModel tableSelectionModel = getSelectionModel();
if(tableSelectionModel.getSelectedCells().size() == 1 && tableSelectionModel.isCellSelectionEnabled()) {
TablePosition cell = (TablePosition) tableSelectionModel.getSelectedCells().get(0);
t.getTableView().getItems().get(j).set(cell.getColumn(),t.getNewValue());
}
}
});
getColumns().addAll(col);
}
}
}
class EditingCell extends TableCell<Person, String> {
private TextField textField;
public EditingCell() {
addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
TableViewSelectionModel tableSelectionModel = getTableView().getSelectionModel();
System.out.println("Mouse event happening at all\tEditing: "+isEditing()+"\tSelectionSize: "+tableSelectionModel.getSelectedCells().size());
if (tableSelectionModel.getSelectedCells().size() == 1) {
TablePosition cell = (TablePosition) tableSelectionModel.getSelectedCells().get(0);
System.out.println("Single CellSelectionEnabled: "+tableSelectionModel.isCellSelectionEnabled());
if(!tableSelectionModel.isCellSelectionEnabled()) {
table.getSelectionModel().setCellSelectionEnabled(true);
tableSelectionModel.clearSelection();
}
tableSelectionModel.select(cell.getRow(), getTableColumn());
getTableView().edit(cell.getRow(), cell.getTableColumn());
((TableViewWrapper)getTableView()).rowIndexTracker.set(cell.getRow());
updateItem(textField.textProperty().get(), false);
} else if(tableSelectionModel.getSelectedCells().size() > 1) {
ArrayList<Integer> selectedRows = new ArrayList<Integer>();
TablePosition cell = (TablePosition) tableSelectionModel.getSelectedCells().get(0);
for(Object cellObject : tableSelectionModel.getSelectedCells()) {
cell = (TablePosition) cellObject;
selectedRows.add(cell.getRow());
System.out.println("adding row to selection: "+cell.getRow());
System.out.println("Multi CellSelectionEnabled: "+tableSelectionModel.isCellSelectionEnabled());
}
tableSelectionModel.clearSelection();
table.getSelectionModel().setCellSelectionEnabled(false);
for(Integer row : selectedRows) {
System.out.println("selecting row: "+row);
tableSelectionModel.select(row, cell.getTableColumn());
}
}
}
});
}
@Override
public void startEdit() {
TableViewSelectionModel tableSelectionModel = getTableView().getSelectionModel();
System.out.println("StartEdit CellSelectionEnabled: "+tableSelectionModel.isCellSelectionEnabled());
if (!isEmpty()) {
super.startEdit();
if (textField == null) {
createTextField();
}
TablePosition cell = (TablePosition) tableSelectionModel.getSelectedCells().get(0);
System.out.println("started editing row "+cell.getRow());
((TableViewWrapper)getTableView()).rowIndexTracker.set(cell.getRow());
setGraphic(textField);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
Platform.runLater(new Runnable() {
@Override
public void run() {
textField.requestFocus();
}
});
}
}
@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());
}
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.focusedProperty().addListener(
new ChangeListener<Boolean>() {
@Override
public void changed(
ObservableValue<? extends Boolean> arg0,
Boolean arg1, Boolean arg2) {
if (!arg2) {
commitEdit(textField.getText());
}
}
});
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();
} else if (t.getCode() == KeyCode.TAB) {
commitEdit(textField.getText());
TableColumn nextColumn = getNextColumn(!t.isShiftDown());
int nextRowIndex = getNextRowIndex(!t.isShiftDown());
if (nextColumn != null) {
getTableView().edit(nextRowIndex,
nextColumn);
TableViewSelectionModel tableSelectionModel = getTableView().getSelectionModel();
tableSelectionModel.clearSelection();
tableSelectionModel.select(nextRowIndex, nextColumn);
((TableViewWrapper)getTableView()).rowIndexTracker.set(nextRowIndex);
}
}
}
});
textField.focusedProperty().addListener(new ChangeListener<Boolean>(){
@Override
public void changed(ObservableValue<? extends Boolean> arg0,
Boolean arg1, Boolean arg2) {
if (!arg2) {
System.out.println("changed");
commitEdit(textField.textProperty().get());
updateItem(textField.textProperty().get(), false);
}
}
});
}
private String getString() {
return getItem() == null ? "" : getItem().toString();
}
private TableColumn<Person, ?> getNextColumn(boolean forward) {
List<TableColumn<Person, ?>> columns = new ArrayList<>();
for (TableColumn<Person, ?> column : getTableView().getColumns()) {
columns.addAll(getLeaves(column));
}
// There is no other column that supports editing.
if (columns.size() < 2) {
return null;
}
int currentIndex = columns.indexOf(getTableColumn());
int nextIndex = currentIndex;
if (forward) {
nextIndex++;
if (nextIndex > columns.size() - 1) {
nextIndex = 0;
}
} else {
nextIndex--;
if (nextIndex < 0) {
nextIndex = columns.size() - 1;
}
}
return columns.get(nextIndex);
}
private int getNextColumnIndex(boolean forward) {
List<TableColumn<Person, ?>> columns = new ArrayList<>();
for (TableColumn<Person, ?> column : getTableView().getColumns()) {
columns.addAll(getLeaves(column));
}
// There is no other column that supports editing.
if (columns.size() < 2) {
return 0;
}
int currentIndex = columns.indexOf(getTableColumn());
int nextIndex = currentIndex;
if (forward) {
nextIndex++;
if (nextIndex > columns.size() - 1) {
nextIndex = 0;
}
} else {
nextIndex--;
if (nextIndex < 0) {
nextIndex = columns.size() - 1;
}
}
return nextIndex;
}
private int getNextRowIndex(boolean forward) {
int nextColIndex = getNextColumnIndex(forward);
System.out.println("current row="+((TableViewWrapper)getTableView()).rowIndexTracker.get());
if(forward) {
if (nextColIndex == 0) { // If was at last column and reset, then
System.out.println("was at last column and jumped to first column in next row");
if (((TableViewWrapper)getTableView()).rowIndexTracker.get() == getTableView().getItems().size()-1) { // If current row is the last row, then
System.out.println("last row");
return 0; // Select first row, first column
} else { // Else
System.out.println("not last row");
return ((TableViewWrapper)getTableView()).rowIndexTracker.get()+1; // Select next row
}
} else {
System.out.println("not last column");
return ((TableViewWrapper)getTableView()).rowIndexTracker.get(); // Select next column
}
} else {
if (nextColIndex == getTableView().getColumns().size()-1) { // If was at first column and reset, then
System.out.println("was at first column and jumped to last column in previous row");
if (((TableViewWrapper)getTableView()).rowIndexTracker.get() == 0) { // If current row is the first row, then
System.out.println("first row");
return getTableView().getItems().size()-1; // Select last row, last column
} else { // Else
System.out.println("not first row");
return ((TableViewWrapper)getTableView()).rowIndexTracker.get()-1; // Select previous row
}
} else {
System.out.println("not first column");
return ((TableViewWrapper)getTableView()).rowIndexTracker.get(); // Select next column
}
}
}
private List<TableColumn<Person, ?>> getLeaves(
TableColumn<Person, ?> root) {
List<TableColumn<Person, ?>> columns = new ArrayList<>();
if (root.getColumns().isEmpty()) {
// We only want the leaves that are editable.
if (root.isEditable()) {
columns.add(root);
}
return columns;
} else {
for (TableColumn<Person, ?> column : root.getColumns()) {
columns.addAll(getLeaves(column));
}
return columns;
}
}
}
}