Tom
Tom

Reputation: 343

How do I create a generic TableView in JavaFx?

I am building a GUI for my project and I need to show information that is coming from various types of data structures: ArrayList,HashMap,TreeSet in a table.

Basically I need to show information from query methods I got in my model package in my view package.

My implementation right now includes a controller for each and every table I build with the object type specified every time I declare a TableView.

I wonder if there is a way to create a class that its constructor will build me the table I need(with as many columns this object require). some sort of a generic table builder that can read an object and find out how many columns it needs to be represented. for example: to present Reservation Object that has 4 fields/columns compare to Member object that has 6 fields/columns. according to the data-structure it will get. therefore I will be able to create an instance of this class and instantiate a table of my need and show it on the screen.

I also need a way to be able to control it from the scene builder as this is my main tool for building the GUI graphically.

I am adding one of my table codes here and I hope someone can help me with this tedious task :)

    package view;

import java.util.ArrayList;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.stage.Stage;

import model.Reservation;



public class QRYClientReservationsTableController {

    private ObservableList<Reservation> ReservationsData = FXCollections.observableArrayList();


    @FXML
    private TableView<Reservation> ReservationforClient;
    @FXML
    private TableColumn<Reservation, String> ReservationIdColumn;
    @FXML
    private TableColumn<Reservation, String> LocationIdColumn;
    @FXML
    private TableColumn<Reservation, String> AddressColumn;
    @FXML
    private TableColumn<Reservation, String> DescriptionColumn;
    @FXML
    private TableColumn<Reservation, String> StartTimeAndDateColumn;
    @FXML
    private TableColumn<Reservation, String> EndTimeAndDateColumn;
    @FXML
    private TextField memId;


    /**
     * The constructor.
     * The constructor is called before the initialize() method.
     */
    public QRYClientReservationsTableController() {

    }

    /**
     * Initializes the controller class. This method is automatically called
     * after the fxml file has been loaded.
     */
    @FXML
    private void initialize() {
        // Initialize the Location table with the tree columns.
        ReservationIdColumn.setCellValueFactory(cellData -> cellData.getValue().ReservationIdProperty());
        LocationIdColumn.setCellValueFactory(cellData -> cellData.getValue().LocationIdProperty());
        AddressColumn.setCellValueFactory(cellData -> cellData.getValue().LocationAddressProperty());
        DescriptionColumn.setCellValueFactory(cellData -> cellData.getValue().LocationDescriptionProperty());
        StartTimeAndDateColumn.setCellValueFactory(cellData -> cellData.getValue().StartDateProperty());
        EndTimeAndDateColumn.setCellValueFactory(cellData -> cellData.getValue().EndDateProperty());
    }
    @FXML
    Button CloseBtn = new Button();//close button inside AddLocation window
    @FXML
    public void handleCloseButtonAction(ActionEvent event) {//for all close Button's this method is called
        Stage stage = (Stage) CloseBtn.getScene().getWindow();
        stage.close();
    }
    /**
     * Is called by the main application to give a reference back to itself.
     *
     * @param Locaion
     */
    public void setQRYClientReservationsTableController(String memId) {
        this.memId.setEditable(true);
        this.memId.setText(memId);
        this.memId.setEditable(false);
        ArrayList<Reservation> reservationsAdd = new ArrayList<Reservation>();
        reservationsAdd.addAll(ViewLogic.controlLogic.Q_getAllReservationsForMember(memId));
        ReservationsData.addAll(reservationsAdd);
        // Add observable list data to the table
        ReservationforClient.setItems(ReservationsData);
        ReservationIdColumn.sortableProperty().setValue(false);
        LocationIdColumn.sortableProperty().setValue(false);
        AddressColumn.sortableProperty().setValue(false);
        DescriptionColumn.sortableProperty().setValue(false);
        StartTimeAndDateColumn.sortableProperty().setValue(false);
        EndTimeAndDateColumn.sortableProperty().setValue(false);
    }
}

The next code is in my ViewLogic Class inside my View package and this code summons the FXML file that loads up a small window to choose a member Id, by this Id I am getting the info to build the table on the code above. the code for this method in ViewLogic is as follows:

@FXML
Button ShowReservationsForClientBtn= new Button();/*Code for pressing the get reservations for client button to open window after pressing the button in Queries*/
@FXML
public void pressShowReservationsForClientBtn(ActionEvent event) throws Exception {
    try {
        FXMLLoader fxmlLoader = new FXMLLoader(ChooseAClientForReservationsQueryWindowController.class.getResource("/view/ChooseAClientForReservationsQueryWindow.fxml"));
        AnchorPane chooseMemberIdForQuery = (AnchorPane) fxmlLoader.load();
        ChooseAClientForReservationsQueryWindowController controller = fxmlLoader.getController();
        controller.setAddTripToReservationClass();
        Stage stage = new Stage();
        stage.setTitle("Please Choose a Member Id");
        stage.setScene(new Scene(chooseMemberIdForQuery));
        stage.show();
    } catch(Exception e) {
        e.printStackTrace();
    }
}

It then calls this class where you choose your member and then the table code above is initiated, the mini window code is:

package view;

    import java.util.ArrayList;
    import javafx.beans.value.ChangeListener;
    import javafx.beans.value.ObservableValue;
    import javafx.event.ActionEvent;
    import javafx.fxml.FXML;
    import javafx.fxml.FXMLLoader;
    import javafx.scene.Scene;
    import javafx.scene.control.Alert;
    import javafx.scene.control.Button;
    import javafx.scene.control.ButtonType;
    import javafx.scene.control.ComboBox;
    import javafx.scene.control.Alert.AlertType;
    import javafx.scene.layout.AnchorPane;
    import javafx.stage.Stage;



    public class ChooseAClientForReservationsQueryWindowController {
        private String memIdChosen;
        @FXML
        private ComboBox<String> MemberIds;
        public void initialize() {
        }
        @FXML
        Button CloseBtn = new Button();//close button inside AddLocation window
        @FXML
        public void handleCloseButtonAction(ActionEvent event) {//for all close Button's this method is called
            Stage stage = (Stage) CloseBtn.getScene().getWindow();
            stage.close();
        }
        @FXML
        Button EraseBtn = new Button();//erase Button
        @FXML
        public void handleEraseButtonAction(ActionEvent event) {//erase Button code
            MemberIds.setValue(null);
        }
        @FXML
        Button GoBtn = new Button();//Go button
        @FXML
        public void handleGoBtn(ActionEvent event) throws Exception {//for all close Button's this method is called
            try{
                if(MemberIds.getValue()==null)
                    throw new Exception();
                FXMLLoader fxmlLoader = new FXMLLoader(QRYClientReservationsTableController.class.getResource("/view/QRYClientReservationsTableController.fxml"));
                AnchorPane qryShowAllReservationsForMember = (AnchorPane) fxmlLoader.load();
                QRYClientReservationsTableController controller = fxmlLoader.getController();
                controller.setQRYClientReservationsTableController(memIdChosen);
                Stage stage = new Stage();
                stage.setTitle("Query - Show all reservations for member Id: "+memIdChosen+".");
                stage.setScene(new Scene(qryShowAllReservationsForMember));
                stage.show();
                handleCloseButtonAction(event);
            }
            catch (Exception e){
                Alert alert = new Alert(AlertType.WARNING,"One of the fields is empty", ButtonType.OK);
                alert.setTitle("One of the fields is empty");
                alert.setHeaderText("One of the fields is empty");
                alert.setContentText("Please fill the empty fields");
                alert.showAndWait();
            }
        }
        /**
         * The constructor.
         * The constructor is called before the initialize() method.
         */
        public ChooseAClientForReservationsQueryWindowController() {

        }

        public void setAddTripToReservationClass(){
            ArrayList<String> memIds = new ArrayList<String>();
            memIds.addAll(ViewLogic.controlLogic.getMembers().keySet());
            MemberIds.getItems().setAll(memIds);
            MemberIds.valueProperty().addListener(new ChangeListener<String>(){

                @Override
                public void changed(ObservableValue<? extends String> arg0, String arg1, String arg2) {
                    memIdChosen=arg2;

                }
            });
        }
    }

That's the relevant FXML file for the table code above if it helps:

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

<?import javafx.scene.text.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="1450.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="view.QRYClientReservationsTableController">
   <children>
      <ButtonBar layoutX="44.0" layoutY="541.0" prefHeight="40.0" prefWidth="700.0" AnchorPane.bottomAnchor="19.0">
        <buttons>
            <Button fx:id="CloseBtn" mnemonicParsing="false" onAction="#handleCloseButtonAction" prefHeight="31.0" prefWidth="212.0" text="Close" />
        </buttons>
      </ButtonBar>
      <TableView fx:id="ReservationforClient" layoutX="5.0" layoutY="55.0" prefHeight="486.0" prefWidth="893.0" AnchorPane.bottomAnchor="59.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="2.0" AnchorPane.topAnchor="55.0">
        <columns>
          <TableColumn fx:id="ReservationIdColumn" prefWidth="121.0" text="Reservation Id" />
            <TableColumn fx:id="LocationIdColumn" prefWidth="128.0" text="Location Id" />
            <TableColumn fx:id="AddressColumn" prefWidth="276.0" text="Address" />
            <TableColumn fx:id="DescriptionColumn" prefWidth="382.0" text="Description" />
            <TableColumn fx:id="StartTimeAndDateColumn" prefWidth="282.0" text="Start Time and Date" />
            <TableColumn fx:id="EndTimeAndDateColumn" prefWidth="252.0" text="EndTime and Date" />
        </columns>
      </TableView>
      <Label layoutX="394.0" layoutY="8.0" prefHeight="40.0" prefWidth="350.0" text="Reservations for Client Id:" AnchorPane.leftAnchor="394.0" AnchorPane.rightAnchor="706.0" AnchorPane.topAnchor="8.0">
         <font>
            <Font name="System Bold" size="28.0" />
         </font>
      </Label>
      <TextField fx:id="memId" editable="false" layoutX="738.0" layoutY="10.0" prefHeight="40.0" prefWidth="191.0" />
   </children>
</AnchorPane>

Thank you

Tom

Upvotes: 2

Views: 2790

Answers (2)

Arkadii Berezkin
Arkadii Berezkin

Reputation: 268

I stumbled across the similar problem and have created a simple library that solves it.

It uses your model class to build a TableView that represents the fields of the given class.

I've published it on GitHub with a sample and instruction.

If you have any suggestions, I would like to hear from you.

Upvotes: 3

brian
brian

Reputation: 10979

You can do it through reflection. This will make an editable String column for each SimpleStringProperty field. Doing it like this means you can't add them in FXML.

        for (Field f : TableInfo.class.getDeclaredFields()) {
            if (f.getType().equals(SimpleStringProperty.class)){
                TableColumn<TableInfo, String> tc = new TableColumn<>(f.getName());
                tv.getColumns().add(tc);
                tc.setCellValueFactory(new PropertyValueFactory<>(f.getName()));
                tc.setCellFactory(TextFieldTableCell.forTableColumn());
             }//else if more field types...
        }

Upvotes: 0

Related Questions