CosmicGiant
CosmicGiant

Reputation: 6441

How to make the main method wait for input on GUI without using Listener as a direct trigger?

I am working on a webscraping tool that should perform various operations with the scraped data.

Because of this, I need various different GUIs to work in an orderly manner and because of that, I need the main method to wait before each has completed it's purpose.

After searching for a while, I have found the following StackOverflow questions that provided some clues on how to solve the problem, but that I could not implement because they have some differences to my case:

How to wait for input in a text field How to make main thread wait a different thread to finish


I know I can trigger code using a Listener to a/the GUI's components (a button, for example), but i'm having a hard time making the main-thread wait for that listener to wake it up, while the code for the GUI's thread (when there is one) is initialized by the main thread...


This is an simplified code to demonstrate how the program is supposed to work:

public class Main {
    /*
     * Waiter is a simple GUI with just an "Start" button in it. Here in place of my actual GUIs.
     */
    private static Waiter auth; //Represents my NTLM-authentication form.
    private static Waiter status; //Represents a status-feedback GUI that will be displayed during processing.
    private static Waiter operation; //Represents a GUI in with the user choses what to do with the gathered data.

    public static void main(String[] args) throws InterruptedException {
        auth = new Waiter();
        auth.setVisible(true);
        System.out.println("NTLM Authentication form. Should wait here until user has filled up the GUI and clicked \"Start\".");
        System.out.println("Authenticates WebClient's NTLM using data inputed to the GUI...");
        auth.dispose();
        Thread srt = new Thread(status = new Waiter());
        srt.start();
        status.setVisible(true);
        //Performs webscraping operations...
        System.out.println("Prepares the webscraped data here...Things like downloading files and/or parsing text...");
        System.out.println("Keeps the user aware of the progress using the \"status\" GUI.");
        status.setVisible(false);
        //Clears the status GUI.
        operation = new Waiter();
        operation.setVisible(true);
        System.out.println("Operation selection form. Should wait here until user selects an option.");
        System.out.println("Starts performing the operation(s)...");
        operation.dispose();
        status.setVisible(true);
        System.out.println("Performs the operation(s), while giving status-feedback to the user.");
        status.setVisible(false);
        System.out.println("Displays a file-save dialog to save the results.");
        System.out.println("And finally, displays a \"End of operations\" dialog before ending.");
    }
}

UPDATE 1: The main difficulty I'm having is to implement something like this (this is what I want to do):

//Main method...code...
Thread srt = new Thread(status = new Waiter());
//Before "srt.start();"...
status.startButton.addActionListener(new ActionListener() {
  @Override
  public void actionPerformed(ActionEvent e) {
    main.continueExecution();
  }
});
//Thread's run() being something like "status.setVisible(true); main.waitGUI();"
srt.start();
//continues here after the Listener is triggered...more code...

Instead of this (what is being the solution to most other people, if I'm understanding it right...) (this is what I don't want to do, if possible):

//GUI before this one...
//code...
Thread srt = new Thread(status = new Waiter());
status.startButton.addActionListener(new ActionListener() {
  @Override
  public void actionPerformed(ActionEvent e) {
    /*
     * Code that should come after this GUI.
     */
  }
});
//Thread's run() being something like "status.setVisible(true);"
srt.start();
//"ends" here...(Initial code or GUI before this "status")

In other words, I'm having trouble implementing the GUIs and Listeners in a way to trigger main's thread's "sleep" and "wake up" actions, instead of triggering actual processing code.


UPDATE 2:

Following @JB_Nizet 's tip on SwingUtilities.invokeLater(), I took a good look at the SwingUtilities docs, and after I found out about how the SwingUtilities.invokeAndWait() method works, and I think I've found how to do it, using a combination of Semaphore and invokeAndWait().

I need someone with a better understanding of multi-threading and/or GUIs to confirm if it's a safe, valid solution or not. (I'll then edit the question and clean it up, and if confirmed, post this in proper "answer format")

Anyways, here goes the modified code, which seems to be working for me:

public class Main_Test {

    //Semaphore:
    public static Semaphore semaphore;
    //GUIs:
    private static Waiter auth; //Represents my NTLM-authentication form.

    public static void main(String[] args) {
        try {
            semaphore = new Semaphore(1);
//          semaphore.acquire();
            auth = new Waiter() {

                @Override
                public void run() {
                    try {
                        System.out.println(Main_Test.getThread() + this.getName() + " has been created and is now running.");
                        semaphore.acquire(); //Makes main pause.
                        this.setVisible(true);
                    } catch (InterruptedException ex) {
                        Logger.getLogger(Main_Test.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            };
            auth.jButton1.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent e) {
                    System.out.println(getThread() + "NTLM has been hypothetically authenticated.");
                    semaphore.release(); //Makes main continue after GUI is done.
                    auth.dispose();
                }
            });
//          semaphore.release();
            SwingUtilities.invokeAndWait(auth);
            semaphore.acquire(); //<- Where the main effectively gets paused until the permit is released.
            /*
             * GUI's run() will accquire the semaphore's permit.
             * The invokeAndWait() garantees (?) it will happen before main's acquire().
             * This causes the main to pause when trying to acquire the permit.
             * It stays paused until the actionListener release() that permit.
             */
            System.out.println(getThread() + "This message represents the processing, and should come only after the hypothetical NTLM authentication.");
        } catch (InterruptedException ex) {
            Logger.getLogger(Main_Test.class.getName()).log(Level.SEVERE, null, ex);
        } catch (InvocationTargetException ex) {
            Logger.getLogger(Main_Test.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public static String getThread() {
        return String.format("%-32s --- ", Thread.currentThread().toString());
    }
}

Upvotes: 0

Views: 2470

Answers (1)

JB Nizet
JB Nizet

Reputation: 691715

I'm not sure I have completely understood what you want to do, but it seems to me that you have a consumer thread (the main thread, waiting for events from the event dispatch thread), and a producer thread (the event dispatch thread).

The typical way to implement this is to use a blocking queue as a communication mechanism:

  • Create a blocking queue
  • Create your GUI and pass it the blocking queue
  • start a loop which gets data from the queue. Since the queue is blocking, the main thread will be blocked untile there is something in the queue
  • Have your event listeners, running in the EDT, post data to the blocking queue

Upvotes: 1

Related Questions