La meilleure façon de mettre en œuvre cette
Au lieu d'essayer de conduire le cheval avec le chariot, pourquoi ne pas simplement suivre le cycle de vie standard JavaFX? En d'autres termes, faites votre classe Main
une sous-classe de Application
, obtenez le fichier dans la méthode start()
, puis passez (dans un fil d'arrière-plan) avec le reste de l'application?une fois que vous avez décidé:
public class Main extends Application {
@Override
public void init() {
// make sure we don't exit when file chooser is closed...
Platform.setImplicitExit(false);
}
@Override
public void start(Stage primaryStage) {
File file = null ;
FileChooser fc = new FileChooser();
while(file == null){
file = fc.showOpenDialog(primaryStage);
}
final File theFile = file ;
new Thread(() -> runApplication(theFile)).start();
}
private void runApplication(File file) {
// run your application here...
}
}
Quel est le problème avec votre code
Si vous voulez vraiment la classe Main
être séparée de la classe JavaFX Application
(qui ne fait pas vraiment de sens pour utiliser un JavaFX FileChooser
, vous avez décidé d'écrire une application JavaFX, donc la classe de démarrage devrait être une sous-classe de Application
), alors ça devient un peu compliqué. Il y a plusieurs problèmes avec votre code tel qu'il est, dont certains sont abordés dans d'autres réponses. Le problème principal, comme indiqué dans la réponse de Fabian, est que vous faites référence à FileGetter.isReady
à partir de plusieurs threads sans assurer la vivacité. C'est exactement le problème abordé dans le Effective Java de Josh Bloch (article 66 de la 2e édition).
Un autre problème avec votre code est que vous ne serez pas en mesure d'utiliser le FileGetter
plus d'une fois (vous ne pouvez pas appeler launch()
plus d'une fois), ce qui peut ne pas être un problème dans votre code maintenant, mais presque certainement sera à un moment donné avec cette application que le développement progresse. Le problème est que vous avez mélangé deux problèmes: démarrage de la boîte à outils FX et récupération d'un fichier à partir d'un FileChooser
. La première chose ne doit être faite qu'une seule fois; la seconde devrait être écrite pour être réutilisable.
Et enfin votre boucle
while(isReady == false){
isReady = FileGetter.getIsReady();
}
est très mauvaise pratique: il vérifie le drapeau isReady
aussi vite qu'il le peut. Dans certaines circonstances (assez inhabituelles), il pourrait même empêcher le thread d'application FX d'avoir des ressources à exécuter. Cela devrait juste bloquer jusqu'à ce que le fichier soit prêt.
Comment fixer sans Main
un JavaFX Application
Alors, encore une fois que si vous avez vraiment besoin pressant de le faire, je voudrais d'abord créer une classe qui a seulement la responsabilité de départ la boîte à outils FX. Quelque chose comme:
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.stage.Stage;
public class FXStarter extends Application {
private static final AtomicBoolean startRequested = new AtomicBoolean(false);
private static final CountDownLatch latch = new CountDownLatch(1);
@Override
public void init() {
Platform.setImplicitExit(false);
}
@Override
public void start(Stage primaryStage) {
latch.countDown();
}
/** Starts the FX toolkit, if not already started via this method,
** and blocks execution until it is running.
**/
public static void startFXIfNeeded() throws InterruptedException {
if (! startRequested.getAndSet(true)) {
new Thread(Application::launch).start();
}
latch.await();
}
}
Créez maintenant une classe qui obtient un fichier pour vous. Cela devrait assurer l'exécution de la boîte à outils FX, en utilisant la classe précédente. Cette implémentation vous permet d'appeler getFile()
de tout fil:
import java.io.File;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import javafx.application.Platform;
import javafx.stage.FileChooser;
public class FileGetter {
/**
** Retrieves a file from a JavaFX File chooser. This method can
** be called from any thread, and will block until the user chooses
** a file.
**/
public File getFile() throws InterruptedException {
FXStarter.startFXIfNeeded() ;
if (Platform.isFxApplicationThread()) {
return doGetFile();
} else {
FutureTask<File> task = new FutureTask<File>(this::doGetFile);
Platform.runLater(task);
try {
return task.get();
} catch (ExecutionException exc) {
throw new RuntimeException(exc);
}
}
}
private File doGetFile() {
File file = null ;
FileChooser chooser = new FileChooser() ;
while (file == null) {
file = chooser.showOpenDialog(null) ;
}
return file ;
}
}
et enfin votre Main
est juste
import java.io.File;
public class Main {
public static void main(String[] args) throws InterruptedException {
File file = new FileGetter().getFile();
// proceed...
}
}
Encore une fois, cela est assez complexe; Je ne vois aucune raison de ne pas simplement utiliser le cycle de vie standard de l'application FX pour cela, comme dans le tout premier bloc de code dans la réponse.
Faites votre 'isReady' dans FileGetter' volatile'. – Codebender
@Codebender Cela a fonctionné! Merci! Juste curieux comme une note de côté, pour une raison quelconque cela a fonctionné quand j'ai inclus un System.out.println (isReady); dans la boucle while. Quelqu'un connaît la raison de cela? –
La boucle while de FileGetter # start permet de s'assurer que l'utilisateur choisit un fichier, tandis que la boucle while de la classe principale permet de s'assurer que le code ne se poursuit pas avant qu'un fichier ne soit choisi. Mais oui, je pourrais utiliser la même déclaration conditionnelle dans les deux, mais je crois que c'est le même concept. –