Multiple Components in one column of JavaFX TableView

user1414600 picture user1414600 · Dec 13, 2012 · Viewed 9.6k times · Source

I am working with JavaFx 2.2. I am having a problem that I am not able to place different components in a TableView Column. For example I have two columns

1) Answer

2) AnswerType

If AnswerType contains “Multiple Choice” then the corresponding cell in Answer Column should display a ComboBox else it should display a TextField.

I have a code example below but its displaying either ComboBox or TextField but not both in different cells of same column.

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Callback;
import javafx.scene.control.cell.ComboBoxTableCell;

public class TableCellWithMultipleComponent extends Application {

     @SuppressWarnings("rawtypes")
TableColumn answerTypeCol; 
@SuppressWarnings("rawtypes")
TableColumn answerCol; 
ObservableList<String> namesChoiceList;

public static void main(String[] args) {
    launch(args);
}

@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public void start(final Stage primaryStage) {
    primaryStage.setTitle("Table Cell With Multiple Components");

     TableView<Person> table = new TableView<Person>();
     table.setEditable(true);
      final ObservableList<Person> data = 
                FXCollections.observableArrayList(
                    new Person("A", "Multiple Choice"),
                    new Person("JOHN", "Free Text"),
                    new Person("123", "Free Text"),
                    new Person("D", "Multiple Choice")
                );

    GridPane gridpane = new GridPane();
    gridpane.setPadding(new Insets(5));
    gridpane.setHgap(5);
    gridpane.setVgap(5);


    namesChoiceList = FXCollections.observableArrayList("A", "B", "C", "D", "INVALID_ANSWER", "NO_ANSWER");

    answerCol = new TableColumn();
    answerCol.setText("Answers");
    answerCol.setMinWidth(210);
    answerCol.setEditable(true);
    answerCol.setCellValueFactory(new PropertyValueFactory("answers"));


    answerCol.setCellFactory( new Callback<TableColumn<String, String>, TableCell<String, String>>() {
        @Override
        public TableCell<String, String> call(TableColumn<String, String> arg0) {
            return new anyMethod();
        }
    });



    answerTypeCol = new TableColumn();
    answerTypeCol.setText("Answers Type");
    answerTypeCol.setMinWidth(210);
    answerTypeCol.setEditable(true);
    answerTypeCol.setCellValueFactory(new PropertyValueFactory("answersType"));



    table.setItems(data);
    table.getColumns().addAll(answerCol, answerTypeCol);

    StackPane root = new StackPane();

    Scene scene =new Scene(root, 500, 550);

    gridpane.add(table, 1, 5,1,20 );


    root.getChildren().addAll(gridpane);
    primaryStage.setScene(scene);
    primaryStage.show();

   }


  private class anyMethod extends TableCell <String, String>{


    @SuppressWarnings("unchecked")
    @Override
    protected void updateItem(String item, boolean arg1) {
        super.updateItem(item, arg1);

        answerCol.setCellFactory(ComboBoxTableCell.<String, String>forTableColumn(namesChoiceList));

        /****  I have to execute this commented code so that if the column cell has text "Multiple Choice" then
         * it displays the comboBox otherwise it displays the text field in the Table View cell

        if (item.equalsIgnoreCase("Multiple Choice")){
            answerCol.setCellFactory(ComboBoxTableCell.<String, String>forTableColumn(namesChoiceList));
        }
        else{
            //answerCol.setCellFactory(TextFieldTableCell.<String>forTableColumn());
        }
    ****/
    }

}


public static class Person {
    private final SimpleStringProperty answers;
    private final SimpleStringProperty answersType;


    private Person(String answers, String answersType) {
        this.answers = new SimpleStringProperty(answers);
        this.answersType = new SimpleStringProperty(answersType);
    }

    public String getAnswers() {
        return answers.get();
    }
    public void setAnswers(String answers) {
        this.answers.set(answers);
    }

    public String getAnswersType() {
        return answersType.get();
    }
    public void setAnswersType(String answersType) {
        this.answersType.set(answersType);
    }
   }



}

Answer

jewelsea picture jewelsea · Dec 13, 2012

Here is a sample for an EditingCell which renders a different control in the cell (TextEdit field or Checkbox) depending on the type of data represented by the Cell's backing field (String or Boolean). Complete executable code is available as a gist.

For your particular example, use the same concept except query the type for either a String => TextField or ObservableList => combobox. Also, for your particular sample, ChoiceBox may be a simpler control to use than ComboBox.

class EditingCell extends TableCell<NamedProperty, Object> {
private TextField textField;
private CheckBox checkBox;
public EditingCell() {}

@Override public void startEdit() {
  if (!isEmpty()) {
    super.startEdit();

    if (getItem() instanceof Boolean) {
      createCheckBox();
      setText(null);
      setGraphic(checkBox);
    } else {
      createTextField();
      setText(null);
      setGraphic(textField);
      textField.selectAll();
    }  
  }
}

@Override public void cancelEdit() {
  super.cancelEdit();

  if (getItem() instanceof Boolean) {
    setText(getItem().toString());
  } else {
    setText((String) getItem());
  }  
  setGraphic(null);
}

@Override public void updateItem(Object item, boolean empty) {
  super.updateItem(item, empty);

  if (empty) {
    setText(null);
    setGraphic(null);
  } else {
    if (isEditing()) {
      if (getItem() instanceof Boolean) {
        if (checkBox != null) {
          checkBox.setSelected(getBoolean());
        }
        setText(null);
        setGraphic(checkBox);
      } else {
        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> observable, Boolean oldValue, Boolean newValue) {
      if (!newValue) {
        commitEdit(textField.getText());
      }
    }
  });
}

private void createCheckBox() {
  checkBox = new CheckBox();
  checkBox.setSelected(getBoolean());
  checkBox.setMinWidth(this.getWidth() - this.getGraphicTextGap()* 2);
  checkBox.focusedProperty().addListener(new ChangeListener<Boolean>() {
    @Override public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
      if (!newValue) {
        commitEdit(checkBox.isSelected());
      }
    }
  });
}

private String getString() {
  return getItem() == null ? "" : getItem().toString();
}

private Boolean getBoolean() {
  return getItem() == null ? false : (Boolean) getItem();
}
}