简体   繁体   中英

Using showAndWait in JavaFX application

I'm creating a Login which uses Single Sign On. To do this, if the company a user logs in with is SSO enabled, then a JavaFX WebView will open, allowing the user to login. This login will be entirely web based so no real data capture on my part is required.

I have two classes, LoginManager and SSO . LoginManager checks to see if the company is SSOEnabled, and then goes to SSO if it is. From here, I create the UI, and then using Platform.runLater() , go to the method which opens the webview . However, my program goes to initJFXPanel and then instantly goes back to LoginManager to execute code there, which causes issues.

What I would like is to be able to go through all of SSO until it reaches a certain webpage and then I use frame.dispose(), then go back to LoginManager to continue. I think I need to use a Stage and showAndWait() but I'm not really sure how to go about this.

I tried creating a Stage inside initJFXPanel but this didn't work as it goes back to LoginManager before it has the chance to do anything, and I also tried setting the stage in createPopup but I wasn't sure what to use as the stage or the scene.

Here is my code:

LoginManager class

public class LoginManager {

    private boolean bSSOEnabled = true;

    private void Login() {

        // get company details

        if(bSSOEnabled) {
            new SSO();
        }

        // once SSO is done, continue with execution
    }
}

SSO class

public class SSO {

    private JFrame frame;
    private JFXPanel fxPanel;
    private JPanel containerPanel;
    final static String WEBVIEW = "WebView";

    public SSO(){
        createPopup();
    }

    private void createPopup(){

        Dimension size = new Dimension(480, 80);

        frame = new JFrame("Web Login");
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

        containerPanel = new JPanel(new CardLayout());

        fxPanel = new JFXPanel();
        fxPanel.setSize(size);

        Platform.runLater(() -> initJFXPanel(frame, fxPanel));

        containerPanel.add(fxPanel, WEBVIEW);

        frame.add(containerPanel, BorderLayout.CENTER);

        frame.setPreferredSize(size);
        frame.setResizable(false);
        frame.getRootPane().setWindowDecorationStyle(JRootPane.NONE);

        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);

    }

    private void initJFXPanel(final JFrame frame, final JFXPanel fxPanel){

        Group group = new Group();
        Scene scene = new Scene(group);
        fxPanel.setScene(scene);

        WebView webview = new WebView();

        group.getChildren().add(webview);
        webview.setMinSize(500, 640);
        webview.setMaxSize(500, 640);

        CookieManager cookieManager = new CookieManager();
        java.net.CookieHandler.setDefault(cookieManager);

        webview.getEngine().load("http://google.com/");


        if(webview.getEngine().getLocation().equalsIgnoreCase("cookie url")) {
            // harvest the cookies
        }
        else if(webview.getEngine().getLocation().equalsIgnoreCase("end url")) {
            frame.dispose();
        }

    }

}

Any help is appreciated. I've been stuck on this for a whole day and am not sure what to do at all.

If your application is a Swing application, and you need to use a WebView , then the correct approach is the one you have shown in your code, ie use a Swing window and embed the WebView in a JFXPanel . It is usually a bad idea to mix windows from the two frameworks, so I would recommend avoiding using a Stage at all.

The way to make code in Swing block until the user dismisses a window, ie the Swing equivalent of a JavaFX Stage.showAndWait() call, is to use a JDialog and make it modal. As well as blocking input to any other window, this will block execution (of the AWT Event Dispatch thread) until the dialog is dismissed.

What makes this a little trickier is that you have three threads to work with: the AWT Event Dispatch Thread, the JavaFX Application Thread, and the main thread. Showing the modal dialog will block the AWT Event Dispatch thread, but you want (if I understand the question correctly) the main thread to be blocked. Additionally, you must only create, modify, and show your Swing components on the AWT Event Dispatch thread, and you must only create, modify, and show your JavaFX nodes on the JavaFX Application Thread.

So I think you want something like this:

public class SSO {

    private JDialog dialog;
    private JFXPanel fxPanel;
    private JPanel containerPanel;
    final static String WEBVIEW = "WebView";

    public SSO(){
        // creates Swing components: MUST be called on AWT Event thread
        createPopup();
    }

    private void createPopup(){

        Dimension size = new Dimension(480, 80);

        dialog = new JDialog();
        dialog.setTite("Web Login");

        // make dialog modal:
        dialog.setModalityType(ModalityType.APPLICATION_MODAL);

        // oops: don't want this:
        // dialog.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        // but this:
        dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

        containerPanel = new JPanel(new CardLayout());

        fxPanel = new JFXPanel();
        fxPanel.setSize(size);

        Platform.runLater(() -> initJFXPanel(dialog, fxPanel));

        containerPanel.add(fxPanel, WEBVIEW);

        dialog.add(containerPanel, BorderLayout.CENTER);

        dialog.setPreferredSize(size);
        dialog.setResizable(false);
        dialog.getRootPane().setWindowDecorationStyle(JRootPane.NONE);

        dialog.pack();
        dialog.setLocationRelativeTo(null);
        dialog.setVisible(true);

    }

    private void initJFXPanel(final JDialog dialog, final JFXPanel fxPanel){

        Group group = new Group();
        Scene scene = new Scene(group);
        fxPanel.setScene(scene);

        WebView webview = new WebView();

        group.getChildren().add(webview);
        webview.setMinSize(500, 640);
        webview.setMaxSize(500, 640);

        CookieManager cookieManager = new CookieManager();
        java.net.CookieHandler.setDefault(cookieManager);

        webview.getEngine().load("http://google.com/");


        if(webview.getEngine().getLocation().equalsIgnoreCase("cookie url")) {
            // harvest the cookies
        }
        else if(webview.getEngine().getLocation().equalsIgnoreCase("end url")) {
            // this will hide the dialog and release the event dispatch thread
            // that is waiting for it, but it must be called on the event dispatch thread
            // not sure of your exact requirements, but you may need to do this
            // in the if clause as well?
            SwingUtilities.invokeLater(() -> dialog.setVisible(false));
        }

    }

}

Now if you instantiate SSO , the code will block until the dialog is dismissed. However, you must do that on the AWT Event Dispatch thread. The trick to waiting for that to complete on the main thread is to represent the call to new SSO() as a FutureTask , submit the FutureTask to SwingUtilities.invokeLater() , and then, back on the main thread, call get() on the FutureTask : this will block until the task completes. You need to be careful here: if you inadvertently do this on the Event Dispatch Thread, you end up in deadlock, as the event dispatch thread will block, waiting for something to happen on the event dispatch thread... So you probably want a guard against that to be safe.

So your code probably needs to look like this:

public class LoginManager {

    private boolean bSSOEnabled = true;

    private void Login() {

        // get company details

        if(bSSOEnabled) {
            if (SwingUtilities.isEventDispatchThread()) {
                // already on EDT, just invoke the SSO which will block 
                // until the dialog is dismissed:
                new SSO();
            } else {
                // create FutureTask to show dialog, etc:
                FutureTask<Void> ssoTask = new FutureTask<>(SSO:new, null);
                // submit task to AWT event dispatch thread:
                SwingUtilities.invokeLater(ssoTask);
                // wait for task to complete, i.e. for dialog to be dismissed:
                try {
                    ssoTask.get();
                } catch (InterruptedException exc) {
                    // thread was interrupted, representing cancelation of some kind:
                    System.out.println("Thread interrupted");
                    Thread.currentThread().interrupt();
                } catch (ExecutionException ee) {
                    // something went wrong...
                    throw new RuntimeException(ee);
                }
            }
        }



    // once SSO is done, continue with execution
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM