basic GUI question

J

jrobinss

Hi all, I'm very sorry about the simplicity of this question, but I
don't seem to get around it.

First, this is what I want:
a command-line interface (CLI) program that displays a dialog asking
something from the user. The user has the choice, either answer the
graphic dialog or else type in "stop" in the CLI (which hides or
destroys the GUI).

Seems simple enough. The principle is to launch the CLI, upon
receiving command "start" launch the GUI in a separate thread, and
wait both for user input in the dialog and in CLI.

My problems, of course, stem from the famous EDT. I've taken the
opportunity to read up on it (not enough I suppose), but I still can't
get it right. Either my dialog is ok, but the CLI hangs, or the CLI
responds, but the dialog is erratic, with freezes (an behavior
described on several web sites and attributes to bad thread
management).

Question: who can give me the code? :)

More detailed question: here are some code samples that obviously
don't work, otherwise I wouldn't be here; Any comments welcome.
Note: I've suppressed some code building the CLI, so it's not an
SSCCE. I suspect that there are enough wrongs in what I'm posting to
start correcting. If not, I'll post an SSCCE.


public class DialogTest {
[ ... main and other stuff...]

private Thread dialogThread_ = null;
private Frame myFrame_ = buildFrame();
private Frame buildFrame() {
// builds a kind of GUI, its correctness is not important here
JFrame frame = new JFrame("my frame");
JTextField myTxtField = new JTextField(10);
Object[] messageElements = {"Some question", myTxtField};
JOptionPane optionPane = new JOptionPane(messageElements,
JOptionPane.QUESTION_MESSAGE,
JOptionPane.YES_NO_OPTION);
frame.setContentPane(optionPane);
return frame;
}
private void onInput(String input) { // some callback
System.out.println("input=" + input);
}

/**
* this implementation doesn't provide any means for the user to
* stop the GUI using CLI, because it waits for a GUI answer.
*/
public void processCommandBasic(CliCommand cmd) {
if (cmd == CliCommand.start) {
String answer = JOptionPane.showInputDialog(myFrame_,
"What?");
onInput(answer);
} else {
myFrame_.setVisible(false);
}
}

/**
* this implementation hands back the hand to the CLI,
* but freezes the first time the user tries to input.
*/
public void processCommandWithMyFrame(CliCommand cmd) {
if (cmd == CliCommand.start) {
myFrame_.pack();
myFrame_.setVisible(true); // input to be handled by frame
} else {
myFrame_.setVisible(false);
}
}

/**
* this freezes and hangs the first time it's called.
*/
public void processCommandUsingJOptionPane(CliCommand cmd) {
if (cmd == CliCommand.start) {
dialogThread_ = new Thread() {
@Override
public void run() {
String answer =
JOptionPane.showInputDialog(myFrame_, "What?");
onInput(answer);
}
};
SwingUtilities.invokeLater(dialogThread_);
} else {
myFrame_.setVisible(false);
}
}

/**
* this works the same as the previous one, but with a frame.
*/
public void processCommandUsingFrame(CliCommand cmd) {
if (cmd == CliCommand.start) {
Runnable showDialog = new Runnable() {
public void run() {
myFrame_.pack();
myFrame_.setVisible(true);
}
};
SwingUtilities.invokeLater(showDialog);
} else {
myFrame_.setVisible(false);
}
}
}
 
S

Simon

jrobinss said:
Hi all, I'm very sorry about the simplicity of this question, but I
don't seem to get around it.

First, this is what I want:
a command-line interface (CLI) program that displays a dialog asking
something from the user. The user has the choice, either answer the
graphic dialog or else type in "stop" in the CLI (which hides or
destroys the GUI).

Seems simple enough. The principle is to launch the CLI, upon
receiving command "start" launch the GUI in a separate thread, and
wait both for user input in the dialog and in CLI.

My problems, of course, stem from the famous EDT. I've taken the
opportunity to read up on it (not enough I suppose), but I still can't
get it right. Either my dialog is ok, but the CLI hangs, or the CLI
responds, but the dialog is erratic, with freezes (an behavior
described on several web sites and attributes to bad thread
management).

Question: who can give me the code? :)

More detailed question: here are some code samples that obviously
don't work, otherwise I wouldn't be here; Any comments welcome.
Note: I've suppressed some code building the CLI, so it's not an
SSCCE. I suspect that there are enough wrongs in what I'm posting to
start correcting. If not, I'll post an SSCCE.

I'm a bit confused about your code because I don't see the part where
the CLI input is read. I believe however, that this (Thread?) is where
your problem is.

Anyway, does the attached code work for you? It creates the dialog in
one thread (the EDT) and creates a separate thread for the CLI. Both
threads notify each other when they have a result. I don't have an
elegant solution to avoid the Thread.stop() in the end, except maybe
making this thread a daemon thread. I am not aware of any more elegant
method to stop a thread while it is reading input (Closing the stream?
Rather not.).

Some remarks regarding your version:
- At least in one place you are creating a Thread where you only need a
Runnable.
- For most people it is easier to follow your code if it follows Java
code conventions. E.g. is CliCommand.start a constant (=static final)?
Then use UPPERCASE names etc.

Cheers,
Simon




import java.util.Scanner;

import javax.swing.JDialog;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

public class Test {

private static Object lock = new Object();
private static String answer = null;
private static JDialog dialog;

public static void main(String[] argv) {

SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
dialog = new JDialog();
JTextField field = new JTextField(10);
dialog.getContentPane().add(field);
dialog.setModal(true);
dialog.pack();
dialog.setVisible(true);
synchronized (lock) {
if (answer == null) {
Test.answer = field.getText();
} else {
System.err.println("CLI has set answer. "+
"Ignoring answer from GUI.");
}
lock.notify();
}
}
});

Thread cliThread = new Thread("CLI-Thread") {
@Override
public void run() {
System.out.println("Please type your answer: ");
Scanner scanner = new Scanner(System.in);
String input = scanner.nextLine();
synchronized (lock) {
Test.answer = input;
lock.notify();
}
}
};
cliThread.start();

synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (dialog.isVisible()) {
dialog.setVisible(false);
}
if (cliThread.isAlive()) {
// don't do this, only for demo
cliThread.stop();
}
System.out.println("Answer = " + answer);
}
System.exit(0);
}
}
 
M

markspace

jrobinss said:
If not, I'll post an SSCCE.

Please post an SSCCE. There's just a bit too much missing here.
However I did notice a few things.

private Frame myFrame_ = buildFrame();
private Frame buildFrame() {
// builds a kind of GUI, its correctness is not important here


Correctness is important. This is not built on the EDT. This object
must be constructed on the EDT.

* this works the same as the previous one, but with a frame.
*/
public void processCommandUsingFrame(CliCommand cmd) {
if (cmd == CliCommand.start) {
Runnable showDialog = new Runnable() {


This is not too bad. invokeLater() takes a Runnable, not a thread, so
this is better than the one that makes a thread. However you must do
more than call "pack()" on the EDT. The whole object and all its
subcomponents must be created on the EDT. Move the call to
"buildFrame()" to here.

public void run() {
myFrame_.pack();
myFrame_.setVisible(true);
}
};
SwingUtilities.invokeLater(showDialog);
} else {
myFrame_.setVisible(false);

setVisible() is NOT thread safe. Call this on the EDT.


Fix those and give us a driver for this code which exhibits the problem.
You probably don't need much more than a main method which calls a few
of these methods to make a complete program. We don't need full source
code, just enough that it duplicates the problem you are seeing.
 
D

Daniel Pitts

jrobinss said:
Hi all, I'm very sorry about the simplicity of this question, but I
don't seem to get around it.

First, this is what I want:
a command-line interface (CLI) program that displays a dialog asking
something from the user. The user has the choice, either answer the
graphic dialog or else type in "stop" in the CLI (which hides or
destroys the GUI).

Seems simple enough. The principle is to launch the CLI, upon
receiving command "start" launch the GUI in a separate thread, and
wait both for user input in the dialog and in CLI.

My problems, of course, stem from the famous EDT. I've taken the
opportunity to read up on it (not enough I suppose), but I still can't
get it right. Either my dialog is ok, but the CLI hangs, or the CLI
responds, but the dialog is erratic, with freezes (an behavior
described on several web sites and attributes to bad thread
management).

Question: who can give me the code? :)

More detailed question: here are some code samples that obviously
don't work, otherwise I wouldn't be here; Any comments welcome.
Note: I've suppressed some code building the CLI, so it's not an
SSCCE. I suspect that there are enough wrongs in what I'm posting to
start correcting. If not, I'll post an SSCCE.



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

/**
* @author Daniel Pitts
*/
public class Input {

public static void main(String...args) throws IOException {
EventQueue.invokeLater(new Runnable() {
public void run() {
JOptionPane.showConfirmDialog(null, "Config");
System.exit(0);
}
});
System.in.read();
System.exit(0);
}
}
 
J

jrobinss

Thanks all for your replies!

I'll now do some testing. Then I'll report the results here. If I'm
still stuck, I'll post an SSCCE.

The reason I didn't post an SSCCE is that for the CLI part I use a
custom utility class that I wrote that takes user input and manages
command recognition, exit messages etc. So it would have been
cumbersome to post it. Again, next time I will, if the first answers
are not enough to get me going.

BTW, I'm aware that enums should be upper case, only here I use them
directly as commands, and I'd rather the "usage" command provided
lower-case commands to the user. So it's a shortcut for enums who have
a string that's different from their name.

More later...
 
A

Andrew Thompson

...If I'm still stuck, I'll post an SSCCE.

The reason I didn't post an SSCCE is that for the CLI part I use a
custom utility class that I wrote that takes user input and manages
command recognition, exit messages etc. So it would have been
cumbersome to post it. Again, next time I will, ...

No, don't do that. A request for an SSCCE is not
an invitation to 'dump all your code' to the forum.

Please *read* the SSCCE document before posting
any code that you think is an SSCCE.
<http://pscode.org/sscce.html>
 
M

markspace

Andrew said:
Good advice.


It is AWT (so it should not have to be built on the EDT).


No, he actually builds a JFrame in the method. He's just using a super
type of JFrame (Frame) for some odd reason. Could be a typo.
 
M

markspace

jrobinss said:
Thanks all for your replies!

I'll now do some testing. Then I'll report the results here. If I'm
still stuck, I'll post an SSCCE.

The reason I didn't post an SSCCE is that for the CLI part I use a
custom utility class that I wrote that takes user input and manages
command recognition, exit messages etc. So it would have been
cumbersome to post it. Again, next time I will, if the first answers
are not enough to get me going.


As Andrew said, please DON'T. Write a short little command interpreter
that only takes two commands (start and stop) and just show us that,
with a short example of creating a GUI component. We don't want to see
your whole code base. Just an example that reproduces the problem.

BTW, I'm aware that enums should be upper case, only here I use them
directly as commands, and I'd rather the "usage" command provided
lower-case commands to the user.


enum Commands { START, STOP }

String name = START.toString().toLowerCase();
 
A

Andrew Thompson

No, he actually builds a JFrame in the method.  He's just using a super
type of JFrame (Frame) for some odd reason.  ...

Good point. :D

Just goes to show you how closely I examine code
that is 'not an SSCCE'. ;)
 
J

jrobinss

[note to andrew: I think I understand what an SSCCE is, and I've spent
some time reducing its size before posting it]

Ok, here's an SSCCE. Its behavior currently is that, upon "start", a
user dialog is created and displayed. When the dialog is validated,
the result is echoed on the command-line. The command "stop" closes
the dialog. One can do "start", OK and "stop" any number of times
(however it's not robust to several consecutive "start"s).

That's what it should do, and what it does after a while. In fact, the
first time the dialog is demanded, it seems to have a problem running:
it doesn't show, then it freezes, and the only solution to get it
active is to hit enter in the CLI several times. After that subsequent
requests work fine.

Question is: why? How to make it work?

BTW, I'm aware that there will be a number of remarks on the code,
even without the threading issue. No problem... :)


Oh and thanks to Simon for introducing me to java.util.Scanner. I
didn't know about it, and was still getting input with
new BufferedReader(new InputStreamReader(System.in)).readLine();
It's nicer with Scanner.

(SSCCE indented with only two spaces, otherwise I had trouble limiting
to 80 chars)
============
package jrobinss.cli_dialog;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Arrays;
import java.util.Scanner;

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

public class DialogSSCCE {

// CLI stuff
// ---------

/**
* lowercase because they are used as user commands
*/
enum UserCommands {
start,
stop
}

public static void main(String[] args) {
new DialogSSCCE().doOperations();
System.exit(-1);
}

private void doOperations() {
while (true) {
String userStr = getUserInput();
// command "x" is for exit
if ("x".equalsIgnoreCase(userStr)) {
return;
}
doOperation(userStr);
}
}

private String getUserInput() {
System.out.println("Please enter your input... "
+ Arrays.toString(UserCommands.values()));
Scanner scanner = new Scanner(System.in);
return scanner.nextLine();
}

private void doOperation(String str) {
Scanner scanner = new Scanner(str);
if (scanner.hasNext()) {
String word = scanner.next();
if (word != null && ! "".equals(word)) {
try {
UserCommands cmd = UserCommands.valueOf(word);
processCommand(cmd);
} catch (Exception e) {
System.out.println("Sorry, can't understand command " + word
+ ", available commands are: "
+ Arrays.toString(UserCommands.values
()));
}
}
}
}

// now for the GUI part
// --------------------

private JFrame myFrame_ = null;
private JOptionPane myOptionPane_ = null;
private JTextField myTxtField_ = null;
private void buildFrame() {
myFrame_ = new JFrame("my frame");
myTxtField_ = new JTextField(10);
Object[] messageElements = {"Enter something", myTxtField_};
myOptionPane_ = new JOptionPane(messageElements,
JOptionPane.QUESTION_MESSAGE,
JOptionPane.YES_NO_OPTION);
myOptionPane_.addPropertyChangeListener(new MyPptyChangeListener
());
myFrame_.setContentPane(myOptionPane_);
myFrame_.pack();
}

private class MyPptyChangeListener implements PropertyChangeListener
{
public void propertyChange(PropertyChangeEvent evt) {
if (myFrame_ == null) {
return;
}
String prop = evt.getPropertyName();

if (myFrame_ != null
&& myFrame_.isVisible()
&& (evt.getSource() == myOptionPane_)
&& (JOptionPane.VALUE_PROPERTY.equals(prop) ||
JOptionPane.INPUT_VALUE_PROPERTY.equals(prop))) {
Object value = myOptionPane_.getValue();
if (value == JOptionPane.UNINITIALIZED_VALUE) {
return;
}

String result = null;
if (value.equals(JOptionPane.YES_OPTION)) {
result = myTxtField_.getText();
} else {
System.out.println("GUI cancelled, no result");
}
clearAndHide(); // destroys the GUI!
if (result != null) {
System.out.println("got GUI result=" + result);
}
}
}
}

private void clearAndHide() {
if (myFrame_ != null) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
myFrame_.setVisible(false);
myFrame_ = null;
}
});
}
}

private void processCommand(UserCommands cmd) {
if (cmd == UserCommands.start) {
Runnable showDialog = new Runnable() {
public void run() {
buildFrame();
myFrame_.setVisible(true);
}
};
SwingUtilities.invokeLater(showDialog);
} else {
assert(cmd == UserCommands.stop);
clearAndHide();
}
}
}
===========

Thanks for reading. :)
 
J

jrobinss

Daniel Pitts wrote:
[solution deleted]

Sorry for being obtuse, but I copy-pasted your code into Eclipse, and
its execution only waits for any input by the user, and then exits.

The EDT seems to never be activated.

This is purely an experimentation result. I suppose for you it
produces an "OK" dialog?
 
J

jrobinss

Simon said:
Anyway, does the attached code work for you?

I doesn't compile. :)
(only because you use @override for an interface method
implementation, I just checked, I'm still with Java 5 so it isn't
accepted)

BTW, to the best of my knowledge the Thread:stop() method is
deprecated, and should be replaced with interrupt().

I ran your code, and saw only the CLI, no GUI. When I typed something
in, it obviously triggered the EDT, because I ran into an NPE, so I
changed your line
if (dialog.isVisible()) {
dialog.setVisible(false);
}
to
if (dialog != null && dialog.isVisible()) {
(the dialog wasn't yet created)

With this corrected, upon startup I only see the CLI, and when I type
in some input, the program exits. (same as Daniel's code in fact)

Could it be that the execution from Eclipse is in cause? I haven't
tested from the command-line... Seems unlikely.
 
M

markspace

jrobinss said:
[note to andrew: I think I understand what an SSCCE is, and I've spent
some time reducing its size before posting it]

Ok, here's an SSCCE. Its behavior currently is that, upon "start", a


I haven't read your SSCCE yet, but I thought I'd show you my solution. I
notice you have one extra niggle that was mentioned earlier (the
property change listener).


package serverwithgui;

import java.awt.Label;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

/**
*
* @author Brenden
*/
public class ServerWithGui {

public static void main(String[] args) throws Exception
{
BufferedReader input = new BufferedReader( new InputStreamReader(
System.in ) );
for(;;) {
System.out.print(" cmd> ");
System.out.flush();
String s = input.readLine();
if (s != null) {
if ("start".equals(s)) {
Gui.start();
} else if ("stop".equals(s)) {
Gui.stop();
} else {
System.err.println("Didn't recognize: " + s);
}
} else { // s == null
break; // exit for(;;) loop
}
} // for(;;)
}

}

class Gui {

// Utility class: "start()" and "stop()"

private Gui() {} // non-instantiable, use start and stop methods

private static JFrame jf;

public static void start()
{
startGuiOnEdt();
}

public static void stop()
{
stopGuiOnEdt();

}

private static void startGuiOnEdt()
{
SwingUtilities.invokeLater( new Runnable() {

public void run()
{
createAndShowGui();
}
} );
}

private static void createAndShowGui() {
jf = new JFrame( "Please answer the question.");
JPanel p = new JPanel();
p.add( new Label( "What is your quest?"));

final JTextField f = new JTextField( 20 );
f.addActionListener( new ActionListener() {

public void actionPerformed(ActionEvent e)
{
System.out.println( f.getText() );
f.setText( "" );
}
} );

p.add( f );
jf.add( p );

jf.pack();
jf.setLocationRelativeTo( null );
jf.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
jf.setVisible( true );
}

private static void stopGuiOnEdt()
{
SwingUtilities.invokeLater( new Runnable() {

public void run()
{
stopGui();
}
} );
}

private static void stopGui()
{
jf.setVisible( false );
jf.dispose();
jf = null;
}
}
 
M

markspace

jrobinss said:
BTW, I'm aware that there will be a number of remarks on the code,
even without the threading issue. No problem... :)


Yes. Your property change listener is just bizarre. You need help with
Swing, not multi-threading. Read the docs on JOptionPane, since you're
trying to use one:

<http://java.sun.com/javase/6/docs/api/javax/swing/JOptionPane.html>

Quote:

"Direct Use:
To create and use an JOptionPane directly, the standard pattern is
roughly as follows:

JOptionPane pane = new JOptionPane(arguments);
pane.setXxxx(...); // Configure
JDialog dialog = pane.createDialog(parentComponent, title);
dialog.show();
Object selectedValue = pane.getValue();
if(selectedValue == ...."


See how much easier that is than your huge property change listener?
Use pane.createDialog(), I don't think stuffing the JOptionPane into a
JFrame will work very well. At least, the above DOES work, so just use
it. If you want a JFrame, make it out of regular components like I did.

Other than that, we do the same thing. My example just prints the
string the user enters in the GUI to the screen, and your's does too.

Don't forget that if you need to do something beside print, you'll need
to synchronize code running on the EDT with the rest of the program.
This is the reverse of the problem we've been talking about so far.
We've been talking about the need to start in the main thread and get
over to the EDT with invokeLater. Now we're inside an ActionListener
(in my code; I think the property change listener works the same) and
we need to get off the EDT and synchronize with all the objects in the
program that are NOT GUI objects.

Try the SwingWorker class for the this:

<http://java.sun.com/docs/books/tutorial/uiswing/concurrency/worker.html>
 
L

Lew

enum Commands { START, STOP }

String name = START.toString().toLowerCase();

Another standard solution is to use a private display String member - a bit
wordy but you can set up your 'enum' template in Eclipse or NetBeans to put up
the boilerplate:

public enum Command
{
START( "start" ),
STOP( "stop" );

private final String handle;

private Command( String d )
{
this.handle = d;
}

@Override public String toString()
{
return this.handle;
}
}
 
J

jrobinss

So, I'm very sorry for wasting everyone's time. As I had hinted in my
reply to Simon, the problem was launching the program from Eclipse.

After receiving several solutions (I just tested Mark Space's, thank
you Mark), I was intrigued that they all had the same result as mine,
regarding hanging and GUI not showing up. None of them worked!

I just tested them all from command-line, and they work fine...
including mine. :-/

So I guess the question is now, why doesn't it work as well executing
from inside Eclipse? I suppose Eclipse is using the EDT too. So is the
lesson learned, never test graphical programs from Eclipse??

Is there any special way to launch programs that require EDT from
Eclipse?

As for me, I'll start cleaning up my code now and move on to my next
problem. Thanks for all the tips.
 
J

jrobinss

markspace said:
enum Commands { START, STOP }

String name = START.toString().toLowerCase();

or this:
enum MyCommand {
START,
STOP;

@Override
public String toString() {
return super.toString().toLowerCase();
}
}
Another standard solution is to use a private display String member - a
bit wordy but you can set up your 'enum' template in Eclipse or NetBeans
to put up the boilerplate:

public enum Command
{
START( "start" ),
STOP( "stop" );

That's what I usually do. Neatly separates HMI from code. In this case
its adds more boilerplate than that, because the user command input is
also not recognized, so I would have to compare the user input to
command.toString() for all commands.

So what I was doing really was a shortcut.

However, in the SSCCE I could have done like all here, and gone with
"start".equalsIgnoreCase(userInput); with no enum at all.
 
J

jrobinss

markspace said:
I haven't read your SSCCE yet, but I thought I'd show you my solution. I
notice you have one extra niggle that was mentioned earlier (the
property change listener).

Many thanks.

I just changed this:
private static void stopGui()
{ if (jf == null) return;
jf.setVisible( false );
jf.dispose();
jf = null;
}

to avoid a NPE.
(yes, I enter a lot of commands, which leads me to these NPEs, not to
test your solution, but to see what it takes to display a GUI)

See my other post to get to the real problem.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,062
Latest member
OrderKetozenseACV

Latest Threads

Top