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

标签 java multithreading swing user-interface real-time-updates

我需要创建一个 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 没有实时更新,它在显示最新数据时遗漏了一些数据。我该如何解决这个问题?

最佳答案

您可以实现一个线程安全模型,封装 View 所需的数据。模型应该通过来自串行端口的信息更新(由 Worker 类表示)。
View 应该监听模型的变化和更新。

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

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;  }
}

enter image description here

关于java - 从另一个线程实时更新 JLabel 文本,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58526874/

相关文章:

Java 反射没有按预期工作

java - 使用 Java 进行 AWS Lambda Python 包部署

java - 通过锁定在Java中实现线程安全的ArrayList

java - 在 Swing 中的 JTable 单元格内滚动

java - 将 JTextField 输入保存到文本文件

java - 如何将 java swing 小程序嵌入到网站中?

java - Eclipse "Terminate/Disconnect All"行为

java - Android下载drawable到Arraylist<Integer>

multithreading - 如何将对堆栈变量的引用传递给线程?

java - Android 信息亭应用程序 - 关闭不需要的应用程序