The problem is that after creating a custom table cell renderer by extending class TableCell, which I've included at the bottom, no cells can be focused, and therefore edited. This cropped up in the process of attempting to specify clickable cells, intuitively after the cell factory was specified and used instead of TextFieldTableCell.<Variable>forTableColumn()
. Ultimately the result is a focusable rows, with no editable cells.
The cell factory is constructed thusly:
Callback<TableColumn<Variable,Object>, TableCell<Variable,Object>>
cellFactory = (TableColumn<Variable, Object> p ) -> new DynamicTableCell();
The column setup is as follows:
TableColumn tabCol = new TableColumn("Variable " + j++);
tabCol.setMinWidth(100);
tabCol.setCellValueFactory(new PropertyValueFactory<Variable, Object>("..."));
tabCol.setCellFactory(cellFactory);
tabCol.setOnEditCommit(new EventHandler<CellEditEvent<Variable, Object>>() {
@Override
public void handle(CellEditEvent<Variable, Object> t) {
((Variable) t.getTableView().getItems().get(
t.getTablePosition().getRow())).setValue(t.getNewValue());
}
});
Below is the TableCell subclass:
public class DynamicTableCell extends TableCell<Variable, Object> {
private TextField textField;
@Override
public void startEdit() {
if(!isEmpty()){
super.startEdit();
createTextField();
setText(null);
textField.selectAll();
}
}
@Override
public void cancelEdit(){
super.cancelEdit();
setText((String) getItem());
setGraphic(null);
}
@Override
protected void updateItem(Object item, boolean empty) {
super.updateItem(item, empty);
if(isEditing()){
if( item != 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((ObservableValue<? extends Boolean> arg0,
Boolean arg1, Boolean arg2) -> {
if(!arg2) {
commitEdit(textField.getText());
}
});
}
private String getString() {
return getItem() == null ? "" : getItem().toString();
}
}
If you'd like to compile and test it yourself, here's the full code:
StatsSoft.java
import javafx.geometry.Insets;
import java.util.HashMap;
import javafx.application.Application;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
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.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.event.EventHandler;
import javafx.scene.control.ScrollPane;
import javafx.util.*;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.TreeView;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class StatsSoft extends Application {
protected final static TableView tableView = new TableView<Variable>();
private HashMap columnMap = new HashMap();
private ObservableList<Variable> data =
FXCollections.observableArrayList(
new Variable((Double)0.00));
@Override
public void start(Stage stage) throws Exception {
BorderPane borderPane = new BorderPane();
AnchorPane root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
borderPane.setPadding(new Insets(0, 0, 0, 0));
HBox hBox = (HBox) root.getChildren().get(0);
hBox.setPadding(new Insets(0, 0, 10, 0));
borderPane.setTop(hBox);
data = FXCollections.observableArrayList();
Callback<TableColumn<Variable,Object>, TableCell<Variable,Object>> cellFactory =
(TableColumn<Variable, Object> p ) -> new DynamicTableCell();
int j = 1;
for(int i = 0; i <= 100; i++){
data.add(new Variable(10.00));
if( i % 5 == 0 && i != 0){
TableColumn tabCol = new TableColumn("Variable " + j++);
tabCol.setMinWidth(100);
tabCol.setCellValueFactory(new PropertyValueFactory<Variable, Object>("..."));
tabCol.setCellFactory(cellFactory);
tabCol.setOnEditCommit(new EventHandler<CellEditEvent<Variable, Object>>() {
@Override
public void handle(CellEditEvent<Variable, Object> t) {
((Variable) t.getTableView().getItems().get(
t.getTablePosition().getRow())).setValue(t.getNewValue());
}
});
columnMap.put(i, tabCol);
}
}
tableView.setItems(data);
for(int i = 0; i <= 100; i++){
if(i % 5 == 0 && i != 0)
tableView.getColumns().add(columnMap.get(i));
}
final ScrollPane scrollPane = new ScrollPane();
scrollPane.setLayoutY(600);
scrollPane.setPadding(new Insets(10, 10, 10, 10));
scrollPane.setPrefWidth(1000);
scrollPane.setFitToWidth(true);
//scrollPane.setMinHeight(595);
scrollPane.setContent(tableView);
borderPane.setCenter(scrollPane);
ToggleButton variableButton = new ToggleButton();
variableButton.setText("Variable");
variableButton.setPadding(new Insets(5, 5, 5, 5));
BorderPane bottomPane = new BorderPane();
bottomPane.setTop(variableButton);
TreeView fileViewer = new TreeView();
fileViewer.setMaxHeight(300);
fileViewer.setPadding(new Insets(50, 50, 10, 50));
bottomPane.setLayoutY(50);
bottomPane.setPadding(new Insets(10, 10, 20, 10));
bottomPane.setBottom(fileViewer);
borderPane.setBottom(bottomPane);
borderPane.setBottom(bottomPane);
Scene scene = new Scene(root);
((AnchorPane)scene.getRoot()).getChildren().addAll(borderPane);
tableView.setEditable(true);
stage.setResizable(false);
stage.setScene(scene);
stage.show();
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
//data model defined below
public static class Variable <T>{
protected SimpleDoubleProperty doubleVariable;
protected SimpleIntegerProperty intVariable;
private SimpleStringProperty nominalVariable;
private SimpleObjectProperty objectVariable;
private Variable( Double value ){
this.doubleVariable = new SimpleDoubleProperty(value);
this.doubleVariable.set(value);
}
private Variable( int value ){
this.intVariable = new SimpleIntegerProperty(value);
}
private Variable( String value ){
this.nominalVariable = new SimpleStringProperty(value);
this.nominalVariable.set(value);
}
private Variable(){
this.doubleVariable = new SimpleDoubleProperty();
this.intVariable = new SimpleIntegerProperty();
this.nominalVariable = new SimpleStringProperty();
this.objectVariable = new SimpleObjectProperty(".");
}
public void setValue( T value ){
if(value instanceof Double){
setDoubleVariable((Double)value);
} else if( value instanceof Integer ){
setIntegerVariable((Integer)value);
} else {
System.out.println(value.getClass());
setStringVariable((String)value);
}
}
public Double getDoubleVariable(){
return this.doubleVariable.get();
}
public void setDoubleVariable(Double doubValue){
this.doubleVariable.set(doubValue);
}
public Integer getIntegerVariable(){
return this.intVariable.get();
}
public void setIntegerVariable(int intValue){
this.intVariable.set(intValue);
}
public String getStringVariable(){
return this.nominalVariable.get();
}
public void setStringVariable(String nomValue){
this.nominalVariable.set(nomValue);
}
public Object getObject(){
return this.objectVariable.get();
}
}
private class DynamicTableCell extends TableCell<Variable, Object> {
private TextField textField;
@Override
public void startEdit() {
if(!isEmpty()){
super.startEdit();
createTextField();
setText(null);
textField.selectAll();
} else {
System.out.println("What?");
}
}
@Override
public void cancelEdit(){
super.cancelEdit();
setText((String) getItem());
setGraphic(null);
}
@Override
protected void updateItem(Object item, boolean empty) {
super.updateItem(item, empty);
if(isEditing()){
if( item != 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((ObservableValue<? extends Boolean> arg0,
Boolean arg1, Boolean arg2) -> {
if(!arg2) {
commitEdit(textField.getText());
}
});
}
private String getString() {
return getItem() == null ? "" : getItem().toString();
}
}
}
FXMLDocumentController.java:
package statssoft;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
public class FXMLDocumentController implements Initializable {
@FXML
private HBox hBox;
@FXML
protected ScrollPane dataPane;
@Override
public void initialize(URL url, ResourceBundle rb) {
}
}
FXMLDocument.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane id="AnchorPane" prefHeight="800.0" prefWidth="1032.0" style="-fx-background-color: #fff;" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="statssoft.FXMLDocumentController">
<children>
<HBox layoutY="7.0" prefHeight="70.0" prefWidth="1032.0">
<children>
<MenuBar prefHeight="29.0" prefWidth="1022.0">
<menus>
<Menu mnemonicParsing="false" text="File">
<items>
<MenuItem mnemonicParsing="false" text="Close" />
</items>
</Menu>
<Menu mnemonicParsing="false" text="Edit">
<items>
<MenuItem mnemonicParsing="false" text="Delete" />
</items>
</Menu>
<Menu mnemonicParsing="false" text="Data">
<items>
<MenuItem mnemonicParsing="false" text="Action 1" />
</items>
</Menu>
<Menu mnemonicParsing="false" text="Transform">
<items>
<MenuItem mnemonicParsing="false" text="Action 1" />
</items>
</Menu>
<Menu mnemonicParsing="false" text="Analysis">
<items>
<MenuItem mnemonicParsing="false" text="Action 1" />
</items>
</Menu>
<Menu mnemonicParsing="false" text="Utilities">
<items>
<MenuItem mnemonicParsing="false" text="Action 1" />
</items>
</Menu>
<Menu mnemonicParsing="false" text="Help">
<items>
<MenuItem mnemonicParsing="false" text="About" />
</items>
</Menu>
</menus>
</MenuBar>
</children>
</HBox>
</children>
</AnchorPane>