Updating JLabel text from another thread in real-time

柔情痞子 提交于 2020-02-25 04:14:00

问题


I need to create a GUI to show real-time data coming from the Serial port. I'm reading serial port data from a separate thread and I need to update GUI from there. My current implementation is like this.

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

I'm receiving about 10 values per second, I hope Swing can handle that. My problem is the JLabel is not updating real-time and it misses some data as it shows the latest one. How can I fix this?


回答1:


You could implement a thread-safe model, that encapsulates the data the view needs. The model should be updated by the information from the serial port (represented by the Worker class).
The view should listen to model changes and update.

The following code implements Model-View-Controller pattern. It is a one-file SSCCE : it can be copy-pasted into ViewUpdatedByThread.java and run.
The view is just that. It listens to changes in Model using Observer interface.
The Model encapsulates the information that the view needs (in this case just a double value). It allows thread-safe update of the value, and notifies the observers (the view) when information changes.
The Worker class uses a thread to change the information in Model.
The Controller orchestrates the various members : initialize them, and links view to model:

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




回答2:


The issue is you can NEVER touch anything owned by the EDT/GUI thread via another thread. This problem is true for all UI systems from Java Swing to Android and iOS platforms. Java Swing has a SwingWorker class to resolve this very issue.

You can find a simple example here: https://docs.oracle.com/javase/tutorial/uiswing/concurrency/simple.html

So in this loop you have: while (true) {
if (dataAvailable) {
....
gui.updateLabel(data); sleep(10); } }

You will find this has already been answered here: How do I use SwingWorker in Java?




回答3:


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


来源:https://stackoverflow.com/questions/58526874/updating-jlabel-text-from-another-thread-in-real-time

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!