简体   繁体   English

从事件回调通知时发生Java Swing异常

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM