JavaFX Table Cell Editing
Asked Answered
B

3

17

I am trying to make a program in Java to manage my bookie accounts. I'm new to java, so I thought I would chose something simple to see how things work. I decided to use a tableview and make the individual cells editable. I've been following this tutorial http://java-buddy.blogspot.co.uk/2012/04/javafx-2-editable-tableview.html. It details how to do it using java code, and copying that into a new class works perfectly. I decided to try and tweak it to work with FXML, since I like Sceneviewer. My issue is, data is loaded in to the table, but when I click/doubleclick a cell, nothing happens. Here's my code.

testController.java

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.util.Callback;

import java.net.URL;
import java.util.ResourceBundle;

public class testController implements Initializable {

@FXML
private TableColumn<Account, String> usernameCol;

@FXML
private TableColumn<Account, String> balanceCol;

@FXML
private TableView<Account> accountTable;

@FXML
private TableColumn<Account, String> bookieCol;

@FXML
private TableColumn<Account, String> passwordCol;

private ObservableList<Account> dataList =
        FXCollections.observableArrayList(
                new Account("bookie", "username", "password", "0"));


@Override
public void initialize(URL location, ResourceBundle resources) {
    Callback<TableColumn, TableCell> cellFactory =
            new Callback<TableColumn, TableCell>() {
                public TableCell call(TableColumn p) {
                    return new EditingCell();
                }
            };

    bookieCol.setCellValueFactory(
            new PropertyValueFactory<>("fieldBookie"));

    usernameCol.setCellValueFactory(
            new PropertyValueFactory<>("fieldUsername"));

    usernameCol.setOnEditCommit(
            new EventHandler<TableColumn.CellEditEvent<Account, String>>() {
                @Override public void handle(TableColumn.CellEditEvent<Account, String> t) {
                    ((Account)t.getTableView().getItems().get(
                            t.getTablePosition().getRow())).setFieldUsername(t.getNewValue());
                }
            });

    passwordCol.setCellValueFactory(
            new PropertyValueFactory<Account, String>("fieldPassword"));

    balanceCol.setCellValueFactory(
            new PropertyValueFactory<Account, String>("fieldBalance"));

    accountTable.setItems(dataList);

}

class EditingCell extends TableCell<Account, String> {

    private TextField textField;

    public EditingCell() {
    }

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

        if (textField == null) {
            createTextField();
        }

        setGraphic(textField);
        setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
        textField.selectAll();
    }

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

        setText(String.valueOf(getItem()));
        setContentDisplay(ContentDisplay.TEXT_ONLY);
    }

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

        if (empty) {
            setText(null);
            setGraphic(textField);
        } 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(t -> {
            if (t.getCode() == KeyCode.ENTER) {
                commitEdit(textField.getText());
            } else if (t.getCode() == KeyCode.ESCAPE) {
                cancelEdit();
            }
        });
    }

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

And here is my Account.java file.

import javafx.beans.property.SimpleStringProperty;


public class Account {
    private SimpleStringProperty fieldBookie;
    private SimpleStringProperty fieldUsername;
    private SimpleStringProperty fieldPassword;
    private SimpleStringProperty fieldBalance;

    Account(String fbookie, String fusername, String fpassword, String fbalance){
        this.fieldBookie = new SimpleStringProperty(fbookie);
        this.fieldUsername = new SimpleStringProperty(fusername);
        this.fieldPassword = new SimpleStringProperty(fpassword);
        this.fieldBalance = new SimpleStringProperty(fbalance);
    }

    public String getFieldBookie() {
        return fieldBookie.get();
    }

    public String getFieldUsername() {
        return fieldUsername.get();
    }

    public String getFieldPassword() {
        return fieldPassword.get();
    }

    public String getFieldBalance() {
        return fieldBalance.get();
    }

    public void setFieldBookie(String fBookie) {
        fieldBookie.set(fBookie);
    }

    public void setFieldUsername(String fUsername) {
        fieldUsername.set(fUsername);
    }

    public void setFieldPassword(String fPassword) {
        fieldUsername.set(fPassword);
    }

    public void setFieldBalance(String fBalance) {
        fieldUsername.set(fBalance);
    }

}

Lastly, here is my fxml file.

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.*?>
<?import javafx.scene.canvas.*?>
<?import java.lang.*?>
<?import javafx.scene.*?>

<Group xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="testController">
   <children>
      <TableView fx:id="accountTable" editable="true" prefHeight="291.0" prefWidth="302.0">
        <columns>
          <TableColumn fx:id="bookieCol" prefWidth="75.0" text="Bookie" />
          <TableColumn fx:id="usernameCol" prefWidth="75.0" text="Username" />
            <TableColumn fx:id="passwordCol" prefWidth="75.0" text="Password" />
            <TableColumn fx:id="balanceCol" prefWidth="75.0" text="Balance" />
        </columns>
      </TableView>
   </children>
</Group>

As I said earlier, nothing happens when I click on the Username cell. No errors, no textfield, just nothing. Any help would be greatly appreciated! Thanks

Burn answered 14/7, 2014 at 8:58 Comment(0)
I
10

I haven`t tried your example, but I think you just forgot to set the cellFactory for the specific column. Adding the folowing line should fix it:

usernameCol.setCellFactory(cellFactory);
Innermost answered 14/7, 2014 at 9:25 Comment(7)
That's correct, but you will also need to fix the types (Callback<TableColumn<Account, String>, TableCell<Account, String>> etc). This approach is a bit legacy: you can do usernameCol.setCellFactory(TextFieldTableCell.forTableColumn()); and get rid of the EditingCellPorky
Okay thanks, I'll try it now. There aren't many tutorials around for javafx and editable tables unfortunately :(Burn
It's not great, but the default Oracle tutorial covers the basics.Porky
It worked perfectly, I removed the editing cell class too! Less code and is a lot easier for me to understand! Thanks both.Burn
Yes, but edit commit happens after pressing Enter key. I would like to save edit after changing cell or row. And I still don't know how.Factoring
@OrdinaryDraft you may want to have a look here: #24695116 . I was facing the same challenge and unfortunatly it is not an easy thing to achieve. Not sure if there is a better way yet, but kleopatra was able to help with decent workaround.Innermost
I already copied @Ogmios class from this thread and it works the way I want, thank you for link anyway.Factoring
N
10

I case someone needs a working example, I was able to get the code to work with this tutorial by adding a

usernameCol.setCellFactory(
                TextFieldTableCell.forTableColumn());

and changing the usernameCol.setOnEditCommit to

usernameCol.setOnEditCommit(
                (TableColumn.CellEditEvent<Account, String> t) ->
                    ( t.getTableView().getItems().get(
                            t.getTablePosition().getRow())
                    ).setFieldUsername(t.getNewValue())
                );

Here is the complete testController class that should be working (Other files stayed the same)

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;

import java.net.URL;
import java.util.ResourceBundle;

public class testController implements Initializable {

    @FXML
    private TableColumn<Account, String> usernameCol;

    @FXML
    private TableColumn<Account, String> balanceCol;

    @FXML
    private TableView<Account> accountTable;

    @FXML
    private TableColumn<Account, String> bookieCol;

    @FXML
    private TableColumn<Account, String> passwordCol;

    private ObservableList<Account> dataList =
            FXCollections.observableArrayList(
                    new Account("bookie", "username", "password", "0"));


    @Override
    public void initialize(URL location, ResourceBundle resources) {
        bookieCol.setCellValueFactory(
                new PropertyValueFactory<>("fieldBookie"));

        usernameCol.setCellValueFactory(
                new PropertyValueFactory<>("fieldUsername"));

        usernameCol.setCellFactory(
                TextFieldTableCell.forTableColumn());

        usernameCol.setOnEditCommit(
                (TableColumn.CellEditEvent<Account, String> t) ->
                    ( t.getTableView().getItems().get(
                            t.getTablePosition().getRow())
                    ).setFieldUsername(t.getNewValue())
                );

        passwordCol.setCellValueFactory(
                new PropertyValueFactory<Account, String>("fieldPassword"));

        balanceCol.setCellValueFactory(
                new PropertyValueFactory<Account, String>("fieldBalance"));

        accountTable.setItems(dataList);

    }
}
Nankeen answered 8/12, 2016 at 16:6 Comment(0)
M
2

You can add the textField to editing cell using :TextFieldTableCell.forTableColumn(); If you need comboBox try : ComboBoxTableCell.forTableColumn(list);

// TextField:
usernameCol.setCellFactory(TextFieldTableCell.forTableColumn());

// ComboBox:
ObservableList<String> list = FXCollections.observableArrayList();
list.add("name 1");
list.add("name 2");
list.add("name 3");
list.add("name 4");

Callback<TableColumn<Account, String>, TableCell<Account, String>> cbtc = 
    ComboBoxTableCell.forTableColumn(list);

usernameCol.setCellFactory(cbtc);
Materialist answered 11/1, 2015 at 22:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.