2

I am a newb in Swing so this may be stupid.

Anyway I have created a class that extends JFrame and inside a panel are contained a JTextField a JTextArea and a JButton.

This is supposed to be a console implementation so the TextField will print the input to the TextArea when I press Enter or the Button next to it.

I have made the listener for this and everything works fine. My problem now is how do I make a method that expects me to press Enter on the Field? e.g. I have a method that needs 3 line input. First line calls method, second one expects me to input something and third one expects some more input. After all input is finished I print something to the TextArea.

So practically will the method have to wait a Listener to fire or what? Can someone explain how this thing can work if at all or give me a workaround that does the trick?

Keep in mind I want some reusable way because I will probably implement a lot of methods with multi-line input. Thanks in advance!

Update: here is my class that extends JFrame - code was netbean generated mostly, I will make sure to work on the import statements sooner or later. I have not implemented a method as of yet because I have no idea how to do so, but expect me adding a little piece of code checking if the input is right at first (inside the ConsoleInputAcionPerformed) and call the method (let's call it methodX) that will need the rest of the two input lines. This class is called from another class in my main().

public class MainWindow extends javax.swing.JFrame {
private javax.swing.JButton EnterButton;
private javax.swing.JPanel ConsolePanel;
private javax.swing.JScrollPane ConsoleScroll;
private javax.swing.JTextArea ConsoleOutput;
private javax.swing.JTextField ConsoleInput;

public MainWindow() {
    initComponents();
}

private void initComponents() {

    ConsolePanel = new javax.swing.JPanel();
    ConsoleScroll = new javax.swing.JScrollPane();
    ConsoleOutput = new javax.swing.JTextArea();
    ConsoleInput = new javax.swing.JTextField();
    EnterButton = new javax.swing.JButton();

    setTitle("Graphical Super Console v.1.0");
    setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
    setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR));
    setPreferredSize(new java.awt.Dimension(800, 600));

    ConsoleOutput.setColumns(20);
    ConsoleOutput.setRows(5);
    ConsoleOutput.setLineWrap(true);
    ConsoleOutput.setEditable(false);
    ConsoleOutput.setFont(new java.awt.Font("Consolas", 1, 14));

    ConsoleScroll.setViewportView(ConsoleOutput);
    ConsoleScroll.setVerticalScrollBarPolicy(javax.swing.JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);

    ConsoleInput.setText("");
    ConsoleInput.requestFocusInWindow();
    ConsoleInput.setFont(new java.awt.Font("Consolas", 1, 14));
    ConsoleInput.addActionListener(new java.awt.event.ActionListener() {
        public void actionPerformed(java.awt.event.ActionEvent evt) {
            ConsoleInputActionPerformed(evt);
        }
    }); 

    EnterButton.setText(">>");
    EnterButton.setFont(new java.awt.Font("Consolas", 1, 14));
    EnterButton.addActionListener(new java.awt.event.ActionListener() {
        public void actionPerformed(java.awt.event.ActionEvent evt) {
            ConsoleInputActionPerformed(evt);
        }
    });

    javax.swing.GroupLayout ConsolePanelLayout = new javax.swing.GroupLayout(ConsolePanel);
    ConsolePanel.setLayout(ConsolePanelLayout);
    ConsolePanelLayout.setHorizontalGroup(
        ConsolePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
        .addGroup(ConsolePanelLayout.createSequentialGroup()
            .addContainerGap()
            .addGroup(ConsolePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addComponent(ConsoleScroll)
                .addGroup(ConsolePanelLayout.createSequentialGroup()
                    .addComponent(ConsoleInput, javax.swing.GroupLayout.DEFAULT_SIZE, 679, Short.MAX_VALUE)
                    .addGap(18, 18, 18)
                    .addComponent(EnterButton)))
            .addContainerGap())
    );
    ConsolePanelLayout.setVerticalGroup(
        ConsolePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
        .addGroup(ConsolePanelLayout.createSequentialGroup()
            .addContainerGap()
            .addComponent(ConsoleScroll, javax.swing.GroupLayout.DEFAULT_SIZE, 536, Short.MAX_VALUE)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
            .addGroup(ConsolePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                .addComponent(EnterButton)
                .addComponent(ConsoleInput, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
            .addContainerGap())
    );

    javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
    getContentPane().setLayout(layout);
    layout.setHorizontalGroup(
        layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
        .addComponent(ConsolePanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
    );
    layout.setVerticalGroup(
        layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
        .addComponent(ConsolePanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
    );

    pack();
    setVisible(true);
    ConsoleInput.requestFocus();
}
private void ConsoleInputActionPerformed(java.awt.event.ActionEvent evt) {
    printf(">"+ConsoleInput.getText()+"\n");
    ConsoleInput.setText("");
}

public javax.swing.JTextArea getConsoleOutput(){
    return ConsoleOutput;
}

public javax.swing.JTextField getConsoleInput(){
    return ConsoleInput;
}

public void printf(Object... obj){
    for(int i=0; i<obj.length; i++){
        ConsoleOutput.append(String.valueOf(obj[i]));
    }
}

}

Angelos Chalaris
  • 6,611
  • 8
  • 49
  • 75
  • Can you post an [SSCCE](http://sscce.org) ? – Branislav Lazic Sep 05 '12 at 20:50
  • 2
    [JTextField.addActionListener](http://docs.oracle.com/javase/7/docs/api/javax/swing/JTextField.html#addActionListener(java.awt.event.ActionListener)) will fire when a user hits [Enter] while the field has focus. – MadProgrammer Sep 05 '12 at 20:51

5 Answers5

1

The observer and observable:

The idea basically is that you have some class observing another and when something happens the class that is being observed, the Observable, will notify the class that Observes, the Observer, and tell it something has changed. The Observable has methods setChanged() and notifyObservers() to accomplish that. And the Observer listens to that call with the implemented update() method.

I put everything into one class so you can copy/paste and run this. When you hit a key you will see how it works.

//the textfield is wrapped in a class so that it can extends Observable
public class MyTextField extends Observable {

    private JTextField jTextField = new JTextField();

    //this method notifies the observers you will add   
    public void notify(Object o) {
        this.setChanged();
        this.notifyObservers(o);
    }

    public JTextField getJTextField() {
        return jTextField;
    }

}

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Observable;
import java.util.Observer;

import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

// The main class that observes the swing component you tell it to
public class Controller implements Observer {

    private final JFrame jFrame = new JFrame();

    private final MyTextField myTextField = new MyTextField();

    public Controller() {

        jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jFrame.pack();
        jFrame.setVisible(true);
        jFrame.add(myTextField.getJTextField());

        //here we add the Observer (Controller) to myTextField (Observable)
        myTextField.addObserver(this);

        //and the keylistener
        myTextField.getJTextField().addKeyListener(new KeyListener() {

            @Override
            public void keyTyped(KeyEvent e) {
                System.out.println("keyTyped " + e.getKeyCode());
                //now we notify our observers for real
                myTextField.notify(e.getKeyCode());
            }

            @Override
            public void keyReleased(KeyEvent e) {
                System.out.println("keyReleased " + e.getKeyCode());
                myTextField.notify(e.getKeyCode());
            }

            @Override
            public void keyPressed(KeyEvent e) {
                System.out.println("keyPressed " + e.getKeyCode());
                myTextField.notify(e.getKeyCode());
            }
        });
    }

    // this is where the event is received by the Observer 
    // from the observable.
    @Override
    public void update(Observable observable, Object object) {
        System.out.println("Notified by " + observable
                + " with object " + object);

    }

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

I hope that is understandable and a solution to your problem :)

fonZ
  • 2,428
  • 4
  • 21
  • 40
  • This is a really good example that I can understand and I will try implementing (in a while though, no time right now). I got a little confused in one point though. Does the whole JFrame need to be observed or only the JTextField? Because if I have to observe only the textfield this will take me a while modifying probably! Thanks a lot anyway! – Angelos Chalaris Sep 06 '12 at 06:41
  • And how can I implement mulitple observers based on different inputs? – Angelos Chalaris Sep 06 '12 at 09:14
  • Unless you have something you want to observe in the JFrame like an onclose/onminimize/... event or something like that, there is no need for it. You can make different instances MyTextField and make them (which is observable) with one or different observers. When you notifyObservers() it will notify all observers you added to the instance of MyTextField. – fonZ Sep 06 '12 at 11:41
  • So I added multiple observers right now that implement different functionality for one reason or another. Is is possible that I disable some observers in some cases and call some other or will I need to kill the ones I have and remake them? Is there some way to blindofld an observer in general? Thanks a great lot by the way, this solution just did it! – Angelos Chalaris Sep 06 '12 at 12:48
  • I think you cant do that. As you said you could remove the observer, execute notify and then readd it but that would require that you have direct access to the observer which might not be the case. I think the best solution in this case is to implement some logic in the update method of the observers and to execute the code it contains if it fills the requirements you set for it. Something like if (object instanceof boolean) ... – fonZ Sep 06 '12 at 13:39
  • Yes yes I am in the proccess of doing so right now as this will happen already. I will probably create a boolean in every observer that will be accessible and will allow me to disable them so when they get the notification they will just return nothing! :) – Angelos Chalaris Sep 06 '12 at 13:54
0

The only thing I can think of is you need to run the input from the field through a single method that can determine the current state...ie

public void handleFieldInput(JTextField field) {
    String text = field;
    switch (state) {
        case 0:
            // First line...maybe store the result in a List or array
            state++;
            break;
        case 1:
            // Second line...
            state++;
            break;
        case 2:
            // Third line...
            // Add contents to the text area
            state = 0;
            break;
    }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
0

Here is your solution:

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.*;

public class Frame extends JFrame{
    JTextField t = new JTextField(20);
    JPanel p = new JPanel();

    public Frame(){
        p.add(t);
        t.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e){
                t.setText("Hello world");
            }
        });
        add(p);
    }

    public static void main(String[] args){
        SwingUtilities.invokeLater(new Runnable(){
            public void run(){
                Frame f = new Frame();
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                f.pack();
                f.setVisible(true);
            }   
        });
    }
}
Branislav Lazic
  • 14,388
  • 8
  • 60
  • 85
  • This is an actionlistener, he wants when you hit enter. You have KeyListener for that. – fonZ Sep 05 '12 at 20:57
  • I have implemented listeners in my code, what I need is a method that does something wait for the Listener to fire once and then again to get the stuff I typed and calculate! – Angelos Chalaris Sep 05 '12 at 21:00
  • @jonathan.cruz An action listener is fired when the user hits enter on the field – MadProgrammer Sep 05 '12 at 21:00
  • And actually now im thinking :p there is a better solution for that. You can use Observer and Observable. Basically the Observable notifies the Observer when the Observable decides it. – fonZ Sep 05 '12 at 21:08
  • @jonathan.cruz That would be bad idea if he is a beginner. – Branislav Lazic Sep 05 '12 at 21:12
  • @brano88 i seriously think this is very powerfull and not that difficult if you understand interfaces and inheritance. – fonZ Sep 05 '12 at 21:14
  • I am too bad with interfaces though if someone cares to give me a simple and short description of observer and observable I may consider it. Not too much of a begginer but not a pro by any means! :P – Angelos Chalaris Sep 05 '12 at 21:32
  • check it out i finished it :) – fonZ Sep 05 '12 at 22:21
0

The easiest way to make a method hang while awaiting user input is probably to make a JDialog with a text box in it. Until the user closes the JDialog, your code will not run past the point at which you display that dialog. This doesn't quite seem like the solution you're looking for however.

What you probably want to do here to make your code hang is use wait and notify. For more information, see How to use wait and notify in Java?.

Community
  • 1
  • 1
Peter Berg
  • 6,006
  • 8
  • 37
  • 51
  • Thanks, this is actually a good start! (as soon as I get reputation I will vote this up, it is my first lead in this problem :D ) – Angelos Chalaris Sep 05 '12 at 21:03
  • Glad to hear it. I happen recently to have written an object called ParameterInputDialog which takes a var args number of types and then launches a JDialog with a JTextField and JLabel for each of the types passed. The user then must enter a valid value in each JTextField and hit okay before proceeding. Again not sure whether this would work for you but figured I'd let you know just in case – Peter Berg Sep 05 '12 at 21:43
  • notify and wait are used for concurrency, i.e. when one class needs to wait on another class to finish its execution. In this case its not usefull. – fonZ Sep 06 '12 at 11:44
  • I believe it is useful in this case actually because he needs the thread the program is running in to wait. What it's waiting for is some user input. Whatever code requests this input could be placed in another thread, launched from the main thread which itself would then be told to wait(). The user-input-requesting thread would notify the main thread that it can continue running once this input has been received. – Peter Berg Sep 06 '12 at 14:12
0

You'll probably have to do this a little differently than you had hoped. Basically you maintain a state of all the lines you have already received, and only when you have three lines already, then call the method that requires three lines. The general idea goes something like this:

List<String> buffer = new ArrayList<String>();

public void actionPerformed(ActionEvent e) {
    buffer.add(getText())
    if (!expectingMoreInput()) {
        processInput(buffer);
        buffer.clear();
    }
}

So for your specific case, expectingMoreInput() would just return buffer.size() < 3, and processInput would actually call the method that needs three lines.

The other way to do it would be with multiple threads and an object to pass lines between them. Be careful with this - thread interactions can get complicated. Something like:

SynchronousQueue<String> queue = new SynchronousQueue<String>();

public void actionPerfomred(ActionEvent e) {
    queue.add(getLine());
}

public void threeLineMethod() {
    String s1, s2, s3;
    try {
        s1 = queue.take();
        s2 = queue.take();
        s3 = queue.take();
    } catch (InterruptedException ex) {

    }
    // process the three lines
}

Note that here, take will block on put, which is exactly what you want. The flip side is that put will block on take, so if you don't have a thread constantly calling take, the event thread will block and it will lock up the interface.

Joe K
  • 18,204
  • 2
  • 36
  • 58
  • Thought of this and is probably my only workaround solution so far. Problem is I was hoping to implement various methods with messages asking for the correct input and handling exceptions in their code, so the problem is I will need to write everything tons of times and this may take a little longer. Basically I was hoping to imitate the normal Console Input idea that expects input. Still this is the only workable way for now! Thanks! :) – Angelos Chalaris Sep 05 '12 at 21:08
  • I edited to add another option that you might consider. There's always some danger when you add threading to an application, but it sounds like it might simplify your code. – Joe K Sep 05 '12 at 21:17
  • I like what I see that can be done using threading. I know the dangers and will be careful enough with it, but probably it can't get better than this in means of imitating the built-in OS Console systems. – Angelos Chalaris Sep 05 '12 at 21:31
  • So you mean that if and when this method is exited I will need some other method to constantly 'take' the input from the Listener so that my application doesn't stop and lock, am I right? (because I will implement a method called validate or recognize or something that will take what is inputted on every hit of the Enter key). – Angelos Chalaris Sep 05 '12 at 21:44
  • Correct. You could do something as simple as like a `while (true) { queue.take(); // do whatever, handle input }` and then inside that loop, you could call other methods that might call `take()` as needed. E.g., based on the first line, determine if this is the case where you need more input, and if so, call `take()` a few more times. When that method exits, it will go back to the while loop and not lock up the application. – Joe K Sep 05 '12 at 21:50