0

So this is just the basic console structure, I follow for my GUI program, however, I noticed whenever I enter one of the options to prompt a screen it would end up freezing, I am aware that it's obviously because of the infinite while loop, but I had no other way of expecting the console to re-prompt as soon as the window is closed. Which was why I relied on the while loop, please can someone provide me with a solution for this.

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import java.util.Scanner;

public class testGuiConsole extends Application {
        public static void main(String[] args) {
            launch();
        }

        @Override
        public void start(Stage primaryStage) throws Exception {

            BorderPane borderPane = new BorderPane();
            labelStatement:
            while (true) {
                System.out.println("Press a for method a");
                System.out.println("Press b for method b");
                System.out.println("Enter q to end program!");

                Scanner sc = new Scanner(System.in);
                String input = sc.nextLine().toLowerCase();
                switch (input) {
                    case "a":
                        optionA(borderPane, primaryStage);
                        primaryStage.show();
                        break;
                    case "b":
                        optionB(borderPane, primaryStage);
                        primaryStage.show();
                        break;
                    case "q":
                        break labelStatement;
                    default:
                        System.out.println("wrong option!");
                }
            }
        }



    private void optionA(BorderPane borderPane, Stage primaryStage) {
            primaryStage.setScene(new Scene(borderPane, 1000, 500));
        }

    private void optionB(BorderPane borderPane, Stage primaryStage) {
        primaryStage.setScene(new Scene(borderPane, 1000, 500));

    }

}

Genesis_Kitty
  • 63
  • 2
  • 7
  • tried debugging or adding some basic print statements? – Stultuske Dec 08 '20 at 12:32
  • I've tried debugging but no errors occurred as in the program never crashed on me, it's just that the window is frozen. and when I close the window the whole program ends. – Genesis_Kitty Dec 08 '20 at 12:40
  • Try `primaryStage.showAndWait()` instead of `primaryStage.show()` – Felix Dec 08 '20 at 12:41
  • debugging is more than watching if errors occur. what did you learn from breakpoints? – Stultuske Dec 08 '20 at 12:41
  • java naming conventions, please – kleopatra Dec 08 '20 at 12:41
  • Well, that actually causes an error, "Exception in Application start method". – Genesis_Kitty Dec 08 '20 at 12:46
  • When I was running in debug mode, it went through most the initialized classes, at the near end it indicated that most of the stage's attributes were either null or false. – Genesis_Kitty Dec 08 '20 at 12:55
  • While you may have some good reason to write your code like this. If possible, you might consider just making the whole thing a straight GUI program, rather than trying to write GUI code and a command processing loop like you have. Instead you could show a window where the user could choose options by clicking on buttons, possibly launching a child window to service the option. The user could close the main window to exit the program. If you want users only to be able to choose a single option at a time, then you could make the child windows modal. Perhaps your requirements differ though. – jewelsea Dec 09 '20 at 01:25

1 Answers1

2

You should not perform long computation in the FXThread and a blocking method (like Scanner#nextLine) can be very long...

You have to request the option on a separated thread. Depending on the selected option, you may have to perform some actions to modify the GUI. In that case you MUST do that in the FXThread by using the Platform#runLater method.

Below is a quick example using Java 8 :

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

import java.lang.annotation.Native;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.function.BooleanSupplier;

public class TestGuiConsole extends Application {
    public static void main(String[] args) {
        launch();
    }


    @Override
    public void start(Stage primaryStage) throws Exception {
        final BorderPane borderPane = new BorderPane();
        borderPane.setCenter(new Label("Select an Option"));

        final Map<String, BooleanSupplier> actionMap = new HashMap<>();
        actionMap.put("a", () -> {
            optionA(borderPane);
            return false;
        });
        actionMap.put("b", () -> {
            optionB(borderPane);
            return false;
        });
        actionMap.put("q", () -> {
            closeGui(primaryStage);
            return true;
        });

        final Thread askOptionThread = new Thread(() -> askOption(actionMap));
        askOptionThread.setDaemon(true);
        askOptionThread.start();

        System.out.println("SHOW");
        primaryStage.setTitle("Options");
        primaryStage.setScene(new Scene(borderPane, 600, 400));
        primaryStage.centerOnScreen();
        primaryStage.show();
    }


    private void closeGui(Stage primaryStage) {
        Platform.runLater(primaryStage::hide);
    }

    private void optionA(BorderPane borderPane) {
        Platform.runLater(() -> {
            borderPane.setCenter(new Label("Option A selected"));
        });
    }

    private void optionB(BorderPane borderPane) {
        Platform.runLater(() -> {
            borderPane.setCenter(new Label("Option B selected"));
        });
    }


    private static void askOption(Map<String, BooleanSupplier> actionMap) {
        while (!Thread.currentThread().isInterrupted()) {
            System.out.println("Press a for method a");
            System.out.println("Press b for method b");
            System.out.println("Enter q to end program!");

            Scanner sc = new Scanner(System.in);
            String input = sc.nextLine().toLowerCase();
            final BooleanSupplier action = actionMap.get(input);
            if (action != null) {
                boolean shouldQuit = action.getAsBoolean();
                if (shouldQuit) {
                    break;
                }
            } else {
                System.out.println("wrong option!");
            }
        }

    }


}

Here is a sample after the comment by @Genesis_Kitty (several optimizations could be done for a better code):

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class TestGuiConsole extends Application {
    public static void main(String[] args) {
        launch();
    }


    @Override
    public void start(Stage primaryStage) throws Exception {

        final Map<String, Runnable> actionMap = new HashMap<>();
        actionMap.put("a", () -> optionA(primaryStage));
        actionMap.put("b", () -> optionB(primaryStage));
        actionMap.put("q", Platform::exit);

        Platform.setImplicitExit(false); //needed or your application will exit
        primaryStage.setTitle("Options");
        primaryStage.showingProperty().addListener((l,o,s) -> {if (!s) { launchAskOption(actionMap);}});
        launchAskOption(actionMap);
    }

    private void launchAskOption(Map<String, Runnable> actionMap) {
        final Thread askOptionThread = new Thread(() -> askOption(actionMap));
        askOptionThread.start();
    }

    private void optionA(Stage primaryStage) {
        final BorderPane borderPane = new BorderPane();
        borderPane.setCenter(new Label("Option A selected"));
        primaryStage.setScene(new Scene(borderPane, 600,400));
        primaryStage.centerOnScreen();
        primaryStage.show();
    }

    private void optionB(Stage primaryStage) {
        final BorderPane borderPane = new BorderPane();
        borderPane.setCenter(new Label("Option B selected"));
        primaryStage.setScene(new Scene(borderPane, 600,400));
        primaryStage.centerOnScreen();
        primaryStage.show();
    }

    private static void askOption(Map<String, Runnable> actionMap) {
        while (!Thread.currentThread().isInterrupted()) {
            System.out.println("Press a for method a");
            System.out.println("Press b for method b");
            System.out.println("Enter q to end program!");

            Scanner sc = new Scanner(System.in);
            String input = sc.nextLine().toLowerCase();
            final Runnable action = actionMap.get(input);
            if (action != null) {
                Platform.runLater(action);
                break;
            } else {
                System.out.println("wrong option!");
            }
        }
    }

}
Bastien Aracil
  • 1,531
  • 11
  • 6
  • This is very helpful, but the reason I had a switch case was that once I enter an option the GUI should appear then when I close the window it should go back to the console, asking for another option until only option q is entered which would end the program. – Genesis_Kitty Dec 08 '20 at 15:03
  • This is well done. An alternate implementation to the actionMap, might be to use the built-in [Accelerator/KeyCombination](https://openjfx.io/javadoc/15/javafx.graphics/javafx/scene/Scene.html#getAccelerators()) system in scenes. For an example, see: [Using JavaFX 2.2 Mnemonic (and accelerators)](https://stackoverflow.com/questions/12710468/using-javafx-2-2-mnemonic-and-accelerators). The accelerator based solution probably doesn't meet the requirements of the original question, but may be an alternative for other users who need to handle input within the GUI context. – jewelsea Dec 14 '20 at 20:38