So I have a GUI which acts kind of like a console. I want the user to enter text into a JTextField and press enter. I've used key bindings to make a callback for when the user presses enter.
Now I want to create a method called waitForInput() which waits for the user to enter something and returns it. What I'm trying is below. But it results in a java.lang.IllegalMonitorStateException when notify() is called in the callback function.
public class MainWindow{
private JFrame mainWindow;
private JTextArea textEntry;
private String inputStringMonitor = ""; // lock/user input value
private Boolean stringReady = false; //flag for wait while loop
public MainWindow(){
mainWindow = new JFrame("console");
textEntry = new JTextArea();
// set up key bindings
InputAction = new UserInputAction();
textEntry.getInputMap().put( KeyStroke.getKeyStroke( "ENTER" ),"EnterAction" );
textEntry.getActionMap().put( "EnterAction", InputAction);
//configure window
mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainWindow.setMinimumSize(new Dimension(800,675));
mainWindow.getContentPane().add(textEntry);
mainWindow.pack();
mainWindow.setVisible(true);
}
// callback action when user presses enter
public class UserInputAction extends AbstractAction
{
public void actionPerformed( ActionEvent bp )
{
System.out.println( "enter pressed" );
textEntry.setText("> ");
textEntry.setCaretPosition(2);
synchronized(inputStringMonitor){
stringReady = true;
inputStringMonitor = textEntry.getText();
inputStringMonitor.notify(); //causes exception
}
}
}
public String waitForInput() throws InterruptedException {
String retval = "";
synchronized(inputStringMonitor){
stringReady = false;
System.out.println("waiting");
while(!stringReady){
inputStringMonitor.wait();
}
retval = inputStringMonitor;
}
return retval;
}
}
I think that I have an idea of what you're trying to do, and if so, I feel that I have a better solution. Correct me if I'm wrong, but I think that you want to create a GUI text entry window that other programs can use, and that notifies other programs when text as been entered. If so, then a better solution is to use a tool that is already present within Swing GUI components -- PropertyChangeSupport. If you want to listen for changes in a String's state, then make the String a "bound" property, one that notifies the GUI if its state ever changes by firing the Swing innate property change method. This way outside classes can register as listeners and be notified of this change.
For instance, the class below extends JPanel, partly because this will give the class a SwingPropertyChangeSupport object as well as add/remove PropertyChangeListener methods, but if you don't want to extend the Swing component, you can easily roll your own by adding your own SwingPropertyChangeSupport object as well as add/remove PropertyChangeListener methods to your class.
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.*;
#SuppressWarnings("serial")
public class CallBackGui extends JPanel {
// public constant for the propertyName
public static final String TEXT_ENTRY = "text entry";
private static final int ROWS = 20;
private static final int COLUMNS = 40;
private static final String CARET_MARKER = "> ";
private JTextArea textEntryArea = new JTextArea(ROWS, COLUMNS);
private String enteredText = ""; // "bound" property
public CallBackGui() {
textEntryArea.setText(CARET_MARKER);
textEntryArea.setWrapStyleWord(true);
textEntryArea.setLineWrap(true);
JScrollPane scrollPane = new JScrollPane(textEntryArea);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
add(scrollPane);
int condition = WHEN_FOCUSED;
InputMap inputMap = textEntryArea.getInputMap(condition);
ActionMap actionMap = textEntryArea.getActionMap();
KeyStroke enterKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
inputMap.put(enterKeyStroke, TEXT_ENTRY);
actionMap.put(TEXT_ENTRY, new TextEntryAction());
}
public String getEnteredText() {
return enteredText;
}
// or can make this private if you wish it to not be changed by outside forces
public void setEnteredText(String enteredText) {
String oldValue = this.enteredText;
String newValue = enteredText;
this.enteredText = enteredText; // change our bound property here
// notify listeners here
firePropertyChange(TEXT_ENTRY, oldValue, newValue);
}
// used by Key Bindings
private class TextEntryAction extends AbstractAction {
#Override
public void actionPerformed(ActionEvent e) {
// call method to set bound poperty
setEnteredText(textEntryArea.getText().substring(CARET_MARKER.length()));
textEntryArea.setText(CARET_MARKER);
}
}
}
Then any outside class that has a reference to the displayed CallBackGui can register a property change listener onto this object and get notification. A very (overly) simple example:
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class TestCallBackGui {
private static void createAndShowGui() {
CallBackGui callBackGui = new CallBackGui();
JFrame frame = new JFrame("CallBackGui");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(callBackGui);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
// add our PropertyChangeListener
callBackGui.addPropertyChangeListener(CallBackGui.TEXT_ENTRY, new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
System.out.println("Text Entered:");
// result held by newValue
System.out.println(evt.getNewValue());
// or can call callBackGui.getEnteredText()
}
});
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
The benefit -- avoidance of all low-level wait/notify/synchronized code, especially the use of this type of code on the Swing event thread, and instead use of safer higher level constructs. Also, since the Swing component actually uses a SwingPropertyChangeSupport object, all call backs will be made on the Swing event thread, an important point if the listening program is also a Swing GUI.
Okay. So here's a solution thanks to Titus
1) Using synchronized() in the callback blocks the EDT, so that's bad. Instead use the invokeLater() to notify asynchronously.
// thread to branch off in order to notify
Runnable doNotify = new Runnable() {
public void run() {
synchronized(inputStringMonitor){
userString = textEntry.getText();
inputStringMonitor.notify();
}
}
};
// callback function
public void actionPerformed( ActionEvent bp )
{
System.out.println( "enter pressed" );
textEntry.setText("> ");
textEntry.setCaretPosition(2);
SwingUtilities.invokeLater(doNotify);
}
2) Assigning to inputStringMonitor re-initializes the lock and messes things up. Instead use a dedicated lock, and a separate string for storing the actual data.
Related
I understand that it is important to use the Event Dispatch Thread for any changes to the interface in Java. However, I have no idea how I can manipulate these events to stop/continue/start. I want to refrain from moving on to the next line of main() (after the ones which put the Runnable in the EventQueue) until a certain key is pressed.
I put together an example for clarity. What I'd like to do here is spawn the JFrame, allow the user to move the box around with the arrow keys and then press Enter to cease the box-shifting operations, and ONLY then make the calculation at the end of main() and cause the answer to appear. I should be able to get 400, 500, 600, etc. As it is, the calculation is made immediately after the JFrame appears, so the answer is always 300.
I carved out a spot for whatever action should be bound to Enter; it's underneath the declarations for the actions bound to the arrow keys.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class EndTheShifter extends JFrame
{
private Color ourRectColor = new Color(28,222,144);
private int ourRectWidth = 50;
private int ourRectHeight = 50;
protected static Point ourRecLocation = new Point(100,100);
// Rectangle object can paint itself
public class Rectangle
{
protected void paint(Graphics2D g2d)
{
g2d.setColor(ourRectColor);
g2d.fillRect(ourRecLocation.x, ourRecLocation.y, ourRectWidth, ourRectHeight);
}
} // Rectangle class
// OurRectangle can create a Rectangle and call paint() on it
public class OurRectangle extends JPanel
{
private Rectangle capableRectangle;
public OurRectangle()
{
capableRectangle = new Rectangle();
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g.create();
capableRectangle.paint(g2d);
g2d.dispose();
}
} // OurRectangle class
KeyStroke pressRight = KeyStroke.getKeyStroke("RIGHT");
KeyStroke pressLeft = KeyStroke.getKeyStroke("LEFT");
KeyStroke pressUp = KeyStroke.getKeyStroke("UP");
KeyStroke pressDown = KeyStroke.getKeyStroke("DOWN");
KeyStroke pressEnter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0);
OurRectangle recToWorkWith = new OurRectangle();
// Create InputMap and ActionMap
InputMap inputMap = recToWorkWith.getInputMap(JPanel.WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = recToWorkWith.getActionMap();
// Mapping Shortcut
protected void setTheAction(KeyStroke a, String b, Action c)
{
inputMap.put(a,b);
actionMap.put(b,c);
}
// Constructor!!!
public EndTheShifter()
{
add(recToWorkWith);
Action rightAction = new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
if(ourRecLocation.x != 600)
ourRecLocation.x += 50;
else
ourRecLocation.x = 100;
recToWorkWith.repaint();
}
};
Action leftAction = new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
if(ourRecLocation.x != 100)
ourRecLocation.x -= 50;
else
ourRecLocation.x = 600;
recToWorkWith.repaint();
}
};
Action downAction = new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
if(ourRecLocation.y != 600)
ourRecLocation.y += 50;
else
ourRecLocation.y = 100;
recToWorkWith.repaint();
}
};
Action upAction = new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
if(ourRecLocation.y != 100)
ourRecLocation.y -= 50;
else
ourRecLocation.y = 600;
recToWorkWith.repaint();
}
};
/*
Action enterAction = new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
}
}
setTheAction(pressEnter,"enterAction",enterAction);
*/
setTheAction(pressRight,"rightAction",rightAction);
setTheAction(pressLeft,"leftAction",leftAction);
setTheAction(pressDown,"downAction",downAction);
setTheAction(pressUp,"upAction",upAction);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(800,800);
setVisible(true);
}
// Main kicks things off by putting all of the above
// in the Event Dispatch thread
// On an enter press, I want the last line of main() to run
public static void main(String[] argv)
{
EventQueue.invokeLater(
new Runnable()
{
#Override
public void run()
{
new EndTheShifter();
}
});
// What I want to trigger only on Enter
System.out.println(ourRecLocation.x + 2*ourRecLocation.y);
}
} // EndTheShifter, our outermost class
and ONLY then make the calculation at the end of main()
That is not the way a GUI works.
The main() method is only used to display the frame.
Once the frame is visible the EDT is started and the frame sits there waiting for user events to be generated.
Your application code then responds to these user events.
I understand that it is important to use the Event Dispatch Thread for any changes to the interface in Java.
All code invoked in a listener does execute on the EDT. So the code in your Action does execute on the EDT. You don't need to do anything special.
What I want to trigger only on Enter
Then that logic should be contained in the Enter Action.
I would like to support what camickr said; there is likely a better way to achieve what you are trying to do. That said, if you really want to make your main method wait until the enter key is pressed, here's how:
First, at the top of your file, define an object to use as a synchronization lock like so:
public static final Object LOCK = new Object();
Then, in your main method, before your println statement, put the following code:
synchronized (LOCK) {
LOCK.wait();
}
What this does is it waits until the LOCK object's monitor lock is not being used by any thread (very simplified explanation, read more here), and then it makes the current thread (in this case, the thread that started your main method) wait indefinitely.
Next, add a throws declaration to the method header on your main method:
public static void main(String[] argv) throws InterruptedException
This tells the compiler that your code could throw an InterruptedException, which would happen if your thread was interrupted while it was waiting.
Finally, anywhere in your EndTheShifter constructor, put the following code:
synchronized (LOCK) {
LOCK.notify();
}
This again waits until the LOCK object's monitor lock becomes available, and it then "notifies" all threads waiting on the LOCK object that they may continue. In this case, it will make our main thread continue and execute the println.
Below is the compiled program replica of actual problem code,
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
public class Dummy {
public static boolean getUserCheck(int size, boolean Check) {
if (Check) {
int ret = JOptionPane.showConfirmDialog(null, size + " entries, Yes or no?",
"Warning", 0);
if (ret > 0) {
System.out.println("User said No: " + ret);
return false;
} else if (ret <= 0) {
System.out.println("user said Yes: " + ret);
return true;
}
}
return true;
}
public static void workerMethod1() {
System.out.println("am worker method 1");
}
public static void workerMethod2() {
System.out.println("am worker method 2");
}
public static void main(String[] args) {
System.out.println("mainthread code line 1");
int size = 13;
boolean thresholdBreach = true;
if (getUserCheck(size, thresholdBreach)) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
workerMethod1();
}
});
SwingUtilities.invokeLater(new Runnable() {
public void run() {
workerMethod2();
}
});
}
System.out.println("mainthread code line 2");
System.out.println("mainthread code line 3");
}
}
where i would like to run the if{} block in main() on separate thread. Because these 2 lines,
System.out.println("mainthread code line 2");
System.out.println("mainthread code line 3");
need not wait for completion of if(){} block
Another problem is, experts recommend to run confirm-dialog methods on event thread.
int ret = JOptionPane.showConfirmDialog(null, size + " entries, Yes or no?",
"Warning", 0);
Please help me!!!!
JOptionPane is a Swing method and should be called on the EDT, the Event Dispatch Thread, and only on this thread, and so it suggests that all your code above should be on the EDT, and that most of your SwingUtilities.invokeLater(new Runnable() calls are completely unnecessary. The only necessary ones will be the main one, where you launch your Swing GUI code, and any areas where Swing calls need to be made from within background threads. Again, if any of the above code is being made within background threads, then the JOptionPane should not be in that thread.
For more specific information in this or any other answer, please provide more specific information in your question. Let's end all confusion. The best way to get us to fully and quickly understand your problem would be if you were to to create and post a minimal example program, a small but complete program that only has necessary code to demonstrate your problem, that we can copy, paste, compile and run without modification.
I have a sneaking suspicion that a decent refactoring along MVC lines could solve most of your problems. Your code is very linear with its lines of code that must follow one another and its if blocks, and it is also tightly coupled with your GUI, two red flags for me. Perhaps better would be less linear code, more event and state-driven code, code where your background code interacts with the GUI via observer notification, and where the background code likewise responds to state changes in the GUI from control notification.
Your control needs two SwingWorkers, one to get the row count and the other to get the rest of the data if the user decides to do so. I'd add a PropertyChangeListener to the first SwingWorker to be notified when the row count data is ready, and then once it is, present it to the view for the user to select whether or not to proceed. If he decides to proceed, I'd then call the 2nd SwingWorker to get the main body of the data.
For example, a rough sketch of what I'm talking about:
import java.awt.Dialog.ModalityType;
import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import javax.swing.*;
#SuppressWarnings("serial")
public class SwingWorkerFooView extends JPanel {
private static final int PREF_W = 400;
private static final int PREF_H = 300;
private JProgressBar progressBar;
private JDialog dialog;
public SwingWorkerFooView() {
add(new JButton(new ButtonAction("Foo", this)));
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
public boolean showOptionGetAllData(int numberOfRows) {
String message = "Number of rows = " + numberOfRows + ". Get all of the data?";
String title = "Get All Of Data?";
int optionType = JOptionPane.YES_NO_OPTION;
int result = JOptionPane.showConfirmDialog(this, message, title, optionType);
return result == JOptionPane.YES_OPTION;
}
public void showProgressBarDialog() {
progressBar = new JProgressBar();
progressBar.setIndeterminate(true);
Window window = SwingUtilities.getWindowAncestor(this);
dialog = new JDialog(window, "Hang on", ModalityType.APPLICATION_MODAL);
JPanel panel = new JPanel();
panel.add(progressBar);
dialog.add(panel);
dialog.pack();
dialog.setLocationRelativeTo(this);
dialog.setVisible(true);
}
public void closeProgressBarDialog() {
dialog.dispose();
}
private static void createAndShowGui() {
JFrame frame = new JFrame("SwingWorkerFoo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new SwingWorkerFooView());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
#SuppressWarnings("serial")
class ButtonAction extends AbstractAction {
Workers workers = new Workers();
private SwingWorker<Integer, Void> firstWorker;
private SwingWorker<List<String>, Void> secondWorker;
private SwingWorkerFooView mainGui;
public ButtonAction(String name, SwingWorkerFooView mainGui) {
super(name);
this.mainGui = mainGui;
}
#Override
public void actionPerformed(ActionEvent e) {
firstWorker = workers.createFirstWorker();
firstWorker.addPropertyChangeListener(new FirstPropertyChangeListener());
firstWorker.execute();
mainGui.showProgressBarDialog();
}
private class FirstPropertyChangeListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
mainGui.closeProgressBarDialog();
try {
int numberOfRows = firstWorker.get();
boolean getAllData = mainGui.showOptionGetAllData(numberOfRows);
if (getAllData) {
secondWorker = workers.createSecondWorker();
secondWorker.addPropertyChangeListener(new SecondPropertyChangeListener());
secondWorker.execute();
mainGui.showProgressBarDialog();
} else {
// user decided not to get all data
workers.cleanUp();
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
private class SecondPropertyChangeListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
mainGui.closeProgressBarDialog();
try {
List<String> finalData = secondWorker.get();
// display finalData in the GUI
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
}
class Workers {
// database object that may be shared by two SwingWorkers
private Object someDataBaseVariable;
private Random random = new Random(); // just for simulation purposes
private class FirstWorker extends SwingWorker<Integer, Void> {
#Override
protected Integer doInBackground() throws Exception {
// The Thread.sleep(...) is not going to be in final production code
// it's just to simulate a long running task
Thread.sleep(4000);
// here we create our database object and check how many rows there are
int rows = random.nextInt(10 + 10); // this is just for demonstration purposes only
// here we create any objects that must be shared by both SwingWorkers
// and they will be saved in a field of Workers
someDataBaseVariable = "Fubar";
return rows;
}
}
private class SecondWorker extends SwingWorker<List<String>, Void> {
#Override
protected List<String> doInBackground() throws Exception {
// The Thread.sleep(...) is not going to be in final production code
// it's just to simulate a long running task
Thread.sleep(4000);
List<String> myList = new ArrayList<>();
// here we go through the database filling the myList collection
return myList;
}
}
public SwingWorker<Integer, Void> createFirstWorker() {
return new FirstWorker();
}
public void cleanUp() {
// TODO clean up any resources and database stuff that will not be used.
}
public SwingWorker<List<String>, Void> createSecondWorker() {
return new SecondWorker();
}
}
The key to all of this is to not to think in a linear console program way but rather to use observer design pattern, i.e., listeners of some sort to check for change of state of both the GUI and the model.
It's essentially:
create worker
add observer to worker (property change listener)
execute worker
show progress bar dialog or notify user in some way that worker is executing.
The listener will be notified when the worker is done, and then you can query the worker (here via the get() method call) as to its end result.
Then the progress dialog can be closed
And the view can display the result or get additional information from the user.
Yes; SwingUtilities.invokeLater() simply places your runnable on the AWT event queue to be processed later, and it is safe to do so at any time.
I have two classes in same package. i have declared a static variable in one class and want to access that variable in another class.
Here is my code in which i have declared the static variable
public class wampusGUI extends javax.swing.JFrame {
static String userCommand;
public wampusGUI() {
initComponents();
}
public void setTextArea(String text) {
displayTextArea.append(text);
}
private void enterButtonActionPerformed(java.awt.event.ActionEvent evt) {
userCommand = commandText.getText();
}
public static void main(String args[]) {
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
wampusGUI w = new wampusGUI();
w.setVisible(true);
Game g = new Game(w);
g.play();
}
});
}
}
Here is the code in which i want to access variable
public class Game {
private wampusGUI gui;
public Game(wampusGUI w) {
world = new World();
world.start();
gui = w;
}
public void play() {
gui.setTextArea(welcome());
gui.setTextArea(describe());
for (;;) {
String s = userCommand; // here value should come should
System.out.println(userCommand);
Command c = Command.create(s);
String r = c.perform(world);
// is game over?
if (r == null) {
break;
}
System.out.println(r);
}
System.out.println("Game over");
}
}
However, i can pass the variable from first class as a argument. but the problem is that, when i will run program the value is going null first time, which i dont want. i want when i enter value in textfield then it should go to another class.
Thank you.
Looking at your code, it seems you want to show dialogs to your user with a certain text
gui.setTextArea(welcome());
gui.setTextArea(describe());
and sometimes, that dialog should capture user input which is handled afterwards.
Those setTextArea calls are not what you want to use. The user will never see the welcome message as it will immediately be replaced by the describe message.
Make sure you do not block the Event Dispatch Thread (EDT) or nothing will be shown at all. I do not know what your Command class will do, but I see an infinite loop on the Event Dispatch Thread which is never a good thing. Take a look at the Concurrency in Swing tutorial for more information
Thanks to that for loop, the user will simply not be capable to input any command as the EDT is busy handling your loop. What you need is a blocking call allowing the user to provide input (not blocking the EDT, but just blocking the execution of your code). The static methods in the JOptionPane class are perfectly suited for this (e.g. the JOptionPane#showInputDialog). These methods also have a mechanism to pass the user input back to the calling code without any static variables, which solves your problem.
I suggest that you use a listener of one sort or another to allow the Game object to listen for and respond to changes in the state of the GUI object. There are several ways to do this, but one of the most elegant and useful I've found is to use Swing's own innate PropertyChangeSupport to allow you to use PropertyChangeListeners. All Swing components will allow you to add a PropertyChangeListener to it. And so I suggest that you do this, that you have Game add one to your WampusGUI class (which should be capitalized) object like so:
public Game(WampusGUI w) {
gui = w;
gui.addPropertyChangeListener(new PropertyChangeListener() {
// ....
}
This will allow Game to listen for changes in the gui's state.
You'll then want to make the gui's userCommand String a "bound property" which means giving it a setter method that will fire the property change support notifying all listeners of change. I would do this like so:
public class WampusGUI extends JFrame {
public static final String USER_COMMAND = "user command";
// ....
private void setUserCommand(String userCommand) {
String oldValue = this.userCommand;
String newValue = userCommand;
this.userCommand = userCommand;
firePropertyChange(USER_COMMAND, oldValue, newValue);
}
Then you would only change this String's value via this setter method:
private void enterButtonActionPerformed(java.awt.event.ActionEvent evt) {
setUserCommand(commandText.getText());
}
The Game's property change listener would then respond like so:
gui.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent pcEvt) {
// is the property being changed the one we're interested in?
if (WampusGUI.USER_COMMAND.equals(pcEvt.getPropertyName())) {
// get user command:
String userCommand = pcEvt.getNewValue().toString();
// then we can do with it what we want
play(userCommand);
}
}
});
One of the beauties of this technique is that the observed class, the GUI, doesn't have to have any knowledge about the observer class (the Game). A small runnable example of this is like so:
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
public class WampusGUI extends JFrame {
public static final String USER_COMMAND = "user command";
private String userCommand;
private JTextArea displayTextArea = new JTextArea(10, 30);
private JTextField commandText = new JTextField(10);
public WampusGUI() {
initComponents();
}
private void setUserCommand(String userCommand) {
String oldValue = this.userCommand;
String newValue = userCommand;
this.userCommand = userCommand;
firePropertyChange(USER_COMMAND, oldValue, newValue);
}
private void initComponents() {
displayTextArea.setEditable(false);
displayTextArea.setFocusable(false);
JButton enterButton = new JButton("Enter Command");
enterButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
enterButtonActionPerformed(evt);
}
});
JPanel commandPanel = new JPanel();
commandPanel.add(commandText);
commandPanel.add(Box.createHorizontalStrut(15));
commandPanel.add(enterButton);
JPanel mainPanel = new JPanel();
mainPanel.setLayout(new BorderLayout());
mainPanel.add(new JScrollPane(displayTextArea));
mainPanel.add(commandPanel, BorderLayout.SOUTH);
add(mainPanel);
}
public void setTextArea(String text) {
displayTextArea.append(text);
}
private void enterButtonActionPerformed(java.awt.event.ActionEvent evt) {
setUserCommand(commandText.getText());
}
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
WampusGUI w = new WampusGUI();
w.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
w.pack();
w.setLocationRelativeTo(null);
w.setVisible(true);
Game g = new Game(w);
g.play();
}
});
}
}
class Game {
private WampusGUI gui;
public Game(WampusGUI w) {
gui = w;
gui.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent pcEvt) {
// is the property being changed the one we're interested in?
if (WampusGUI.USER_COMMAND.equals(pcEvt.getPropertyName())) {
// get user command:
String userCommand = pcEvt.getNewValue().toString();
// then we can do with it what we want
play(userCommand);
}
}
});
}
public void play() {
gui.setTextArea("Welcome!\n");
gui.setTextArea("Please enjoy the game!\n");
}
public void play(String userCommand) {
// here we can do what we want with the String. For instance we can display it in the gui:
gui.setTextArea("User entered: " + userCommand + "\n");
}
}
I agree with Jon Skeet that this is not a good solution...
But in case u want an dirty solution to ur problem then u can try this:
public class wampusGUI extends javax.swing.JFrame
{
private static wampusGUI myInstance;
public wampusGUI( )
{
myInstance = this;
initComponents();
}
public static void getUserCommand()
{
if(myInstance!=null)
{
return myInstance.commandText.getText();
}
else
{
return null;
}
}
......
......
}
in the other class use:
public void play()
{
.....
//String s = userCommand; // here value should come should
String s = wampusGUI.getUserCommand();
.....
}
This kind of code is there in some of our legacy projects... and I hate this.
I have a GUI application that uses an InputVerifier to check the content of text fields before yielding the focus. This is all very normal. Yesterday, however, discovered a problem - it seems to be a bug, but I cannot find any mention of it anywhere. Before I report this as a bug, I thought I would ask: am I missing something obvious here?
Situation:
A set of text fields with InputVerifiers.
Listeners for FocusLost and FocusGained on all controls, so I can see what is happening.
A separate thread uses a DefaultKeyboardFocusManager to report (every 2 seconds) which control has the focus.
I place invalid data in a JTextField in the middle of the form, and try to leave the control.
If I try to move away from this control using the mouse, or using the tab-key, I cannot. The FocusLost event does not fire and the control properly retains the focus.
However, if I try to move away from the control in reverse tab order, using Shift-Tab, sometimes the FocusLost event fires. If this happens, the separate thread reports that no control has the focus, i.e., getFocusOwner() returns null.
Edit: below is a small sample program that shows the problem. The problem has nothing to do with the extra thread - the thread is just there to make the problem more obvious. If there is a race-condition, it is somewhere in Swing.
To see the problem, go to the second text box and empty it. The control should retain the focus, and does so unless you leave it by pressing shift-tab. Unlike the full application, the error seems to occur here 100% of the time. This is true both under OpenJDK 6 and under Oracle Java 7.
This is almost too obvious to be a bug, plus it happens in multiple Java environments. Hence, my suspicion that I am missing something obvious. Anyone?
public class FocusBugDemo extends JFrame {
static JTextField txtOne = new JTextField("Ignore this control");
static JTextField txtTwo = new JTextField("Delete this text, then press shift-tab");
static JLabel lblFocus = new JLabel("");
static KeyboardFocusManager kfm = new DefaultKeyboardFocusManager();
public static void main(String[] args) {
new FocusBugDemo();
Thread t = new Thread() {
#Override
public void run() {
while(true) {
Component c = kfm.getFocusOwner();
String focusInfo = "elsewhere";
if (c == null) { focusInfo = "null";
} else if (c == txtOne) { focusInfo = "txtOne";
} else if (c == txtTwo) { focusInfo = "txtTwo";
}
lblFocus.setText(System.currentTimeMillis() + " - Focus owner " + focusInfo);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
}
};
t.start();
}
private FocusBugDemo() {
super("Focus bug demo");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setPreferredSize(new Dimension(300,100));
setLayout(new GridLayout(3,1));
NotEmpty validator = new NotEmpty();
txtOne.setInputVerifier(validator);
txtTwo.setInputVerifier(validator);
add(txtOne);
add(txtTwo);
add(lblFocus);
pack();
setVisible(true);
}
private class NotEmpty extends InputVerifier {
#Override
public boolean verify(JComponent input) {
JTextField txtField = (JTextField) input;
return (txtField.getText().length() > 0);
}
}
}
Now reported to Oracle as bug 7167871.
Using your sscce, I am unable to reproduce the effect you describe on Mac OS X, Java 6, which supports #CatalinaIsland's observation. In particular, focus never leaves an empty text field using either tab or shift-tab; focus becomes null only when the frame is deactivated.
I see two threads accessing multiple fields with no synchronization at all. At a minimum, you should use EventQueue.invokeLater() in t to update the GUI, as described in Concurrency in Swing and show below.
The broader question is this: What focus problem are you trying to solve using t?
import java.awt.Component;
import java.awt.DefaultKeyboardFocusManager;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.KeyboardFocusManager;
import javax.swing.InputVerifier;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;
public class FocusDemo {
private static final JTextField txtOne =
new JTextField("Ignore this control");
private static final JTextField txtTwo =
new JTextField("Delete this text, then press shift-tab");
private static final JLabel lblFocus = new JLabel("");
public static void main(String[] args) {
new FocusDemo();
Thread t = new Thread() {
#Override
public void run() {
while (true) {
EventQueue.invokeLater(new Runnable() {
KeyboardFocusManager kfm =
new DefaultKeyboardFocusManager();
#Override
public void run() {
Component c = kfm.getFocusOwner();
String focusInfo = "elsewhere";
if (c == null) {
focusInfo = "null";
} else if (c == txtOne) {
focusInfo = "txtOne";
} else if (c == txtTwo) {
focusInfo = "txtTwo";
}
lblFocus.setText(System.currentTimeMillis()
+ " - Focus owner " + focusInfo);
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace(System.err);
}
}
}
};
t.start();
}
private FocusDemo() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame f = new JFrame("Focus bug demo");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setPreferredSize(new Dimension(300, 100));
f.setLayout(new GridLayout(3, 1));
NotEmpty validator = new NotEmpty();
txtOne.setInputVerifier(validator);
txtTwo.setInputVerifier(validator);
f.add(txtOne);
f.add(txtTwo);
f.add(lblFocus);
f.pack();
f.setVisible(true);
}
});
}
private class NotEmpty extends InputVerifier {
#Override
public boolean verify(JComponent input) {
JTextField txtField = (JTextField) input;
return (txtField.getText().length() > 0);
}
}
}
I am working on an assignment, and I need to enter an SQL Query in a textfield. The user can either press the custom 'execute query' button, or they can press the enter key. When either of these are used, it is to trigger an ActionListener (no other listener is allowed). Is it as simple as writing:
if (e.getSource()=='querybutton' || e.getSource=='enter')
Or is there more to it than this?
As I said, it is a simple question (I know).
edit:
I would write this bit in my ActionPerformed as:
public void actionPerformed(ActionEvent e)
{
if(e.getSource()==gui.executeQueryButton || e.getSource()==gui.enter)
{
String query = gui.queryText.getText();
//more code to follow
}
}
e.getSource() actually returns the object responsible for firing the event (not the name of the variable you used when creating the control). In this case, your button. You could in principle compare e.getSource() with the actual button instances. However, are you actually adding this action listener to buttons other than those two? Presumably you'd only have to add this listener to the two buttons for which you want this behavior -- in which case you wouldn't have to have this if check.
" Is it as simple as writing:
if (e.getSource()=='querybutton' || e.getSource=='enter')"
It's not simple to write this, but rather it is wrong to write it.
For one you don't want to compare Strings with ==, for another, you don't declare Strings with single quotes, and for a third, the enter key is not obtained in this way, but rather by adding the appropriate ActionListener object to the JTextField itself, and finally there should be in a single ActionListener class that handles this action, so the if block is completely unnecessary. This can probably be best done with a small inner private ActionListener class. You'd then create one object of this class and add it as an ActionListener for the querybutton and for the JTextField.
edit 1:
A more complete example of what I mean is shown below, a demo class that has a private inner handler class:
import java.awt.event.*;
import javax.swing.*;
public class ActionListenerEg extends JPanel {
private JButton queryButton = new JButton("Query");
private JTextField textField = new JTextField("hello", 20);
public ActionListenerEg() {
QueryListener qListener = new QueryListener();
queryButton.addActionListener(qListener);
textField.addActionListener(qListener);
add(queryButton);
add(textField);
}
private class QueryListener implements ActionListener {
public void actionPerformed(ActionEvent arg0) {
String textInField = textField.getText();
System.out.println("Use text in field, \"" + textInField + "\" to call SQL query in a background SwingWorker thread.");
}
}
private static void createAndShowUI() {
JFrame frame = new JFrame("ActionListenerEg");
frame.getContentPane().add(new ActionListenerEg());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
createAndShowUI();
}
});
}
}
The ActionListener is fired either by pressing the button or by pressing enter from within the JTextField. I'd then have in my control class, code that is called inside of the actinoPerformed method.
edit 2: Having most handler or "control" code in its own Handler or Control class can be a good idea, but it doesn't have to implement ActionListener interface itself, but rather just have the code that will be called from within the ActionListener codes. For example, here I try to put all the handler code in its own class. It will have different methods that are called for various situations. e.g.,
import java.awt.Component;
import java.awt.event.*;
import javax.swing.*;
public class ActionListenerEg extends JPanel {
private ActionListenerHandler handler;
private JButton queryButton = new JButton("Query");
private JButton displayButton = new JButton("Display");
private JTextField textField = new JTextField("hello", 20);
// pass in handler or handler
public ActionListenerEg(final ActionListenerHandler handler) {
this.handler = handler;
QueryListener qListener = new QueryListener();
queryButton.addActionListener(qListener);
textField.addActionListener(qListener);
displayButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (handler != null) {
handler.displayActionPerformed(e);
}
}
});
add(queryButton);
add(textField);
add(displayButton);
}
private class QueryListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
if (handler != null) {
String textInField = textField.getText();
handler.doQueryAction(e, textInField);
}
}
}
private static void createAndShowUI() {
ActionListenerHandler handler = new ActionListenerHandler();
JFrame frame = new JFrame("ActionListenerEg");
frame.getContentPane().add(new ActionListenerEg(handler));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
createAndShowUI();
}
});
}
}
class ActionListenerHandler {
public void displayActionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog((Component) e.getSource(), "Display things!");
}
public void doQueryAction(ActionEvent e, String textInField) {
String text = "We will use \"" + textInField + "\" to help create and run the SQL Query";
JOptionPane.showMessageDialog((Component) e.getSource(), text);
}
}
Please ask questions if it's clear as mudd, or if anything is wrong.