从另一个线程实时更新 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?
我需要创建一个 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?