Why does my JavaFX Preloader occasionally show up gray/black and other times it correctly loads? - javafx

I'm trying to get my JavaFX Preloader splash sccreen to show up before my application. I'm using Eclipse IDE and when I click "run", half the time the splash screen will display correctly and the other half of the time I will get a gray or black screen instead of where the image should be.
I'm not sure what the issue is to cause it to only display correctly sometimes.
SplashController:
public class SplashController extends Preloader {
private static final double WIDTH = 676;
private static final double HEIGHT = 227;
private Stage preloaderStage;
private Label progressText;
private Pane splashScreen;
public SplashController() {}
#Override
public void init() throws Exception {
ImageView splash =
new ImageView(new Image(Demo.class.getResource("pic.png").toString()));
progressText =
new Label("VERSION: " + getVersion() + " ~~~ Loading plugins, please wait...");
splashScreen = new VBox();
splashScreen.getChildren().addAll(splash, progressText);
progressText.setAlignment(Pos.CENTER);
}
#Override
public void start(Stage primaryStage) throws Exception {
this.preloaderStage = primaryStage;
Scene splashScene = new Scene(splashScreen);
this.preloaderStage.initStyle(StageStyle.UNDECORATED);
final Rectangle2D bounds = Screen.getPrimary().getBounds();
this.preloaderStage.setScene(splashScene);
this.preloaderStage.setX(bounds.getMinX() + bounds.getWidth() / 2 - WIDTH / 2);
this.preloaderStage.setY(bounds.getMinY() + bounds.getHeight() / 2 - HEIGHT / 2);
this.preloaderStage.show();
}
}
And then in my main class Demo I simply have:
public class Demo extends Application {
#Override
public void start(Stage stage) throws Exception {
FXMLLoader loader = new
FXMLLoader(Demo.class.getResource("FXMLDocument.fxml"));
GridPane root = loader.load();
--------other app code here---------
}
public static void main(String[] args) {
LauncherImpl.launchApplication(Demo.class, SplashController.class, args);
}
}

Likely, you are executing some long running process on the JavaFX application thread or a thread involved in the application startup, which prevents the smooth operation of the preloader.
I suggest you review an Oracle Preloader sample and compare to your application. Ensure that you are using concurrent features like Task correctly, similar to the linked example. Check the linked sample works in your environment.
Source code (just copied from the Oracle Preloader sample link)
Note how in the start method of the main LongAppInit application class, that a Task and thread is spawned to ensure that the long application initiation does not take place on the JavaFX application thread. Also see how the notifyPreloader() method of application is called at various times within the long application initialization to let the preloader know of the current state of the initialization process so that it can reflect the progress accurately in the UI in real time.
LongAppInitPreloader.java
public class LongAppInitPreloader extends Preloader {
ProgressBar bar;
Stage stage;
boolean noLoadingProgress = true;
private Scene createPreloaderScene() {
bar = new ProgressBar(0);
BorderPane p = new BorderPane();
p.setCenter(bar);
return new Scene(p, 300, 150);
}
public void start(Stage stage) throws Exception {
this.stage = stage;
stage.setScene(createPreloaderScene());
stage.show();
}
#Override
public void handleProgressNotification(ProgressNotification pn) {
//application loading progress is rescaled to be first 50%
//Even if there is nothing to load 0% and 100% events can be
// delivered
if (pn.getProgress() != 1.0 || !noLoadingProgress) {
bar.setProgress(pn.getProgress()/2);
if (pn.getProgress() > 0) {
noLoadingProgress = false;
}
}
}
#Override
public void handleStateChangeNotification(StateChangeNotification evt) {
//ignore, hide after application signals it is ready
}
#Override
public void handleApplicationNotification(PreloaderNotification pn) {
if (pn instanceof ProgressNotification) {
//expect application to send us progress notifications
//with progress ranging from 0 to 1.0
double v = ((ProgressNotification) pn).getProgress();
if (!noLoadingProgress) {
//if we were receiving loading progress notifications
//then progress is already at 50%.
//Rescale application progress to start from 50%
v = 0.5 + v/2;
}
bar.setProgress(v);
} else if (pn instanceof StateChangeNotification) {
//hide after get any state update from application
stage.hide();
}
}
}
LongAppInit.java
public class LongInitApp extends Application {
Stage stage;
BooleanProperty ready = new SimpleBooleanProperty(false);
private void longStart() {
//simulate long init in background
Task task = new Task<Void>() {
#Override
protected Void call() throws Exception {
int max = 10;
for (int i = 1; i <= max; i++) {
Thread.sleep(200);
// Send progress to preloader
notifyPreloader(new ProgressNotification(((double) i)/max));
}
// After init is ready, the app is ready to be shown
// Do this before hiding the preloader stage to prevent the
// app from exiting prematurely
ready.setValue(Boolean.TRUE);
notifyPreloader(new StateChangeNotification(
StateChangeNotification.Type.BEFORE_START));
return null;
}
};
new Thread(task).start();
}
#Override
public void start(final Stage stage) throws Exception {
// Initiate simulated long startup sequence
longStart();
stage.setScene(new Scene(new Label("Application started"),
400, 400));
// After the app is ready, show the stage
ready.addListener(new ChangeListener<Boolean>(){
public void changed(
ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) {
if (Boolean.TRUE.equals(t1)) {
Platform.runLater(new Runnable() {
public void run() {
stage.show();
}
});
}
}
});;
}
}

Related

Execute and wait for multiple parallel and sequential Tasks by using a Arraylist of Tasks in JavaFX

I'm looking for a suitable way to display the processing time of parallel running Tasks on a separate stage.
I want to execute different tasks combined in an ArrayList - one after the other. For this case I'm using a ThreadPool. After each executed list, I want to wait until all tasks are completed. Only when the tasks have reached the status „succeeded“, I want to do something in the MainThread. After that I want to execute another list of tasks and visualize them on a separate stage as well. The following figure shows the desired processing sequence (depending on the source code listed below):
enter image description here
For this purpose I have written the classes MyLoader. The MyLoader-class contains a separate Task and binds the progress-properties with a Label and a Progressbar in the constructor:
public class MyLoader {
public Label label = null;
public ProgressBar progressBar = null;
public VBox vbox;
public Task<Integer> task = null;
public String name;
public MyLoader(String name) {
this.name = name;
this.label = new Label();
this.progressBar = new ProgressBar();
this.vbox = new VBox(2);
//UI-Layout for Progress
this.vbox.getChildren().addAll(this.label, this.progressBar);
HBox.setHgrow(this.vbox, Priority.ALWAYS);
this.vbox.setAlignment(Pos.CENTER);
this.progressBar.prefWidthProperty().bind(this.vbox.widthProperty().subtract(20));
//Counter-Size
Random r = new Random();
int max = r.nextInt((100 - 50) + 1) + 50;
//Task
this.task = new Task<Integer>() {
#Override
protected Integer call() throws Exception {
int idx = 0;
while(idx <= max) {
Thread.sleep(20); //... for long lasting processes
updateMessage(name+"-progress: "+idx);
updateProgress(idx, max);
idx++;
}
return max;
}
protected void succeeded() {
updateMessage(name+" succeeded!");
System.out.println(name+" succeeded!");
super.succeeded();
}
};
//Bind Properties
this.label.textProperty().bind(task.messageProperty());
this.progressBar.progressProperty().bind(task.progressProperty());
}
}
In the MainClass, I combine several MyLoader instances in an ArrayList and run them with an ExecutorService. To create the new stage I use the static method progressStage(List). Each Stage is shown before the ExecutorService executes the respective tasks. Here's the MainClass code:
public class MainClass extends Application{
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
//Thread-Pool
ExecutorService es = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
//FirstLoaders
List<MyLoader> firstLoaders = new ArrayList<MyLoader>();
firstLoaders.add(new MyLoader("A"));
firstLoaders.add(new MyLoader("B"));
//Show 1. Stage
Stage firstStage = progressStage(firstLoaders);
firstStage.show();
//Execute firstLoaders
for(MyLoader l1 : firstLoaders)
es.execute(l1.task);
//1) TODO: How can I wait for the completion of the first loaders and start the second loaders?
//... doSomething1() ...
//SecondLoaders
List<MyLoader> secondLoaders = new ArrayList<MyLoader>();
secondLoaders.add(new MyLoader("C"));
secondLoaders.add(new MyLoader("D"));
secondLoaders.add(new MyLoader("E"));
//Show 2. Stage
Stage secondStage = progressStage(secondLoaders);
secondStage.setX(firstStage.getX());
secondStage.setY(firstStage.getY()+firstStage.getHeight());
secondStage.show();
for(MyLoader l2 : secondLoaders)
es.execute(l2.task);
//2) TODO How can I wait for the completion of the second loaders and start the primaryStage?
//... doSomething2() ...
Scene scene = new Scene(new StackPane(), 450, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
static Stage progressStage(List<MyLoader> loaderTasks) {
int count = loaderTasks.size();
VBox loadBox = new VBox(count);
for(int i=0; i<count; i++)
loadBox.getChildren().add(loaderTasks.get(i).vbox);
HBox.setHgrow(loadBox, Priority.ALWAYS);
loadBox.setAlignment(Pos.CENTER);
Stage dialogStage = new Stage();
dialogStage.setScene(new Scene(loadBox, 300, count * 50));
dialogStage.setAlwaysOnTop(true);
return dialogStage;
}
}
The program is executable so far - but the calculation sequence appears completely parallel.
What I tasted:
1) So far I have managed to get the process to be read and stopped using the get() method. But then the stage is only displayed when the threads in the background have finished their work.
//1) TODO: „doSomeThing1()“
List<Integer> integers = new ArrayList<Integer>();
for(MyLoader ml : firstLoaders)
integers.add(ml.task.get());
System.out.println(integers.toString());
2) Also with the Task.setOnSucceded() method I could not get any useful results yet. Mainly because the stage is only shown after the computing. The problem is that I am not able to query the status of all tasks at a defined time.
3) The application of a CountDownLatch has also achieved a comparable result.
4) In addition, the shutdown() method of the ExecutorService causes a termination. This solution is therefore also not suitable.
//1) TODO: „doSomeThing1()“
es.shutdown();
try {
es.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
//SecondLoaders
//...
}catch (InterruptedException e) {
e.printStackTrace();
}
Is there a suitable approach for such intentions? So far I have not come to any useful result.
When a task is done, just update a counter and check, if the task currently completed was the last one in the current set.
The following code demonstrates this. (There are certainly things in the code that could be improved though, but the concept should get clear.)
public class App extends Application {
public static void main(String[] args) {
launch(args);
}
private VBox taskViewContainer;
ExecutorService executor;
int tasksDone;
private void runTasks(List<MyTask> tasks, IntegerProperty index) {
if (tasks.isEmpty()) {
index.set(index.get()+1);
} else {
int taskCount = tasks.size();
tasksDone = 0;
for (MyTask task : tasks) {
taskViewContainer.getChildren().add(new TaskView(task));
task.setOnSucceeded(evt -> {
++tasksDone;
if (tasksDone == taskCount) {
// proceed to next task set after all tasks are done
index.set(index.get() + 1);
}
});
executor.submit(task);
}
}
}
#Override
public void init() throws Exception {
// create executor during initialisation
executor = Executors.newFixedThreadPool(4);
}
#Override
public void stop() throws Exception {
// shutdown executor when javafx shuts down
executor.shutdownNow();
}
#Override
public void start(Stage primaryStage) throws Exception {
taskViewContainer = new VBox();
Label text = new Label();
// generate random set of tasks
Random random = new Random();
List<List<MyTask>> taskLists = new ArrayList<>();
for (int i = 0; i < 20; ++i) {
int count = random.nextInt(10) + 1;
List<MyTask> tasks = new ArrayList<>(count);
taskLists.add(tasks);
for (int j = 0; j < count; ++j) {
tasks.add(new MyTask(String.format("%d.%c", i+1, (char) ('A'+j)), random.nextInt((100 - 50) + 1) + 50));
}
}
// property holding the current index in the task set list
IntegerProperty index = new SimpleIntegerProperty(-1);
index.addListener((o, oldValue, newValue) -> {
// gui update for change of task set
taskViewContainer.getChildren().clear();
text.setText(String.format("Task set %d / %d done", newValue, taskLists.size()));
int i = newValue.intValue();
if (i < taskLists.size()) {
// launch next set of tasks
runTasks(taskLists.get(i), index);
}
});
// start initial tasks
index.set(0);
text.setMinWidth(200);
text.setMaxWidth(Double.MAX_VALUE);
HBox root = new HBox(text, taskViewContainer);
root.setMinHeight(10 * 50);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
}
class TaskView extends HBox {
TaskView(MyTask task) {
setPrefSize(400, 50);
ProgressBar progress = new ProgressBar();
progress.progressProperty().bind(task.progressProperty());
Label label = new Label(task.getName());
Label message = new Label();
message.textProperty().bind(task.messageProperty());
getChildren().addAll(progress, new VBox(label, message));
}
}
class MyTask extends Task<Integer> {
private final int max;
private final String name;
public String getName() {
return name;
}
public MyTask(String name, int max) {
this.max = max;
this.name = name;
}
#Override
protected Integer call() throws Exception {
int idx = 0;
while(idx <= max) {
Thread.sleep(20); //... for long lasting processes
updateMessage(name+"-progress: "+idx);
updateProgress(idx, max);
idx++;
}
return max;
}
}
The above code does not take the possibility of canceling tasks/tasks terminating with an exception.

JavaFX2 play wav in background

So I'm trying to play indefinitly a song on a background thread, but when the music ends it does not loop as it was supose to.
Tried the offered solution but yet no joy! Here is the code for the main class, hope this helps in the resolution of the issue.
Even tried to loop the thread, but no joy...
Not sure why it's ending after playing the full file once, but not sure how to solve it!
Here is the code I have. Any help is welcome
public class Main extends Application {
Media sugar = new Media(this.getClass().getResource("sounds/t1coSugar.wav").toExternalForm());
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
Application.launch(Main.class, args);
}
#Override
public void start(Stage primaryStage)
{
primaryStage.setTitle("pacman");
primaryStage.setWidth(MazeData.calcGridX(MazeData.GRID_SIZE_X + 2)); //stage size x
primaryStage.setHeight(MazeData.calcGridY(MazeData.GRID_SIZE_Y + 5)); //stage size y
//splash screen
//end of splash screen
final Group root = new Group();
final Scene scene = new Scene(root);
root.getChildren().add(new Maze());
primaryStage.setScene(scene);
primaryStage.show();
int playbackgroundmusic = playbackgroundmusic();
}
private int playbackgroundmusic()
{
Runnable task = new Runnable() {
#Override
public void run() {
playSugar(); //method of the music
}
};
// Run the task in a background thread
Thread backgroundThread = new Thread(task);
// Terminate the running thread if the application exits
backgroundThread.setDaemon(true);
// Start the thread
backgroundThread.start();
return 0;
}
public void playSugar()
{
MediaPlayer mediaplayer = new MediaPlayer(sugar);
mediaplayer.volumeProperty().setValue(0.4);
mediaplayer.setStartTime(Duration.seconds(0));
mediaplayer.setStopTime(Duration.seconds(67));
mediaplayer.setAutoPlay(true);
mediaplayer.setCycleCount(MediaPlayer.INDEFINITE);
mediaplayer.play();
}

How do i update a textfield in javafx in loop ?

I have a small javafx application using scene builder which on a button click should read a string from COM port at regular intervals and update in a text field.
But now it only shows the last string if I use a for loop, and nothing if i put the code in infinite loop (That's my temporary requirement).
Can anyone help me so that at each read from COM port the new string is updated in the text field.
Here is the code I used for both the cases :
Note : In both cases in controller class, I'm getting perfect output on console.
public class Main extends Application
{
#Override
public void start(Stage primaryStage)
{
try
{
Parent root = FXMLLoader.load(getClass().getResource("test.fxml"));
Scene scene = new Scene(root);
//scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setTitle("test");
primaryStage.setScene(scene);
primaryStage.show();
}
catch(Exception e)
{
e.printStackTrace();
}
}
public static void main(String[] args)
{
launch(args);
}
}
Here is the Controller class :
// In this case it shows only the last string in the text field.
public class Controller implements Initializable
{
#FXML
private Button sayHelloButton;
#FXML
private TextField helloField;
#Override
public void initialize(URL arg0, ResourceBundle arg1)
{
}
#FXML
public void printHello(ActionEvent event)
{
if(event.getSource() == sayHelloButton)
{
SerialPort serialPort = new SerialPort("COM22");
for(int i=0;i<5;i++)
{
try
{
if(!serialPort.isOpened())
{
serialPort.openPort();
serialPort.setParams(9600, 8, 1, 0);
}
String str = serialPort.readString(10,3000);
System.out.println(str);
helloField.clear();
helloField.setText(str);
}
catch(Exception e)
{
helloField.setText(e.toString());
}
}
}
}
}
Here is the method with infinite loop :
//this shows nothing in the text field
#FXML
public void printHello(ActionEvent event)
{
if(event.getSource() == sayHelloButton)
{
SerialPort serialPort = new SerialPort("COM22");
while(true)
{
try
{
if(!serialPort.isOpened())
{
serialPort.openPort();
serialPort.setParams(9600, 8, 1, 0);
}
String str = serialPort.readString(10,3000);
System.out.println(str);
helloField.clear();
helloField.setText(str);
}
catch(Exception e)
{
helloField.setText(e.toString());
}
}
}
}
There are a couple things happening here. In your first example, you state that the console output is correct but the TextField only shows the last result.
This is expected if the loop executes quickly. The TextField is being updated, but it happens so quickly that you can't see it until the loop ends and the last result is still being displayed. Even if you have a delay built into the loop, this could still block the UI from being updated until the loop is completed.
With your infinite loop, the issue is that the loop is being run on the JavaFX Application Thread (JFXAT). This blocks any updates to the GUI until the loop is finished, which is never is.
You will need to move the infinite loop to a new background thread. From there, you can update the GUI using the Platform.runLater() method.
SerialPort serialPort = new SerialPort("COM22");
new Thread(() -> {
while(true)
{
try
{
if(!serialPort.isOpened())
{
serialPort.openPort();
serialPort.setParams(9600, 8, 1, 0);
}
String str = serialPort.readString(10,3000);
System.out.println(str);
// Update the UI on the JavaFX Application Thread
Platform.runLater(() -> {
helloField.clear();
helloField.setText(str);
});
}
catch(Exception e)
{
Platform.runLater(() -> helloField.setText(e.toString()));
}
}
}).start();
This allows your UI to continually update as the background thread sends it new information.

stop for X seconds before running next function in JAVA FXML (using SCENEBUILDER)

I've used Scenebuilder to place few shapes in my GUI (simplified version of my project). I would like the shapes to change colours but wait 2 seconds between changing colours. I want these changes to happen in my controller class after a button is pressed.
Circle1.setFill(YELLOW)
Wait(2 seconds)
Circle2.setFill(BLUE)
I'm not sure how to do that. I have read online about threading, but I don't really understand how to implement that from my Main and into my Controller class. Also, I could not really find any examples online. My Main class looks like:
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
BorderPane root = (BorderPane)FXMLLoader.load(getClass().getResource("File.fxml"));
Scene scene = new Scene(root);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
}
Please help. Also, if you could provide an example would be helpful for me to understand as I could not really find one online that gives an example of this.
Answering this question is easiest through an example I believe. So I've created a small Traffic Light application, since it allows me to use Circle and a timed sequence similar to your problem, whilst being a familiar concept for all.
I'll be using java.util.Timer alongside java.util.TimerTask for handling the sequence of lights. You may choose to use some animation / time line in JavaFX, but I think that is overkill for this kind of task.
I include the three files used in this project:
FXMLTrafficLight.fxml - which defines my FXML layout
FXMLTrafficLightController.java - my FXML controller
TrafficLightApplication.java - for completeness my subclass of Application, this is just the boiler plate.
FXMLTrafficLight.fxml
Not a fancy layout, just a VBox with three circles redLight, amberLight and greenLight, plus two Button objects startLights and stopLights used to start and stop the timer.
<VBox fx:id="root" id="VBox" xmlns:fx="http://javafx.com/fxml/1" fx:controller="javafxtimer.FXMLTrafficLightController">
<children>
<Circle fx:id="redLight" radius="100"></Circle>
<Circle fx:id="amberLight" radius="100"></Circle>
<Circle fx:id="greenLight" radius="100"></Circle>
<Button fx:id="startLights" text="Start Lights" onAction="#startLights"></Button>
<Button fx:id="stopLights" text="Start Lights" onAction="#stopLights"></Button>
</children>
</VBox>
FXMLTrafficLightController.java
I've included the model/state in the controller for simplicity. Whether a light is red / amber / green is determined by a boolean flag. The initial state is set in the initialize() method, and is updated by calling updateState().
When startLights(ActionEvent) is invoked (the EventHandler for startLights) a new Timer is constructed with a TimerTask implementation that first invokes updateState() on the thread created by the Timer and then invokes updateLights() which changes the color of the lights based on the current state on the JavaFX Application Thread using Platform.runLater(Runnable).
Note: the TimerTask itself will not be run on the JavaFX Application Thread, hence the need to use Platform.runLater(Runnable) for updating the GUI.
When stopLights(ActionEvent) is invoked, it will cancel the Timer.
Note that both startLights(ActionEvent) and stopLights(ActionEvent) toggle which Button objects are enabled on the interface as well.
public class FXMLTrafficLightController implements Initializable {
#FXML
private Circle redLight;
#FXML
private Circle amberLight;
#FXML
private Circle greenLight;
#FXML
private Button startLights;
#FXML
private Button stopLights;
private Timer timer;
private static final int DELAY = 2000; // ms
private boolean red, amber, green;
#Override
public void initialize(URL url, ResourceBundle rb) {
red = true;
amber = false;
green = false;
stopLights.setDisable(true);
updateLights();
}
#FXML
private void startLights(ActionEvent e) {
toggleButtons();
timer = new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
// Not run on the JavaFX Application Thread!
updateState();
// Using Platform.runLater(Runnable) to ensure updateLights()
// is run on the JavaFX Application Thread
Platform.runLater(new Runnable() {
#Override
public void run() {
updateLights();
}
});
}
}, 0, DELAY); // no initial delay, trigger again every 2000 ms (DELAY)
}
#FXML
private void stopLights(ActionEvent e) {
toggleButtons();
timer.cancel();
}
private void toggleButtons() {
startLights.setDisable(!startLights.isDisable());
stopLights.setDisable(!stopLights.isDisable());
}
private void updateState() {
if (red && !amber && !green) {
amber = true;
} else if (red && amber && !green) {
red = false;
amber = false;
green = true;
} else if (!red && !amber && green) {
green = false;
amber = true;
} else {
red = true;
amber = false;
green = false;
}
}
private void updateLights() {
redLight.setFill(red ? Color.RED : Color.GREY);
amberLight.setFill(amber ? Color.ORANGE : Color.GREY);
greenLight.setFill(green ? Color.GREEN : Color.GREY);
}
}
TrafficLightApplication.java
For completeness... Just the standard boiler plate with file names changed.
public class TrafficLightApplication extends Application {
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("FXMLTrafficLight.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Handle multiple JavaFX application launches within a loop

My code currently reads my Gmail inbox via IMAP (imaps) and javamail, and once it finds an email with zip/xap attachment, it displays a stage (window) asking whether to download the file, yes or no.
I want the stage to close once I make a selection, and then return to the place within the loop from which the call came. My problem arises because you cannot launch an application more than once, so I read here that I should write Platform.setImplicitExit(false); in the start method, and then use primartyStage.hide() (?) and then something like Platform.runLater(() -> primaryStage.show()); when I need to display the stage again later.
The problem occuring now is that the flow of command begins in Mail.java's doit() method which loops through my inbox, and launch(args) occurs within a for loop within the method. This means launch(args) then calls start to set the scene, and show the stage. Since there is a Controller.java and fxml associated, the Controller class has an event handler for the stage's buttons which "intercept" the flow once start has shown the stage. Therefore when I click Yes or No it hides the stage but then just hangs there. As if it can't return to the start method to continue the loop from where launch(args) occurred. How do I properly hide/show the stage whenever necessary, allowing the loop to continue whether yes or no was clicked.
Here is the code for Mail.java and Controller.java. Thanks a lot!
Mail.java
[Other variables set here]
public static int launchCount = 0;#FXML public Text subjectHolder;
public static ReceiveMailImap obj = new ReceiveMailImap();
public static void main(String[] args) throws IOException, MessagingException {
ReceiveMailImap.doit();
}
#Override
public void start(Stage primaryStage) throws Exception {
loader = new FXMLLoader(getClass().getResource("prompts.fxml"));
root = loader.load();
controller = loader.getController();
controller.setPrimaryStage(primaryStage);
scene = new Scene(root, 450, 250);
controller.setPrimaryScene(scene);
scene.getStylesheets().add("styleMain.css");
Platform.setImplicitExit(false);
primaryStage.setTitle("Download this file?");
primaryStage.initStyle(StageStyle.UNDECORATED);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void doit() throws MessagingException, IOException {
Folder inbox = null;
Store store = null;
try {
Properties props = System.getProperties();
Session session = Session.getDefaultInstance(props, null);
store = session.getStore("imaps");
store.connect("imap.gmail.com", "myAccount#gmail.com", "Password");
inbox = store.getFolder("Inbox");
inbox.open(Folder.READ_WRITE);
Message[] messages = inbox.getMessages();
FetchProfile fp = new FetchProfile();
fp.add(FetchProfile.Item.ENVELOPE);
fp.add(UIDFolder.FetchProfileItem.FLAGS);
fp.add(UIDFolder.FetchProfileItem.CONTENT_INFO);
fp.add("X-mailer");
inbox.fetch(messages, fp);
int doc = 0;
int maxDocs = 400;
for (int i = messages.length - 1; i >= 0; i--) {
Message message = messages[i];
if (doc < maxDocs) {
doc++;
message.getSubject();
if (!hasAttachments(message)) {
continue;
}
String from = "Sender Unknown";
if (message.getReplyTo().length >= 1) {
from = message.getReplyTo()[0].toString();
} else if (message.getFrom().length >= 1) {
from = message.getFrom()[0].toString();
}
subject = message.getSubject();
if (from.contains("myAccount#gmail.com")) {
saveAttachment(message.getContent());
message.setFlag(Flags.Flag.SEEN, true);
}
}
}
} finally {
if (inbox != null) {
inbox.close(true);
}
if (store != null) {
store.close();
}
}
}
public static boolean hasAttachments(Message msg) throws MessagingException, IOException {
if (msg.isMimeType("multipart/mixed")) {
Multipart mp = (Multipart) msg.getContent();
if (mp.getCount() > 1) return true;
}
return false;
}
public static void saveAttachment(Object content)
throws IOException, MessagingException {
out = null; in = null;
try {
if (content instanceof Multipart) {
Multipart multi = ((Multipart) content);
parts = multi.getCount();
for (int j = 0; j < parts; ++j) {
part = (MimeBodyPart) multi.getBodyPart(j);
if (part.getContent() instanceof Multipart) {
// part-within-a-part, do some recursion...
saveAttachment(part.getContent());
} else {
int allow = 0;
if (part.isMimeType("application/x-silverlight-app")) {
extension = "xap";
allow = 1;
} else {
extension = "zip";
allow = 1;
}
if (allow == 1) {
if (launchCount == 0) {
launch(args);
launchCount++;
} else {
Platform.runLater(() -> primaryStage.show());
}
} else {
continue;
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if ( in != null) { in .close();
}
if (out != null) {
out.flush();
out.close();
}
}
}
public static File createFolder(String subject) {
JFileChooser fr = new JFileChooser();
FileSystemView myDocs = fr.getFileSystemView();
String myDocuments = myDocs.getDefaultDirectory().toString();
dir = new File(myDocuments + "\\" + subject);
savePathNoExtension = dir.toString();
dir.mkdir();
System.out.println("Just created: " + dir);
return dir;
}
}
Controller.java
public class Controller implements Initializable {
#FXML
private Text subjectHolder;
public Button yesButton, noButton;
public ReceiveMailImap subject;
#Override
public void initialize(URL url, ResourceBundle rb) {
subject= new ReceiveMailImap();
subjectHolder.setText(subject.returnSubject());
}
public Stage primaryStage;
public Scene scene;
#FXML
ComboBox<String> fieldCombo;
public void setPrimaryStage(Stage stage) {
this.primaryStage = stage;
}
public void setPrimaryScene(Scene scene) {
this.scene = scene;
}
public String buttonPressed(ActionEvent e) throws IOException, MessagingException {
Object source = e.getSource();
if(source==yesButton){
System.out.println("How to tell Mail.java that user clicked Yes?");
return "POSITIVE";}
else{subject.dlOrNot("no");
System.out.println("How to tell Mail.java that user clicked No?");
primaryStage.hide();
return "NEGATIVE";}
}
}
There are a lot of issues with the code you have posted, but let me just try to address the ones you ask about.
The reason the code hangs is that Application.launch(...)
does not return until the application has exited
In general, you've kind of misunderstood the entire lifecycle of a JavaFX application here. You should think of the start(...) method as the equivalent of the main(...) method in a "traditional" Java application. The only thing to be aware of is that start(...) is executed on the FX Application Thread, so if you need to execute any blocking code, you need to put it in a background thread.
The start(...) method is passed a Stage instance for convenience, as the most common thing to do is to create a scene graph and display it in a stage. You are under no obligation to use this stage though, you can ignore it and just create your own stages as and when you need.
I think you can basically structure your code as follows (though, to be honest, I have quite a lot of trouble understanding what you're doing):
public class Mail extends Application {
#Override
public void start(Stage ignored) throws Exception {
Platform.setImplicitExit(false);
Message[] messages = /* retrieve messages */ ;
for (Message message : messages) {
if ( /* need to display window */) {
showMessage(message);
}
}
}
private void showMessage(Message message) {
FXMLLoader loader = new FXMLLoader(getClass().getResource("prompts.fxml"));
Parent root = loader.load();
Controller controller = loader.getController();
Scene scene = new Scene(root, 450, 250);
stage.setScene(scene);
stage.initStyle(StageStyle.UNDECORATED);
stage.setTitle(...);
// showAndWait will block execution until the window is hidden, so
// you can query which button was pressed afterwards:
stage.showAndWait();
if (controller.wasYesPressed()) {
// ...
}
}
// for IDEs that don't support directly launching a JavaFX Application:
public static void main(String[] args) {
launch(args);
}
}
Obviously your logic for decided whether to show a window is more complex, but this will give you the basic structure.
To check which button was pressed, use showAndWait as above and then in your controller do
public class Controller {
#FXML
private Button yesButton ;
private boolean yesButtonPressed = false ;
public boolean wasYesPressed() {
return yesButtonPressed ;
}
// use different handlers for different buttons:
#FXML
private void yesButtonPressed() {
yesButtonPressed = true ;
closeWindow();
}
#FXML
private void noButtonPressed() {
yesButtonPressed = false ; // not really needed, but makes things clearer
closeWindow();
}
private void closeWindow() {
// can use any #FXML-injected node here:
yesButton.getScene().getWindow().hide();
}
}

Resources