1
\$\begingroup\$

Right now, I've got this really ugly loop in my code that waits for a boolean to be true before the method can return. It's triggered when "Done" is clicked. I'm wondering, what is the proper way for me to delay this method's return until the button is pressed? How should I refactor this code so that it follows common practices for this?

Here is the relevant code. It is part of a much bigger application. This is the only place this happens because it is the only custom dialog box in the application. Everything else is done with JOptionPane, so it doesn't have this problem.

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;

public class Main {
    public static void main(String[] args) throws FileNotFoundException {
        PrintWriter out = new PrintWriter("textfile.txt");
        out.println(GUI.getUserInput());
        out.close();
    }
}

class GUI implements ActionListener {

    private static boolean pressed;

    public static String getUserInput() {
        pressed = false;

        JFrame frame = new JFrame();
        frame.setSize(400, 250);
        frame.setLocationRelativeTo(null);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setTitle("Diary");
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout(4, 2));
        JLabel instructionLabel = new JLabel("What's on your mind?");
        panel.add(instructionLabel, BorderLayout.NORTH);
        JTextArea inputField = new JTextArea("", 7, 30);
        inputField.setLineWrap(true);
        inputField.setWrapStyleWord(true);
        panel.add(new JScrollPane(inputField), BorderLayout.CENTER);
        JButton done = new JButton("Done");
        done.addActionListener(new GUI());
        panel.add(done, BorderLayout.SOUTH);
        frame.add(panel);
        frame.setVisible(true);
        inputField.setCaretPosition(inputField.getText().length());

        while (!pressed) { // Wait for the "Done" button to be pressed
            // For some reason, there has to be something in this loop for it to work
            System.out.println();
        }

        frame.dispose();
        return inputField.getText();
    }

    public void actionPerformed(ActionEvent e) {
        pressed = true;
    }
}
\$\endgroup\$
  • 1
    \$\begingroup\$ @4castle Your entire design is flawed. Java is intended to be event-driven, not polling-driven. \$\endgroup\$ – phyrfox May 3 '16 at 3:48
  • \$\begingroup\$ @phyrfox That is what I guessed, but I wasn't sure. What changes would need to be made? \$\endgroup\$ – 4castle May 3 '16 at 4:05
  • \$\begingroup\$ So you're trying to create your custom OptionPane that returns the users input as soon as done is clicked? \$\endgroup\$ – Bene May 3 '16 at 10:59
  • \$\begingroup\$ @Bene Yes, that is correct. \$\endgroup\$ – 4castle May 3 '16 at 12:38
  • 1
    \$\begingroup\$ @4castle this SO question should help you: stackoverflow.com/questions/14513494/… "Dialog.setVisible(true) blocks the calling thread until the dialog is no longer visible, if the dialog is modal." \$\endgroup\$ – Bene May 3 '16 at 15:30
1
\$\begingroup\$

1 import

I suggest you don't use the wildcard in your import statements, because

  • it clutters your namespace,
  • the maintainer of your code does not know what classes exactly you use in your code.

2 Main class

I would rename your class Main to, say, MyFrame, and declare it as a subclass of JFrame.

Summa summarum

All in all, I had this in mind:

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;


public class MyFrame extends JFrame {

    private final JPanel panel = new JPanel();
    private final JLabel instructionLabel = new JLabel("What's on your mind?");
    private final JTextArea textArea = new JTextArea(7, 30);
    private final JButton button = new JButton("Done");

    public MyFrame() {
        super("Diary"); // Sets the title to "Diary".
        this.setSize(400, 250);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.panel.setLayout(new BorderLayout(4, 2));
        this.panel.add(instructionLabel, BorderLayout.NORTH);
        this.textArea.setLineWrap(true);
        this.textArea.setWrapStyleWord(true);
        this.panel.add(new JScrollPane(textArea), BorderLayout.CENTER);
        this.button.addActionListener(new MyActionListener());
        this.panel.add(button, BorderLayout.SOUTH);
        this.getContentPane().add(panel);
        centerWindow(this);
        this.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> { new MyFrame(); });
    }

    private final class MyActionListener implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            try (PrintWriter writer = new PrintWriter("textfile.txt")) {
                writer.println(textArea.getText());
            } catch (final FileNotFoundException ex) {

            }

            MyFrame.this.dispatchEvent(
                    new WindowEvent(MyFrame.this, WindowEvent.WINDOW_CLOSING));
        }
    }

    private static void centerWindow(final Window window) {
        final Dimension dimension = window.getSize();
        final Dimension screen    = Toolkit.getDefaultToolkit().getScreenSize();

        window.setLocation((screen.width - dimension.width) / 2, 
                           (screen.height - dimension.height) / 2);
    }
}

Hope that helps.

\$\endgroup\$
  • \$\begingroup\$ Thanks for your help! I have one other consideration though. Is there a way for the UI to be separate from the part of the program that writes to the file? The overall application uses MVC to separate the View from the business logic. So I was hoping there would be a way to call getUserInput from a controller and return the result, similar to a JOptionPane which has to wait for an 'OK' button to be pressed. \$\endgroup\$ – 4castle May 3 '16 at 13:23
  • \$\begingroup\$ In larger applications you should strive for MVC. However, in your program, the business logic is just creating a PrintWriter and writing to it, so using MVC will not simplify your code. \$\endgroup\$ – coderodde May 3 '16 at 13:27
  • \$\begingroup\$ Well, as I said, the overall application already uses MVC for everything else in the application, so it would be weird that this is the only part of the program that violates it. With this, I would have to make the view call a method in the controller in order to resume the application, and that's usually frowned upon. \$\endgroup\$ – 4castle May 3 '16 at 13:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.