java从另一个线程更新UI组件

时间:2014-10-21 08:32:11

标签: java multithreading user-interface elements invokelater

我找到了许多关于我的问题的答案,但我仍然不明白为什么我的申请不会引起任何例外。 我在NetBeans 8中创建了一个新的Java表单应用程序。 我的表单在主方法中创建并显示如下:

public static void main(String args[])
    {
        /* Set the Nimbus look and feel */
        //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
        /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
         * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html 
         */
        try
        {
            for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels())
            {
                if ("Nimbus".equals(info.getName()))
                {
                    javax.swing.UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        }
        catch (ClassNotFoundException ex)
        {
            java.util.logging.Logger.getLogger(MainForm.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        catch (InstantiationException ex)
        {
            java.util.logging.Logger.getLogger(MainForm.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        catch (IllegalAccessException ex)
        {
            java.util.logging.Logger.getLogger(MainForm.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        catch (javax.swing.UnsupportedLookAndFeelException ex)
        {
            java.util.logging.Logger.getLogger(MainForm.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        //</editor-fold>

        /* Create and display the form */
        java.awt.EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                new MainForm().setVisible(true);     
            }
        });
    }

因此,这个新的Runnable创建了新的MainForm并将其设置为可见。

然后,在我的代码中,我启动了新的线程来更新一些jButtons和jTextFields。代码如下:

private void updateUI() {
        updateUIThread = new Thread(() ->
        { 
            while (true) {
                try {
                    jtfIP.setEnabled(!Start && !autoRec);
                    jtfPort.setEnabled(!Start && !autoRec);
                    jtfSlaveID.setEnabled(!Start && !autoRec);
                    jtfTimeout.setEnabled(!Start && !autoRec);
                    jtfReqInterval.setEnabled(!Start && !autoRec);
                    jCheckBox1.setEnabled(!Start && !autoRec);
                    jCBReconnect.setEnabled(!Start && !autoRec);

                    if (db != null) {
                        if (!db.getIsOpen()) {
                            jPBD.setBackground(Color.RED);
                            jPBD.setForeground(Color.WHITE);
                            jPBD.setText("ER");
                        } else {
                            jPBD.setBackground(Color.GREEN);
                            jPBD.setForeground(Color.BLACK);
                            jPBD.setText("OK ");
                        }
                    } else {
                        jPBD.setBackground(Color.RED);
                        jPBD.setForeground(Color.WHITE);
                        jPBD.setText(" ER ");
                    }


                    if (autoRec){
                        jbtnConnect.setText("Auto");
                        if (Start && Connected) {
                            jbtnConnect.setForeground(Color.BLACK);
                            jbtnConnect.setBackground(Color.GREEN);
                        } else {       
                            jbtnConnect.setForeground(Color.WHITE);
                            jbtnConnect.setBackground(Color.RED);
                        }
                    } else {
                        if (Start) {
                            jbtnConnect.setText("Disconnect");
                            jbtnConnect.setForeground(Color.BLACK);
                            jbtnConnect.setBackground(Color.GREEN);

                        } else {
                            jbtnConnect.setText("Connect");
                            jbtnConnect.setForeground(Color.WHITE);
                            jbtnConnect.setBackground(Color.RED);
                        }
                    }

                    jtfErroriCitire.setText(String.valueOf(totalErrors));

                    try
                    {
                        Thread.sleep(300);
                        jPanel4.repaint(1);
                    }
                    catch (InterruptedException ex)
                    {
                        Logger.getLogger(MainForm.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
                catch (Exception ex) {
                    Logger.getLogger(MainForm.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        });
        updateUIThread.start();
    }

还有其他线程像上面这样开始,我得到的值不同,在上面的帖子中有更新。

我的问题是为什么我的代码不会抛出有关从另一个线程更新的UI元素的任何异常?我没有使用SwingUtilities.invokeLater(new Runnable() { //code here }); 我的代码执行得很完美......

谢谢!

3 个答案:

答案 0 :(得分:4)

Swing不是线程安全的并且是单线程的。您永远不应该从事件调度线程外部更新UI组件,同样,您不应该在EDT中运行长时间运行的进程或阻止代码,因为这会阻止它处理事件队列中的新事件,从而导致您的应用看起来像它挂...因为它有......

请查看Concurrency in Swing了解详情。

在我搔了一会儿之后,我意识到,简单的解决方案就是使用javax.swing.Timer

您希望以固定间隔(300毫秒)重复更新并更新UI,完美,Swing Timer能够定期更新更新并在EDT上下文中执行回调!

它还具有合并重复呼叫的能力。这意味着,如果事件队列中已经存在“计时器”操作,则计时器将不会生成新的计时器,从而防止充斥EDT并导致可能的性能问题......

javax.swing.Timer timer = new Timer(300, new ActionListener() {
    public void actionPerformed(ActionEvent evt) {    
        jtfIP.setEnabled(!Start && !autoRec);
        jtfPort.setEnabled(!Start && !autoRec);
        jtfSlaveID.setEnabled(!Start && !autoRec);
        jtfTimeout.setEnabled(!Start && !autoRec);
        jtfReqInterval.setEnabled(!Start && !autoRec);
        jCheckBox1.setEnabled(!Start && !autoRec);
        jCBReconnect.setEnabled(!Start && !autoRec);

        if (db != null) {
            if (!db.getIsOpen()) {
                jPBD.setBackground(Color.RED);
                jPBD.setForeground(Color.WHITE);
                jPBD.setText("ER");
            } else {
                jPBD.setBackground(Color.GREEN);
                jPBD.setForeground(Color.BLACK);
                jPBD.setText("OK ");
            }
        } else {
            jPBD.setBackground(Color.RED);
            jPBD.setForeground(Color.WHITE);
            jPBD.setText(" ER ");
        }


        if (autoRec){
            jbtnConnect.setText("Auto");
            if (Start && Connected) {
                jbtnConnect.setForeground(Color.BLACK);
                jbtnConnect.setBackground(Color.GREEN);
            } else {       
                jbtnConnect.setForeground(Color.WHITE);
                jbtnConnect.setBackground(Color.RED);
            }
        } else {
            if (Start) {
                jbtnConnect.setText("Disconnect");
                jbtnConnect.setForeground(Color.BLACK);
                jbtnConnect.setBackground(Color.GREEN);

            } else {
                jbtnConnect.setText("Connect");
                jbtnConnect.setForeground(Color.WHITE);
                jbtnConnect.setBackground(Color.RED);
            }
        }

        jtfErroriCitire.setText(String.valueOf(totalErrors));
    }
});
timer.start();

有关详细信息,请参阅How to use Swing Timers

答案 1 :(得分:1)

Swing说你不应该从Swing Event Dispatch Thread外面更新组件,但它并没有强制执行。检查每个呼叫来自哪个线程都是不切实际的。

此外,由于线程问题(通常只是)通常会导致问题的性质,因此当您遇到多线程代码中的错误时,不应该总是抛出异常。这是因为线程问题经常导致deadlockmemory consistency errors,这在大多数情况下是不可恢复的(通常整个JVM都会崩溃)。

答案 2 :(得分:0)

我这样做了。

private void updateUI() {
    updateUIThread = new Thread(() ->
    { 
        while (true) {
            try {
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        jtfIP.setEnabled(!Start && !autoRec);
                        jtfPort.setEnabled(!Start && !autoRec);
                        jtfSlaveID.setEnabled(!Start && !autoRec);
                        jtfTimeout.setEnabled(!Start && !autoRec);
                        jtfReqInterval.setEnabled(!Start && !autoRec);
                        jCheckBox1.setEnabled(!Start && !autoRec);
                        jCBReconnect.setEnabled(!Start && !autoRec);

                        if (db != null) {
                            if (!db.getIsOpen()) {
                                jPBD.setBackground(Color.RED);
                                jPBD.setForeground(Color.WHITE);
                                jPBD.setText("ER");
                            } else {
                                jPBD.setBackground(Color.GREEN);
                                jPBD.setForeground(Color.BLACK);
                                jPBD.setText("OK ");
                            }
                        } else {
                            jPBD.setBackground(Color.RED);
                            jPBD.setForeground(Color.WHITE);
                            jPBD.setText(" ER ");
                        }


                        if (autoRec){
                            jbtnConnect.setText("Auto");
                            if (Start && Connected) {
                                jbtnConnect.setForeground(Color.BLACK);
                                jbtnConnect.setBackground(Color.GREEN);
                            } else {       
                                jbtnConnect.setForeground(Color.WHITE);
                                jbtnConnect.setBackground(Color.RED);
                            }
                        } else {
                            if (Start) {
                                jbtnConnect.setText("Disconnect");
                                jbtnConnect.setForeground(Color.BLACK);
                                jbtnConnect.setBackground(Color.GREEN);

                            } else {
                                jbtnConnect.setText("Connect");
                                jbtnConnect.setForeground(Color.WHITE);
                                jbtnConnect.setBackground(Color.RED);
                            }
                        }

                        jtfErroriCitire.setText(String.valueOf(totalErrors));
                    }
                });
                try
                {
                    Thread.sleep(300);
                    jPanel4.repaint(1);
                }
                catch (InterruptedException ex)
                {
                    Logger.getLogger(MainForm.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
            catch (Exception ex) {
                Logger.getLogger(MainForm.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    });
    updateUIThread.start();
}

我将更新的UI代码放在invokeLater的run方法中。 updateUI()在应用程序启动时调用它。