1

I have a table which displays information about several trips. One of the columns is driver which tells who is assigned as driver for a certain trip. What I want to achieve is so that if there's no driver assigned (null in database), you should be able to click on a button in that particular cell and choose a driver.

This proved to be more challenging than I initially thought. This is the idea: enter image description here

I found the question below but I'm not sure how that solution can be implemented in my system. JavaFX 2 TableView : different cell factory depending on the data inside the cell

What should I look into? Is there a documented way of how to achieve this?

Here's the code:

public class BookViewController {

    private String dbUrl = "jdbc:postgresql://localhost:5432/BookingApp";
    private String dbUsername = "postgres";
    private String dbPassword = "secret";
    @FXML
    private TextField personnr;
    @FXML
    private TextField name;
    @FXML
    private TextField email;
    @FXML
    private TextField telnr;
    @FXML
    private Button addTraveler;
    @FXML
    private Button removeTraveler;
    @FXML
    private TableView<Traveler> travelers;
    @FXML
    private TableColumn<?, ?> personnrColumn;
    @FXML
    private TableColumn<?, ?> nameColumn;
    @FXML
    private TableColumn<?, ?> emailColumn;
    @FXML
    private TableColumn<?, ?> telnrColumn;
    @FXML
    private TableView<Trip> trips;
    @FXML
    private TableColumn<?, ?> originColumn;
    @FXML
    private TableColumn<?, ?> destinationColumn;
    @FXML
    private TableColumn<?, ?> departureColumn;
    @FXML
    private TableColumn<?, ?> arrivalColumn;
    @FXML
    private TableColumn<?, ?> driverColumn;
    @FXML
    private TableColumn<?, ?> priceAmountColumn;
    @FXML
    private TableColumn<?, ?> seatsColumn;
    @FXML
    private Button bookTrip;
    private Trip rowData;
    private ObservableList<Trip> tripData;

    protected void initialize(Trip rowData) {
        this.rowData = rowData;
        addTraveler.setDisable(true);
        removeTraveler.setDisable(true);
        populateTravelPlan();
    }

    @FXML
    private void populateTravelPlan() {
        originColumn.setCellValueFactory(new PropertyValueFactory<>("origin"));
        destinationColumn.setCellValueFactory(new PropertyValueFactory<>("destination"));
        departureColumn.setCellValueFactory(new PropertyValueFactory<>("departure"));
        arrivalColumn.setCellValueFactory(new PropertyValueFactory<>("arrival"));
        driverColumn.setCellValueFactory(new PropertyValueFactory<>("driver"));
        priceAmountColumn.setCellValueFactory(new PropertyValueFactory<>("priceAmount"));
        seatsColumn.setCellValueFactory(new PropertyValueFactory<>("seats"));        

        try {
            Connection conn = DriverManager.getConnection(dbUrl, dbUsername, dbPassword);
            tripData = FXCollections.observableArrayList();
            ResultSet rs = null;
            if(rowData.getTrip1() != 0 && rowData.getTrip2() == 0 && rowData.getTrip3() == 0) {
                rs = conn.createStatement().executeQuery(
                        "select trip.tripid, connexion.origin, connexion.destination, trip.departure, trip.arrival, trip.driverpnr, trip.priceamount, trip.seats\r\n" + 
                        "from trip, connexion\r\n" + 
                        "where trip.tripid = " + rowData.getTrip1() + "\r\n" + 
                        "and trip.connexionid = connexion.connexionid\r\n" + 
                        "order by departure;");
            } else if(rowData.getTrip1() != 0 && rowData.getTrip2() != 0 && rowData.getTrip3() == 0) {
                rs = conn.createStatement().executeQuery(
                        "select trip.tripid, connexion.origin, connexion.destination, trip.departure, trip.arrival, trip.driverpnr, trip.priceamount, trip.seats\r\n" + 
                        "from trip, connexion\r\n" + 
                        "where trip.tripid = " + rowData.getTrip1() + "\r\n" + 
                        "and trip.connexionid = connexion.connexionid\r\n" + 
                        "union\r\n" + 
                        "select trip.tripid, connexion.origin, connexion.destination, trip.departure, trip.arrival, trip.driverpnr, trip.priceamount, trip.seats\r\n" + 
                        "from trip, connexion\r\n" + 
                        "where trip.tripid = " + rowData.getTrip2() + "\r\n" + 
                        "and trip.connexionid = connexion.connexionid\r\n" + 
                        "order by departure;");
            } else if(rowData.getTrip1() != 0 && rowData.getTrip2() != 0 && rowData.getTrip3() != 0) {
                rs = conn.createStatement().executeQuery(
                        "select trip.tripid, connexion.origin, connexion.destination, trip.departure, trip.arrival, trip.driverpnr, trip.priceamount, trip.seats\r\n" + 
                        "from trip, connexion\r\n" + 
                        "where trip.tripid = " + rowData.getTrip1() + "\r\n" + 
                        "and trip.connexionid = connexion.connexionid\r\n" + 
                        "union\r\n" + 
                        "select trip.tripid, connexion.origin, connexion.destination, trip.departure, trip.arrival, trip.driverpnr, trip.priceamount, trip.seats\r\n" + 
                        "from trip, connexion\r\n" + 
                        "where trip.tripid = " + rowData.getTrip2() + "\r\n" + 
                        "and trip.connexionid = connexion.connexionid\r\n" + 
                        "union\r\n" + 
                        "select trip.tripid, connexion.origin, connexion.destination, trip.departure, trip.arrival, trip.driverpnr, trip.priceamount, trip.seats\r\n" + 
                        "from trip, connexion\r\n" + 
                        "where trip.tripid = " + rowData.getTrip3() + "\r\n" + 
                        "and trip.connexionid = connexion.connexionid\r\n" + 
                        "order by departure;");
            }
            if(rs.next()) {    
                tripData.add(new Trip(rs.getInt(1), rs.getString(2), rs.getString(3), ""+rs.getTimestamp(4), ""+rs.getTimestamp(5), rs.getString(6), ""+rs.getInt(7), ""+rs.getInt(8)));
                while(rs.next()) {
                    tripData.add(new Trip(rs.getInt(1), rs.getString(2), rs.getString(3), ""+rs.getTimestamp(4), ""+rs.getTimestamp(5), rs.getString(6), ""+rs.getInt(7), ""+rs.getInt(8)));
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        trips.setItems(tripData);
    }
} 
Slamdunk
  • 424
  • 1
  • 8
  • 20
  • 1
    Don't use wildcards for the `TableColumn` types. For the columns for `trips`, you should have `TableColumn`, replacing `XXX` for each column with the actual type of data in that column. – James_D Apr 09 '18 at 13:33
  • 1
    A better UI design would be placing a “Choose Driver” button outside of the table, and enabling it whenever exactly one row is selected in the table. This would also remove the need for a CellFactory in the Chaufför column. – VGR Apr 09 '18 at 14:16
  • @VGR You are right. Im going to change the design so that when I double click on a row it will give me a new stage where I can not only choose a driver but also change and remove a driver. – Slamdunk Apr 09 '18 at 18:28

3 Answers3

1

Edited based on comments (thanks, @fabian).

This assumes that Trip has a method Trip.getDriver() which returns the driver as a String to display.

Customise the CellFactory for the driverColumn:

//give driverColumn proper types
TableColumn<Trip,String> driverColumn;
//...       

driverColumn.setCellValueFactory(new Callback<CellDataFeatures<Trip, String>, ObservableValue<String>>() {
   public ObservableValue<String> call(CellDataFeatures<Trip, String> p) {
        // p.getValue() returns the Trip instance for a particular TableView row
        return new SimpleStringProperty(p.getValue().getDriver());
    }
});
driverColumn.setCellFactory(param -> new TableCell<Trip,String>{
    public void updateItem(String driver, boolean empty) {
        super.updateItem(driver, empty);

        //reset any previous values, as cells may be reused
        setText(null);
        setGraphic(null);

        if(empty) {
            //contingency if cell is empty
        } else if(driver!=null) {
            setText(driver);
        } else {
            //driver is null
            Button b = new Button("Choose Driver");
            //... hook up button actions, etc
            setGraphic(b);
        }
    }
});

There are likely to be other options than to re-create the button each time the cell is updated, but I hope you get the general idea.

You can use the same technique to change the way items are displayed (such as adding styling depending on value), adding controls like checkboxes, etc.

As a separate note, I suggest that you may want to consider moving your database connection code out of the Platform (JavaFX) Thread, for example by using the Service interface.

Thomas Timbul
  • 1,634
  • 6
  • 14
  • 1
    This implementation will not work singe the second parameter of `TableCell` denotes the item type, not the first. The `updateItem` you need to override takes `String` as first parameter instead of `Trip`. – fabian Apr 09 '18 at 13:31
  • Ah, you are right of course. I've been working with too many `ListCell`s where you just set the one value (Trip). – Thomas Timbul Apr 09 '18 at 13:53
0

Add a CellFactory for the driver column, and in that created cell create different content for the data:

 driverColumn.setCellFactory(this::createDriverCell);

 private TableCell<Trip,String> createDriverCell(TableColumn<Trip, String> table) {
    return new TableCell<Trip, String>() {
        @Override
        protected void updateItem(String item, boolean empty) {
            super.updateItem(item, empty);

            if (empty) {
              // configure empty
            } else if (item == null) {
              // create no driver content
            } else {
              // create different content
            }
        }           
    };
}
M. le Rutte
  • 3,525
  • 3
  • 18
  • 31
0

Use the cellFactory to create cells that display different content depending on the cell item. The following code assumes your cellValueFactory returns a ObservableValue that also implements WritableValue and allows you to store the data in a item this way.

For simplicity the following example uses a table of StringPropertys (you didn't post the Trip class):

@Override
public void start(Stage primaryStage) {
    TableView<StringProperty> table = new TableView<>();

    // fill table
    for (int i = 0; i < 20; i++) {
        table.getItems().add(new SimpleStringProperty());
    }

    TableColumn<StringProperty, String> column = new TableColumn<>();
    column.setCellValueFactory(TableColumn.CellDataFeatures::getValue); // returns the row item itself
    column.setCellFactory(col -> new TableCell<StringProperty, String>() {

        private final Button button;

        {
            button = new Button("Set Value...");
            button.setOnAction(evt -> {
                TextInputDialog dialog = new TextInputDialog();
                Optional<String> opt = dialog.showAndWait();
                String result = opt.orElse(null);
                if (result != null) {
                    ObservableValue<String> ov = getTableColumn().getCellObservableValue(getIndex());
                    if (ov instanceof WritableValue) {
                        ((WritableValue) ov).setValue(result);
                    }
                }
            });
        }

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

            if (empty) {
                // empty cell
                setText("");
                setGraphic(null);
            } else {
                if (item == null) {
                    // editable cell
                    setText("");
                    setGraphic(button);
                } else {
                    // uneditable cell
                    setText(item);
                    setGraphic(null);
                }
            }
        }

    });

    table.getColumns().add(column);

    Scene scene = new Scene(table);

    primaryStage.setScene(scene);
    primaryStage.show();
}
fabian
  • 80,457
  • 12
  • 86
  • 114