javajavafxtableview

JavaFX TableView populating columns with same value 3 times


I have this bit of code for setting up a TableView

    @Override
    public void initialize(URL location, ResourceBundle resources){
        if(App.Customers.get(App.currentcustomerindex).getAccounts().get(App.currentaccountindex).getAccounttype().equalsIgnoreCase("tmb")||App.Customers.get(App.currentcustomerindex).getAccounts().get(App.currentaccountindex).getAccounttype().equalsIgnoreCase("gold")){
            Checking workchecking = (Checking) App.Customers.get(App.currentcustomerindex).getAccounts().get(App.currentaccountindex);
            String Balance = Double.toString(workchecking.getBalance());
            String Dateopened = workchecking.getDateopened().toString();
            String Savingsid = workchecking.getSavingsaccountid();
            ObservableList<String> list = FXCollections.observableArrayList();
            list.addAll(Balance,Dateopened,Savingsid);
            tablebox.setItems(list);
            TableColumn<String,String> column1 = new TableColumn<String,String>("Balance");
            column1.setCellValueFactory(cellData -> new SimpleStringProperty(Balance));
            TableColumn<String,String> column2= new TableColumn<>("Date Opened");
            column2.setCellValueFactory(cellData -> new SimpleStringProperty(Dateopened));
            TableColumn<String,String> column3 = new TableColumn<>("Savings ID");
            column3.setCellValueFactory(cellData -> new SimpleStringProperty(Savingsid));
            tablebox.getColumns().setAll(column1,column2,column3);
        }
    }

Which produces the following TableView: enter image description here

So any idea what I am doing wrong here? I have also tried cellData.getValue() in place of Balance, DateOpened and SavingsID in the SimpleStringProperty with the same results.I would like the data to only display in the first row..


Solution

  • The first thing you need to fix is your use of a TableView<String>. Each row of your table represents a Checking instance, so you should create a TableView<Checking>. The items in that table should be Checking objects, not strings.

    Collection<Checking> workchecking =
        App.Customers.get(App.currentcustomerindex).getAccounts();
    
    tablebox.getItems().setAll(workchecking);
    

    You have multiple Checking records, so reading the balance, date, and ID at this time is meaningless. Don’t read any of them when creating the table. Instead, read those values only when the TableColumn asks for them, which it will do by calling your cell value factories.

    The whole point of a cell value factory is that it retrieves the values of the record at a requested row. Which row, you might ask? The row specified in the CellDataFeatures which is passed to the cell value factory.

    So, you can just write:

    TableColumn<Checking, String> balanceColumn = new TableColumn<>("Balance");
    balanceColumn.setCellValueFactory(
        cellData -> new SimpleStringProperty(
            String.valueOf(cellData.getValue().getBalance())));
    
    TableColumn<Checking, String> dateColumn = new TableColumn<>("Date Opened");
    column2.setCellValueFactory(
        cellData -> new SimpleStringProperty(
            String.valueOf(cellData.getValue().getDateopened())));
    
    TableColumn<Checking, String> idColumn = new TableColumn<>("Savings ID");
    column3.setCellValueFactory(
        cellData -> new SimpleStringProperty(
            cellData.getValue().getSavingsaccountid()));
    
    tablebox.getColumns().add(balanceColumn);
    tablebox.getColumns().add(dateColumn);
    tablebox.getColumns().add(idColumn);
    

    In each of the above cell value factories, cellData.getValue() provides the Checking object at the row whose values the TableView is about to display. That is the entire point of the CellDataFeatures object.

    trashgod has already linked to an explanation of why you should never use PropertyValueFactory. To summarize: it’s easy to make mistakes with PropertyValueFactory, and neither the compiler nor PropertyValueFactory will tell you when you’ve made such a mistake. Instead, write your own cell value factories, like above; the compiler can check them for correctness.

    Formatting

    To go a step farther: you should not turn all of your data into strings. A TableView lets a user sort by columns, but because your values are strings, it would sort 155.28 before a value like 19.10. This happens because they are being ordered by their characters, not their numeric values.

    You can solve this by making your cell value factory return raw data, while having the cell factory (not the cell value factory) format the data for display. This works because a TableView sorts based on raw data (obtained from cell value factories), not on how the data is displayed.

    To do this, assign the actual data types to each column:

    TableColumn<Checking, Double> balanceColumn = new TableColumn<>("Balance");
    balanceColumn.setCellValueFactory(
        cellData -> new SimpleDoubleProperty(
            cellData.getValue().getBalance()));
    
    TableColumn<Checking, LocalDate> dateColumn = new TableColumn<>("Date Opened");
    column2.setCellValueFactory(
        cellData -> new SimpleObjectProperty<LocalDate>(
            cellData.getValue().getDateopened()));
    
    TableColumn<Checking, String> idColumn = new TableColumn<>("Savings ID");
    column3.setCellValueFactory(
        cellData -> new SimpleStringProperty(
            cellData.getValue().getSavingsaccountid()));
    
    tablebox.getColumns().add(balanceColumn);
    tablebox.getColumns().add(dateColumn);
    tablebox.getColumns().add(idColumn);
    

    Notice the use of TableColumn<Checking, Double> and TableColumn<Checking, LocalDate>, to indicate the type of data displayed in each of those columns. Also notice the use of SimpleDoubleProperty and SimpleObjectProperty, instead of SimpleStringProperty.

    To get them to display in a certain format, create a single formatting object and use it in a cell factory (which is not the same as a cell value factory):

    NumberFormat balanceFormat = NumberFormat.getCurrencyInstance();
    
    balanceColumn.setCellFactory(c -> new TableCell<Checking, Double>() {
        @Override
        protected void updateItem(Double balance,
                                  boolean empty) {
    
            super.updateItem(balance, empty);
    
            setGraphic(null);
            if (empty || item == null) {
                setText(null);
            } else {
                setText(balanceFormat.format(balance));
            }
        }
    });
    

    It is vital that a cell factory both calls super.updateItem and properly handles the empty and null cases properly. The documentation of Cell.updateItem explains this in detail.

    You seem to be content with your dates displaying in their default (ISO 8601) format. If you wanted them to be localized, you could do the same for dates as the above code does for balance values:

    DateTimeFormatter dateFormatter =
        DateTimeformatter.ofLocalizedDate(FormatStyle.MEDIUM);
    
    dateColumn.setCellFactory(c -> new TableCell<Checking, LocalDate>() {
        @Override
        protected void updateItem(LocalDate date,
                                  boolean empty) {
    
            super.updateItem(date, empty);
    
            setGraphic(null);
            if (empty || item == null) {
                setText(null);
            } else {
                setText(dateFormatter.format(date));
            }
        }
    });
    

    Again, the benefit of doing this is that the TableView will sort dates in chronological order, not alphabetical order.