从另一个线程实时更新 JLabel 文本

Updating JLabel text from another thread in real-time

我需要创建一个 GUI 来显示来自串口的实时数据。我正在从一个单独的线程读取串行端口数据,我需要从那里更新 GUI。我现在的实现是这样的。

class Gui extends JFrame {
    private JLabel lbl = new JLabel();
    ....
    void updateLabel(String text) {
        lbl.setText(text);
    }
}

class CommPortReceiver extends Thread {
    private Gui gui = new Gui();

    void run() {
        gui.setVisible(true);
        ....
        while (true) {  
            if (dataAvailable) {    
                ....          
                gui.updateLabel(data);
                sleep(10);
            }
        }
    }
}

我每秒接收大约 10 个值,我希望 Swing 可以处理。我的问题是 JLabel 没有实时更新,它在显示最新数据时遗漏了一些数据。我该如何解决这个问题?

class Gui extends JFrame {
    private JLabel lbl = new JLabel();
    ....
    void updateLabel(String text) {
        SwingUtilities.invokeLater(new Runnable() {lbl.setText(text); });
        lbl.repaint();
    }
}

class CommPortReceiver extends Thread {
    private Gui gui = new Gui();

    void run() {
        gui.setVisible(true);
        ....
        while (true) {  
            if (dataAvailable) {    
                ....          
                gui.updateLabel(data);
                sleep(10);
            }
        }
    }
}

您可以实施线程安全模型,封装视图所需的数据。模型应由来自串口的信息更新(由 Worker class 表示)。
视图应该监听模型的变化和更新。

以下代码实现了模型-视图-控制器模式。它是一个文件 SSCCE :它可以复制粘贴到 ViewUpdatedByThread.java 和 运行.
观点就是这样。它使用 Observer 接口监听 Model 的变化。
模型封装了视图所需的信息(在本例中只是一个双精度值)。它允许线程安全地更新值,并在信息更改时通知观察者(视图)。
Worker class 使用线程更改 Model 中的信息。
Controller 编排各种成员:初始化它们,并将视图链接到模型:

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridBagLayout;
import java.util.Collections;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class ViewUpdatedByThread {

    public static void main(String[] args) {
        new Controller();
    }
}

//Controller of the MVC pattern."wires" model and view (and in this case also worker)
class Controller{

    public Controller() {

        Model model = new Model();
        View view = new View(model);
        model.registerObserver(view); //register view as an observer to model

        Worker worker = new Worker(model);

        view.getStopBtn().addActionListener(e -> worker.cancel());
    }
}

//view of the MVC pattern. Implements observer to respond to model changes
class View implements Observer{

    private final Model model;
    private final DataPane pane;
    private final JButton stopBtn;

    public View(Model model) {

        this.model = model;
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        pane = new DataPane();
        frame.add(pane, BorderLayout.CENTER);

        stopBtn = new JButton("Stop");
        frame.add(stopBtn, BorderLayout.SOUTH);

        frame.pack();
        frame.setVisible(true);
    }

    JButton getStopBtn()  { return stopBtn; }

    @Override
    public void onObservableChanged() { //update text in response to change in model
        pane.setText(String.format("%.2f",model.getValue()));
    }

    class DataPane extends JPanel {

        private final JLabel label;

        DataPane() {
            setPreferredSize(new Dimension(200, 100));
            setLayout(new GridBagLayout());
            label = new JLabel(" ");
            add(label);
        }

        void setText(String text){  label.setText(text); }
    }
}

//Model of the MVC pattern. Holds the information view needs
//Notifies observers (in this case View) when model changes
class Model { //you can make it generic Model<T>

    //the value that needs to be updated
    private Double value = 0.;

    // thread safe set for observers
    private final Set<Observer> mObservers = Collections.newSetFromMap(
                                        new ConcurrentHashMap<Observer, Boolean>(0));
    Model() {}

    //set all elements to value
    void changeValue(Double value){
        this.value = value;
        notifyObservers();
    }

    synchronized Double getValue() { return value; }

    synchronized void setValue(Double value) {  this.value = value; }

    //-- handle observers

    // add new Observer - it will be notified when Observable changes
    public void registerObserver(Observer observer) {
        if (observer != null) {
            mObservers.add(observer);
        }
    }

    //remove an Observer
    public void unregisterObserver(Observer observer) {
        if (observer != null) {
            mObservers.remove(observer);
        }
    }

    //notifies registered observers
    private void notifyObservers() {
        for (Observer observer : mObservers) {
            observer.onObservableChanged();
        }
    }
}

//Interface implemented by View and used by Model
interface Observer {
    void onObservableChanged();
}

//Encapsulates thread that does some work on model
class Worker implements Runnable{

    private final Model model;
    private boolean cancel = false;
    private final Random rnd = new Random();

    public Worker(Model model) {
        this.model = model;
        new Thread(this).start();
    }

    @Override
    public void run() {
        while(! cancel){
            model.changeValue(rnd.nextDouble()* 100);  //generate random value
            try {
                TimeUnit.MILLISECONDS.sleep(300); //pause
            } catch (InterruptedException ex) { ex.printStackTrace();   }
        }
    }

    void cancel() { cancel = true;  }
}

问题是您永远不能通过另一个线程触及 EDT/GUI 线程拥有的任何内容。这个问题对于从 Java Swing 到 Android 和 iOS 平台的所有 UI 系统都是真实的。 Java Swing 有一个 SwingWorker class 来解决这个问题。

你可以在这里找到一个简单的例子:https://docs.oracle.com/javase/tutorial/uiswing/concurrency/simple.html

所以在这个循环中你有: 而(真){
如果(数据可用){
....
gui.updateLabel(数据); 睡觉(10); } }

你会发现这里已经回答了这个问题:How do I use SwingWorker in Java?