0

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>
Adrian M.
  • 360
  • 7
  • 17
  • Your cells don't have any valid values associated with them: the argument to the `PropertyValueFactory` is supposed to be the name of a property in the model class (i.e. `integerVariable`, `doubleVariable`, `object`, etc). I think your cells are all empty because of this, so your `startEdit` is a no-op. Read the [Javadocs for `PropertyValueFactory`](http://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/cell/PropertyValueFactory.html) to see how it works. – James_D Dec 19 '15 at 23:07
  • Thanks for your answer. I used the propertyvaluefactory and the changes are committed. However, the values are lost after the cell is taken out of view by scrolling. I'm not sure if this is my not providing the instances for the values or if it's a bug in Java. Hope it's the former. Either way, using the `propertyValueFactory` precludes the use of my own `TableCell` subclass, which I was trying to avoid. The use of the subclass is an alternative to the propertyValueFactory method given in a java tutorial, so I figured it was acceptable. – Adrian M. Dec 20 '15 at 15:06
  • How does the `PropertyValueFactory` preclude the use of your `TableCell` subclass? If the cell values are reverting when you scroll, it means the `onEditCommit` is either not being invoked, or is not updating the model correctly. Note that your focus listener will not work in Java 8 (see http://stackoverflow.com/questions/29576577/tableview-doesnt-commit-values-on-focus-lost-event) – James_D Dec 20 '15 at 15:07

0 Answers0