0

I'm starting to learn JavaFX and I want to populate my tableview with data from my database. I've learned that I could use map to build data but it didn't work. The tableview just shows the columns' name but no rows.

Here is my code:

Main.java

package application;

import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.fxml.FXMLLoader;


public class Main extends Application {
    @Override
    public void start(Stage primaryStage) {
        try {
            AnchorPane root = (AnchorPane)FXMLLoader.load(getClass().getResource("MainWindow.fxml"));
            Scene scene = new Scene(root);
            scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
            primaryStage.setScene(scene);
            primaryStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

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

MainWindowControl.java

package application;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.MapValueFactory;

public class MainWindowController {

    @FXML
    private TableView<Map<Integer, String>> tableView = new TableView<>(generateDataInMap());

    final static Integer first = 1;

    public void openDatabase() throws SQLException {
        Connection connection = DriverManager.getConnection("jdbc:sqlite:test.db");
        Statement statement = connection.createStatement();
        ResultSet rSet = statement.executeQuery("select * from myTable");

        int col = rSet.getMetaData().getColumnCount();

        for (int i = 1; i <= col; i++) {
            rSet = statement.executeQuery("select * from myTable");
            Integer key = 1;
            String name = rSet.getMetaData().getColumnName(i);
            TableColumn<Map<Integer, String>, String> tableColumn = new TableColumn<>(name);
            tableColumn.setCellValueFactory(new MapValueFactory(key));
            tableView.getColumns().add(tableColumn);
        }
    }

    private ObservableList<Map<Integer, String>> generateDataInMap(){
        Connection connection = null;
        ObservableList<Map<Integer, String>> alldata = FXCollections.observableArrayList();
        try {
            connection = DriverManager.getConnection("jdbc:sqlite:test.db");
            Statement statement = connection.createStatement();
            ResultSet rSet = statement.executeQuery("select * from myTable");

            int col = rSet.getMetaData().getColumnCount();
            while(rSet.next()) {
                Map<Integer, String> dataRow = new HashMap<>();
                for (int i = 1; i <= col; i++) {
                    String value = rSet.getString(i);
                    dataRow.put(i, value);
                }
                alldata.add(dataRow);
            }

            connection.close();
            return alldata;
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }
}

MainWindow.fxml

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

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

<AnchorPane prefHeight="255.0" prefWidth="427.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.MainWindowController">
   <children>
      <TableView fx:id="tableView" layoutX="34.0" layoutY="41.0" prefHeight="200.0" prefWidth="359.0" />
      <Button layoutX="186.0" layoutY="14.0" mnemonicParsing="false" onAction="#openDatabase" text="Load Data" />
   </children>
</AnchorPane>

Do you know where the bugs in my code are? I'm trying to simulate the function of SQLiteStudio that when I choose a database, the data will populate the tableview.

fabian
  • 80,457
  • 12
  • 86
  • 114
方致远
  • 5
  • 3

1 Answers1

0

You're creating a TableView in the initializer of the controller class. This TableView is filled with data but the instance will never be displayed in a scene since FXMLLoader creates the one from the fxml.

Do initialisations like this in the initialize() method. This method is run by FXMLLoader after all objects have been injected making the tableView instance shown on screen accessible.

Additional notes:

  • You pass key which is always 1 to the value factory. Instead you should use i.
  • You requery the db for every column always fetching every single row in the table. This is very inefficient. Just execute the query a single time. Consider using the result for both initializing rows and columns.
  • Consider seperating data access and view. You could do this by passing data to the controller, see Passing Parameters JavaFX FXML
@FXML
private TableView<Map<Integer, String>> tableView;

@FXML
private void initialize() throws Exception {
    try (Connection connection = DriverManager.getConnection("jdbc:sqlite:test.db");
         Statement statement = connection.createStatement()) {
         ResultSet rSet = statement.executeQuery("select * from myTable");
         openDatabase(rSet);
         tableView.setItems(generateDataInMap(rSet));
    }
    // TODO: Exception handling???
}

private void openDatabase(ResultSet rSet) throws SQLException {
    final ResultSetMetaData metadata = rSet.getMetaData();
    final int col = metadata.getColumnCount();

    for (int i = 1; i <= col; i++) {
        String name = metadata.getColumnName(i);
        TableColumn<Map<Integer, String>, String> tableColumn = new TableColumn<>(name);
        tableColumn.setCellValueFactory(new MapValueFactory(i));
        tableView.getColumns().add(tableColumn);
    }
}

private ObservableList<Map<Integer, String>> generateDataInMap(ResultSet rSet) {
    ObservableList<Map<Integer, String>> alldata = FXCollections.observableArrayList();
    final int col = rSet.getMetaData().getColumnCount();
    while(rSet.next()) {
        Map<Integer, String> dataRow = new HashMap<>();
        for (int i = 1; i <= col; i++) {
            String value = rSet.getString(i);
            dataRow.put(i, value);
        }
        alldata.add(dataRow);
    }

    return alldata;
}

Note that the above modified code version requires you to remove the onAction event handler but since adding the rows but not initializing the columns makes little sense I moved the call to the initialize method and modified the signature in a way that does not allow you to use it as a EventHandler anymore.

Community
  • 1
  • 1
fabian
  • 80,457
  • 12
  • 86
  • 114
  • Thank you very much. Your advice exactly solved my problem. I have another question that the eclipse gives me a warning in sentence " tableColumn.setCellValueFactory(new MapValueFactory(i));". Could you tell me how to solve it? – 方致远 Nov 18 '17 at 14:27
  • @方致远 Probably a warning because of the raw type used. Does specifying the type parameter fix this `tableColumn.setCellValueFactory(new MapValueFactory(i));`? – fabian Nov 18 '17 at 15:11
  • I did this yesterday before I ask you for help. The eclipse gave me an error that `The method setCellValueFactory(Callback,String>,ObservableValue>) in the type TableColumn,String> is not applicable for the arguments (MapValueFactory)` I have read the sorce code but I still don't know what should I do. – 方致远 Nov 19 '17 at 05:22
  • You're right. `MapValueFactory` (unfortunately) requires a raw `Map`, so one way or another you'll have to use a raw type. I'd just ignore the warning in this case. Just be sure never to put anything other than `String`s in the map. Otherwise this may yield `ClassCastException`s when other `TableColumn` properties are used with a custom class... – fabian Nov 19 '17 at 10:06
  • Thank for your reply. I think I can finish my programme with your suggestions. – 方致远 Nov 19 '17 at 12:56