2

I'm writing a program that completes the following tasks in sequence:

  1. Collects user input from a JPanel
  2. Uses the input to copy dependencies from the program directory into a new project directory
  3. Uses the input to construct an interactive graph in the project directory

I have a separate class for each task and a main class that calls each object in sequence.

My problem is that the main class evaluates step 2 before step 1 is complete. Because the user has not yet closed the JPanel when the main class calls object 2, the user input is not collected before step 2 starts and the program crashes.

What I need is a way to signal to class 2 that the JPanel in class 1 has been closed. This way step 2 begins after the input fields have been collected in step 1.

Is there a way to have the window closing in class 1 trigger an action in class 2? If not, what would be the best way to solve this problem?

Bobulous
  • 12,967
  • 4
  • 37
  • 68
Reggie
  • 413
  • 2
  • 9
  • 19
  • 1
    You are most likely either 1) running Swing components off of the EDT, causing threading issues or 2) not using a modal dialogue. – Boris the Spider Aug 30 '14 at 16:47
  • I read up on threading and I think that might be the problem. I was hoping to keep things compartmentalized, but I guess I'll just have to put it all in one class. For anyone else who stumbles across this, the EDT is the Event Dispatch Thread and there's a pretty good article on it here: http://stackoverflow.com/questions/7217013/java-event-dispatching-thread-explanation. – Reggie Sep 03 '14 at 23:21

2 Answers2

5

"Is there a way to have the window closing in class 1 trigger an action in class 2? If not, what would be the best way to solve this problem?"

As Boris the Spider pointed out, you should be using a model dialog. You're probably using a frame. You should read up on Modality to learn its behavior and features. Also take some time to look at How to make Dialogs. In short, turning the dialog's modality on (which is defaulted on JOptionPane static showXxx methods and can be set on JDialog either through setModalityType or passed through the constructor), the flow will "block" until the dialog is closed.

Below is an example. It may be overcomplicated for such a simple task (as it could easily be accomplished with a JOptionPane), but it shows how to use a JDialog. Look as the ShowDialogActionListener class. The dialog is set visible and flow is not continued in the actionPerformed until the dialog is closed, which is when the Input is obtained from the dialog.

import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

public class DialogDemo {
    private JFrame frame = new JFrame();

    public DialogDemo() {
        JButton button = new JButton("Open Dialog");
        button.addActionListener(new ShowDialogActionListener());
        frame.setLayout(new GridBagLayout());
        frame.add(button);
        frame.setSize(300, 300);
        frame.setLocationRelativeTo(null);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }

    class ShowDialogActionListener implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            InputDialog dialog = new InputDialog(frame, true);
            System.out.println("Opened dialog.....");

            long start = System.currentTimeMillis();
            dialog.setVisible(true);
            System.out.println("Dialog closed after "
                    + (System.currentTimeMillis() - start) + " ms");

            Input input = dialog.getInput();
            ServiceOne service = new ServiceOne();
            service.serviceMethod(input);
        }
    }

    class ServiceOne {
        public void serviceMethod(Input input) {
            System.out.println(input.getInput());
        }
    }

    class InputDialog extends JDialog {
        private Input input;

        public InputDialog(JFrame parent, boolean modal) {
            super(parent, modal);

            JPanel panel = new JPanel(new GridLayout(0, 1));
            final JTextField field = new JTextField(20);
            JButton okButton = new JButton("OK");
            panel.add(field);
            panel.add(okButton);

            okButton.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    String text = field.getText();
                    input = new Input();
                    input.setInput(text);
                    InputDialog.this.dispose();
                }
            });

            setLayout(new GridBagLayout());
            add(panel);
            setSize(250, 250);
            setLocationRelativeTo(parent);
        }

        public Input getInput() {
            return input;
        }
    }

    class Input {
        private String input = "default";

        public void setInput(String input) {
            this.input = input;
        }

        public String getInput() {
            return input;
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new DialogDemo();
            }
        });
    }
}

As I said earlier, the same could easily have been accomplished with

String input = JOptionPane.showInputDialog("Enter a Message");

The above would also block flow execution.

Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • I think you may have misunderstood my question. I ran the code you provided and it does almost exactly what my program currently does--opens a parent window, opens a pop-up window when a button in the parent window is clicked, and logs the pop-up window input when the pop-up is closed. What I need is a way to then communicate to a separate class (the class from which the parent window was invoked) that the _parent_ window has been closed. Otherwise the class that invoked the input gui tries to execute the rest of its tasks before the gui input has actually been collected. – Reggie Sep 03 '14 at 23:14
  • It'll be a lot easier to tell what you're talking about with some code. Please edit your post with some relevant code – Paul Samsotha Sep 04 '14 at 01:43
  • If there was concise way to show you some code, I would have :) But I figured it out--had to use wait() and notify(). – Reggie Sep 06 '14 at 16:04
1

The trick to communicating events between classes is the wait() and notify() methods.

Suppose you're executing a main method. At some point main invokes another class, say a gui. Here you want main to pause and wait for certain events in the gui to complete before main proceeds with the rest of its actions.

This is accomplished by synchronizing code blocks between the two classes and telling main to wait() until the gui notifies it to proceed, notify(). For example:

main

public static void main(String[] args) throws Exception {

    GUI gui = new GUI();

    // Do some things
    doSomething();
    doSomthingElse();

    // Make sure we wait until gui input has been collected before proceeding
    synchronized(gui) {
        try {
            gui.wait();
        }
        catch(InterruptedException e){
            e.printStackTrace();
        }
    }

    // Do some things using the gui input we've been waiting for        
    doSomeMoreThings();
}

gui

// The gui method we want to synchronize
public void collectInput() {

    synchronized(this) {

        // Collect fields
        name = nameField.getText();
        age = ageField.getText();
        date = dateField.getText();

        // Notify waiter that our business is complete  
        notify();
    }
}
Reggie
  • 413
  • 2
  • 9
  • 19