Обновление текста JLabel из другого потока в режиме реального времени - PullRequest
1 голос
/ 23 октября 2019

Мне нужно создать графический интерфейс для отображения данных в реальном времени, поступающих с последовательного порта. Я читаю данные последовательного порта из отдельного потока, и мне нужно обновить 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 не обновляет в режиме реального времени, и он пропускает некоторые данные, поскольку он показывает самую последнюю. Как я могу это исправить?

Ответы [ 3 ]

2 голосов
/ 23 октября 2019

Вы можете реализовать поточно-ориентированную модель, которая инкапсулирует данные, необходимые для представления. Модель должна обновляться информацией из последовательного порта (представлен классом Worker). Представление должно прослушивать изменения модели и обновлять.

Следующий код реализует шаблон Model-View-Controller. Это один файл SSCCE : его можно скопировать в ViewUpdatedByThread.java и запустить. Точка зрения это просто так. Он слушает изменения в модели, используя Observer интерфейс. Модель инкапсулирует информацию, которая необходима представлению (в данном случае это просто двойное значение). Это позволяет потокобезопасное обновление значения и уведомляет наблюдателей (представление), когда информация изменяется. Класс Worker использует поток для изменения информации в 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;  }
}
1 голос
/ 24 октября 2019

Проблема в том, что вы НИКОГДА не можете касаться чего-либо, принадлежащего потоку EDT / GUI, через другой поток. Эта проблема актуальна для всех систем пользовательского интерфейса от Java Swing до платформ Android и iOS. Java Swing имеет класс SwingWorker для решения этой самой проблемы.

Вы можете найти простой пример здесь: https://docs.oracle.com/javase/tutorial/uiswing/concurrency/simple.html

Итак, в этом цикле вы имеете: while (true) {
if (dataAvailable) {
....
gui.updateLabel (data);сна (10);}}

Здесь вы уже найдете ответ: Как использовать SwingWorker в Java?

0 голосов
/ 23 октября 2019
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);
            }
        }
    }
}
...