【问题标题】:Why does my GUI window freeze whenever ActionListener is performed?为什么每次执行 ActionListener 时我的 GUI 窗口都会冻结?
【发布时间】:2014-11-01 23:27:39
【问题描述】:

每当单击 GUI 窗口上的按钮并且服务器接收到消息并将其发送到连接到同一端口的另一个客户端客户端 2 时,我都会通过客户端 1 向服务器发送一条字符串消息“5”作为客户端 1。当单击按钮时,客户端 2 确实收到字符串消息“5”,但问题是一旦单击按钮,客户端 1 的整个 GUI 窗口就会冻结(无法单击任何事物)。客户端 2 也是如此,它执行相同的操作,即在单击按钮时发送字符串消息“2”,但在单击按钮时 GUI 窗口也会冻结。为什么 GUI 窗口冻结?

代码sn-p:

BufferedReader buffR;
Socket sock = new Socket(nameHost, portNum); 
PrintWriter printW = new PrintWriter(sock.getOutputStream()); 
this.buffR = new BufferedReader(new InputStreamReader(sock.getInputStream()));

button.addActionListener(new ActionListener(){
    public void actionPerformed(ActionEvent ae){
        printW.println("5");
        printW.flush(); 
    }
});

已编辑 - 附加代码片段(接收部分): 这是线程类中的一个可运行方法以及上面的代码 sn-p。

public void run() { 

    try{
        while(true){

            String line = br.readLine(); 
            int update = Integer.parseInt(line);
            current-= update; //int current = 0 and update is the number received from the other client, which is "5"

            JLabel newNumber = new JLabel(current); //Assign the newNumber JLabel with the current number

            jp.remove(number); //Remove the JLabel 'number' which initially shows "50" 
            jp.revalidate();
            jp.repaint();
            jp.add(newNumber); //Replace the JLabel 'number' with 'newNumber'

            if(update == 0){
                JOptionPane.showMessageDialog(jf, "Reached 0", "Message", JOptionPane.INFORMATION_MESSAGE);
                System.exit(0);
        }
    }

    } catch (IOException ioe){
        System.out.println("ioe in ChatClient.run: " + ioe.getMessage());
    }
}

【问题讨论】:

  • 你认为 while (true) 循环有什么作用?什么时候停止?
  • 你到底需要多少个动作监听器?
  • @JBNizet 我想让两个客户端继续通过按下按钮发送消息来相互交互,所以我把它作为一个 while 循环。我怎样才能以交互持续到终止的方式修复它?
  • @BoristheSpider 只有两个,但我怎样才能修复它,使两个客户端之间的交互持续到它们的任何一个窗口终止?
  • @user3466314:是的,您需要一个循环来从 PrintWriter 信息中获取信息,但不要不断添加 ActionListener,因为这种方式很疯狂。我从循环中的 BufferedReader 读取,然后在 GUI 中显示结果(但注意以线程安全的方式显示字符串)。我会使用 SwingWorker 来帮助我做到这一点。

标签: java multithreading swing user-interface networking


【解决方案1】:

您的while (true) 正在 Swing 事件线程上被调用,从而占用了线程并阻止它执行绘制 GUI 和与用户交互的操作。此外,您要多次添加 ActionListener,这几乎没有意义,因为按下按钮时将调用每个 ActionListener。

解决办法:

  1. 摆脱不必要且危险的 while 循环。
  2. 阅读Swing Threading,然后努力遵守规则。

请注意,如果您需要连续读取 BufferedReader 对象,那么可以,将读取的内容放在 while (true) 循环中,但不要添加任何 ActionListener 或对 Swing 对象的状态进行任何更改从循环内部。相反,我会使用 SwingWorker 对象,将我的 while (true) 循环在其 doInBackground() 方法中,以便它确保在后台线程中运行,然后我会使用 SwingWorker 的发布更新 GUI 的显示/处理方法对,以便以线程安全的方式将字符串推送到 GUI 中。我上面给你的链接将帮助你实现这一目标。


编辑
您的代码可能类似于:

设置按钮的 ActionListener

  // add this ActionListener once and only once
  button.addActionListener(new ActionListener() {
     @Override
     public void actionPerformed(ActionEvent e) {
        // get the line of text from the GUI
        String text = myTextField.getText();

        // send the text out to the socket via the PrintWriter
        printW.println(text);
     }
  });

请注意,最好使用 AbstractAction,然后使用同一个对象设置 JButton 和 JTextArea 的 Action。

事情的接受方面将涉及使用 SwingWorker 来允许我们使用阻塞代码buffR.readLine(); 而不会阻塞 GUI。请注意,doInBackground() 方法中的所有代码都是在后台线程中完成的,并且在 Swing 事件线程、EDT(事件调度线程)之外完成:

  // use a <Void, String> SwingWorker since I want to publish a String
  new SwingWorker<Void, String>() {
     protected Void doInBackground() throws Exception {
        while (true) {

           // read in a String from the BufferedReader in background thread
           String line = buffR.readLine();

           // publish String so it can be used on Swing event thread, the EDT 
           publish(line);
        }            
     };

     // this code is called in the Swing event thread, the EDT
     protected void process(java.util.List<String> chunks) {
        for (String line : chunks) {
           // display text in my JTextArea
           myTextArea.append(line + "\n");
        }
     };
  }.execute();

【讨论】:

  • 嗯,我只希望他们在按下按钮时发送消息,但同时,我希望他们能够通过使用按钮发送消息来相互交互.当第一次按下按钮时,它会与另一个客户端交互并反映在另一个客户端的 GUI 上,但是一旦执行,按下按钮的窗口就会冻结。如果我摆脱了 while 循环,他们将无法在窗口未终止的情况下不断地相互交互?
  • @user3466314:见编辑回答。我可以告诉你,你只会通过尝试在循环内重复添加一个 ActionListener 来实现疯狂。只是不要这样做。
  • 我检查了它,但我不明白 actionListener 如何允许用户继续按下按钮将消息“5”发送到服务器而不进入循环。似乎提供的那个只允许用户按一次按钮来发送消息。
  • @user3466314:您似乎误解了 ActionListeners 和事件驱动编程的工作原理。当您将 ActionListener 附加到 JButton 时,每次按下按钮时都会调用侦听器代码。将其放入循环不会多次调用监听器代码,而是向按钮添加了一堆不必要的监听器。
  • 啊,明白了!所以我只是从我在原始帖子中提供的代码中取出了 while(true) 循环,并启动了客户端 1 和客户端 2,并将它们都连接到服务器。我按下客户端 1 上的按钮,它发送消息“5”,它反映在客户端 2 的窗口上,客户端 1 的 GUI 窗口页面没有冻结,按钮看起来是可按下的,但它不会发送字符串“5”的消息了。现在可能是什么问题?
猜你喜欢
  • 2021-10-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-20
相关资源
最近更新 更多