0

I'm trying to retrieve an XLS file and load it into a TableView when a button is clicked in JavaFX. I'm using Task class and ExecutorService to launch new threads. I need the reader class to be reusable, but the FileChooser isn't showing up.

This is my attempt at writing some concurrent code. I'd like to know what am I doing wrong and how would I improve my code, since everything is event-driven?

Controller class code

public void retrieveCustomersFromXLS() {
        try {
            loader.setOnSucceeded(workerStateEvent -> {
                File file = null;
                try {
                    file = loader.get();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                if (file != null && file.exists()) {
                    reader.setWorkingFile(file);
                    executor.submit(reader);
                }
            });
            reader.setOnSucceeded(workerStateEvent1 -> {
                Object[][] XLSFile = new Object[0][];
                try {
                    XLSFile = reader.get();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                if (XLSFile != null) {
                    tableInterface.setEntries(XLSFile);
                    tableInterface.setEntryType("customers");
                    executor.submit(tableInterface);
                }
            });
            tableInterface.setOnSucceeded(workerStateEvent2 -> {
                customerList = FXCollections.observableArrayList(tableInterface.getCustomerEntries());
                column_customerReference.setCellValueFactory(new PropertyValueFactory<customers, Integer>("customerReference"));
                column_customerName.setCellValueFactory(new PropertyValueFactory<customers, String>("customerName"));
                column_customerAddress.setCellValueFactory(new PropertyValueFactory<customers, String>("customerAddress"));
                column_customerPost.setCellValueFactory(new PropertyValueFactory<customers, Integer>("customerPost"));
                column_customerRegion.setCellValueFactory(new PropertyValueFactory<customers, String>("customerRegion"));
                column_customerID_DDV.setCellValueFactory(new PropertyValueFactory<customers, String>("customerDDV"));
                table_customerImports.setItems(customerList);
            });
            executor.submit(loader);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

Reader class file

@Override
    protected File call() throws Exception {
        FileChooser fileChooser = new FileChooser();
        fileChooser.setTitle("Choose Excel file");
        fileChooser.getExtensionFilters().addAll(new FileChooser.ExtensionFilter("Excel", "*.xlsx", "*.xls", "*.csv"));
        File selectedFile = fileChooser.showOpenDialog(new Stage());
        if (selectedFile != null) {
            path = selectedFile.getAbsolutePath();
            file = new File(path);
        }
        return file;
    }
xblaz3kx
  • 341
  • 1
  • 3
  • 15
  • Just my advice, if you don't know what you are doing (and you don't), then don't write concurrent code. Instead, just write for one thread without any concurrency or executor service or task or anything, and see if it performs acceptably. Only if it does not perform, then write concurrent code, but base it off of the working implementation you already have. If you have don't know how to do that, then paste a *complete* [mcve], of the non-concurrent code (without UI tables as they don't matter for concurrency) and ask a question on how you make it concurrent. – jewelsea Oct 09 '19 at 20:51
  • I had a working non-concurrent example before I made any UI or background processing changes and It was very unreadable and blocked the UI thread. I'm well aware I have no idea what I am doing but I want to learn how to properly make a concurrent program. Basically what I need to know is how to chain tasks. – xblaz3kx Oct 09 '19 at 21:00
  • 1
    "Basically what I need to know is how to chain tasks". You can use a [single thread executor](https://docs.oracle.com/en/java/javase/13/docs/api/java.base/java/util/concurrent/Executors.html#newSingleThreadExecutor()). See the answer to [How to reset progress indicator between tasks in JavaFX2?](https://stackoverflow.com/questions/16368793/how-to-reset-progress-indicator-between-tasks-in-javafx2/16372123#16372123). For a more concise example, see: [How to queue tasks in JavaFX?](https://stackoverflow.com/questions/26611042/how-to-queue-tasks-in-javafx) – jewelsea Oct 09 '19 at 21:37

1 Answers1

2

You must call the FileChooser#showXXXDialog methods on the JavaFX Application Thread. If you observed your task for failure you'd be seeing an IllegalStateException with a message stating you tried to perform an operation on the wrong thread. Also, you don't need a background task to prompt the user for a file in the first place.

Here's an example of prompting the user for a text file, reading the text file in a Task, and putting the result in a ListView.

App.java

package com.example;

import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class App extends Application {

  @Override
  public void start(Stage primaryStage) throws IOException {
    Scene scene = new Scene(FXMLLoader.load(getClass().getResource("/App.fxml")));
    primaryStage.setScene(scene);
    primaryStage.setTitle("FileChooser Example");
    primaryStage.show();
  }

}

App.fxml

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

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.layout.VBox?>

<VBox xmlns="http://javafx.com/javafx/13" xmlns:fx="http://javafx.com/fxml/1" spacing="5" prefWidth="600"
      prefHeight="400" alignment="CENTER" fx:controller="com.example.Controller">
    <padding>
        <Insets topRightBottomLeft="5"/>
    </padding>
    <Button text="Open File..." onAction="#handleOpenFile"/>
    <Separator/>
    <ListView fx:id="listView" VBox.vgrow="ALWAYS"/>
</VBox>

Controller.java

package com.example;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import javafx.collections.FXCollections;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.ListView;
import javafx.stage.FileChooser;
import javafx.stage.FileChooser.ExtensionFilter;

public class Controller {

  private final Executor executor = Executors.newSingleThreadExecutor(r -> {
    Thread t = new Thread(r, "controller-thread");
    t.setDaemon(true);
    return t;
  });

  @FXML private ListView<String> listView;

  @FXML
  private void handleOpenFile(ActionEvent event) {
    event.consume();

    FileChooser chooser = new FileChooser();
    chooser.getExtensionFilters()
        .add(new ExtensionFilter("Text Files", "*.txt", "*.json", "*.xml", "*.html", "*.java"));

    File file = chooser.showOpenDialog(listView.getScene().getWindow());
    if (file != null) {
      ReadFileTask task = new ReadFileTask(file.toPath());
      task.setOnSucceeded(wse -> listView.setItems(FXCollections.observableList(task.getValue())));
      task.setOnFailed(wse -> task.getException().printStackTrace());
      executor.execute(task);
    }
  }

  private static class ReadFileTask extends Task<List<String>> {

    private final Path file;

    private ReadFileTask(Path file) {
      this.file = Objects.requireNonNull(file);
    }

    @Override
    protected List<String> call() throws Exception {
      return Files.readAllLines(file);
    }

  }

}
Slaw
  • 37,820
  • 8
  • 53
  • 80