[英]Java Swing exception when notifying from an event callback
So I have a GUI which acts kind of like a console. 因此,我有一个类似于控制台的GUI。 I want the user to enter text into a JTextField and press enter.
我希望用户将文本输入JTextField并按Enter。 I've used key bindings to make a callback for when the user presses enter.
我使用键绑定为用户按下Enter时进行回调。
Now I want to create a method called waitForInput() which waits for the user to enter something and returns it. 现在,我想创建一个名为waitForInput()的方法,该方法等待用户输入内容并将其返回。 What I'm trying is below.
我正在尝试的是下面。 But it results in a java.lang.IllegalMonitorStateException when notify() is called in the callback function.
但是,当在回调函数中调用notify()时,会导致java.lang.IllegalMonitorStateException。
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.
如果我错了,请纠正我,但是我认为您想创建一个GUI文本输入窗口,其他程序可以使用该窗口,并在输入文本时通知其他程序。 If so, then a better solution is to use a tool that is already present within Swing GUI components -- PropertyChangeSupport.
如果是这样,那么更好的解决方案是使用Swing GUI组件中已经存在的工具-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.
如果要侦听String状态的更改,则将String设置为“绑定”属性,该属性会通过触发Swing固有属性change方法来通知GUI状态是否发生更改。 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. 例如,下面的类扩展了JPanel,部分原因是这将为该类提供一个SwingPropertyChangeSupport对象以及添加/删除PropertyChangeListener方法,但是如果您不想扩展Swing组件,则可以通过添加自己的对象来轻松滚动自己的对象。自己的SwingPropertyChangeSupport对象以及向类添加/删除PropertyChangeListener方法。
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. 然后,任何引用了所显示CallBackGui的外部类都可以在该对象上注册一个属性更改侦听器,并获取通知。 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. 好处-避免所有低层的等待/通知/同步代码,尤其是在Swing事件线程上使用这种类型的代码,而不是使用更安全的高层构造。 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.
另外,由于Swing组件实际上使用了SwingPropertyChangeSupport对象,因此所有回调都将在Swing事件线程上进行,如果侦听程序也是Swing GUI,则这一点很重要。
Okay. 好的。 So here's a solution thanks to Titus
因此,感谢Titus,这是一个解决方案
1) Using synchronized() in the callback blocks the EDT, so that's bad. 1)在回调中使用synced()会阻止EDT,所以很糟糕。 Instead use the invokeLater() to notify asynchronously.
而是使用invokeLater()异步通知。
// 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. 2)分配给inputStringMonitor会重新初始化锁并将事情弄糟。 Instead use a dedicated lock, and a separate string for storing the actual data.
而是使用专用锁和单独的字符串来存储实际数据。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.