繁体   English   中英

如何动态显示线程数据到java swing GUI?

[英]How to dynamically display thread data to java swing GUI?

我正在开发一个测量由汽车 object 驾驶的“距离”的应用程序。每辆汽车都是它自己的线程,具有随机生成的速度,我计算每辆汽车行驶的距离。 虽然我能够在我想要的任何时间间隔内从线程内获取正确的数据以打印到控制台(目前是每秒一次,但稍后可能会更新),但我正在尝试找出一种方法来代替显示该信息一个 swing GUI(作为表格行,或者只是作为文本区域行)由于 Car class 是一个单独的文件,我不能让它直接推送到 GUI(或者至少,我不知道该怎么做那)。 我需要定期更新 2 个“列”信息:当前速度和距离。

我尝试做的事情:设置 jtable 并提取行数据:这允许我捕获快照但不能不断更新数据。

将信息推送到 jtextarea:我可以将其作为快照进行。 当我尝试将它包装在 while 循环中时(线程运行时,append ...),系统崩溃了。 当我尝试将 append 包装在 Thread.sleep 中时,结果相同。

因为在任何给定时间我都可以拍摄汽车位置的快照(car.getLocation()),我在想的是,也许主要方法可以每秒主动寻找该快照,但是,当我尝试使用 while 循环时和 Thread.sleep,如前所述,它使系统崩溃。

还需要注意的是,完成后,GUI 将允许创建任意数量的 Car 对象,我希望每个对象都有一行并定期更新,以便可以比较距离数字。

编辑:根据@matt 的建议,我添加了一个 swing 计时器并修改了 GUI 以适应。 现在的挑战是新的 jtextfield 只在我调整页面大小时在显示器上弹出。 有没有办法以某种方式更新/刷新 GUI?

更新的 GUI 元素:

JButton carb = new JButton("add car");
        GridBagConstraints carC = new GridBagConstraints();
        carC.gridx = 1;
        carC.gridy = 2;
        carb.addActionListener(a -> {
            totalCars++;
            String carName = "Car" + totalCars;
            Car car = new Car(carName);
            cars.add(car);

            Thread thread = new Thread(car);

            JTextField t = new JTextField(50);
            GridBagConstraints tC = new GridBagConstraints();
            t.setEditable(false);
            tC.gridx = 1;
            tC.gridy = currentcol;
            currentcol++;
            content.add(t, tC);

            running = true;
            thread.start();
            ActionListener taskPerformer = new ActionListener() {
                public void actionPerformed(ActionEvent evt) {
                    t.setText(df.format(car.getLocation()));
                    t.setText("Name: " + carName + ", Drive speed: " + car.getSpeed() + ", Current speed: "
                            + car.getCurrentSpeed() + ", Location (distance from beginning): "
                            + df.format(car.getLocation()));
                }
            };
            Timer timer = new Timer(1000, taskPerformer);
            timer.start();
        });
        content.add(carb, carC);

代码(到目前为止):汽车 class:

      private void setLocation(long timeElapsed) {
        location = location + ((timeElapsed-lastCheck)*(currentSpeed*0.44704));
        lastCheck = timeElapsed;
    }  

      public void run() {
        long startTime = System.currentTimeMillis();
        long elapsedTime;
        while (flag) {
            try {
                Thread.sleep(1000);
                elapsedTime = System.currentTimeMillis()-startTime;
                elapsedTime = elapsedTime/1000;
                setLocation(elapsedTime);
                System.out.println(getName()+": " + df.format(getLocation()) + " meters");
            } catch (InterruptedException e) {
            }
        }
    }


这是我尝试从 main 方法使用 Thread.sleep 的方法。 这不起作用(使系统崩溃):

            while (running) {
                try {
                    Thread.sleep(1000);
                    carArea.append("\nName: " + carName + ", Drive speed: " + car.getSpeed() + ", Current speed: "
                            + car.getCurrentSpeed() + ", Location (distance from beginning): " + car.getLocation());

                } catch (InterruptedException e) {

                }

            }

首先,首先看一下Swing中的并发性。 重要的是,Swing 不是线程安全的,你不应该从事件调度线程的上下文之外更新 UI(或它所依赖的 state)

您有三个基本选项

Swing Timer

这为您提供了一种定期安排重复回调的方法,该回调在事件调度线程的上下文中执行

SwingWorker

这为您提供了一个包装器 class,它可以在后台线程上执行功能,但可用于将信息传递回事件调度线程以进行“处理”

EventQueue.invokeLater

这基本上提供了在事件调度线程上执行功能(通过Runnable interface )。 虽然这很有用,但很难将信息从一个上下文传递到另一个上下文而不必构建它,这应该让你看看SwingWorker


推送还是轮询?

您需要问的下一个问题是,您想“推送”更新还是“轮询”更新。 这将影响您使用哪种 API 方法来更新 UI。

例如,如果要轮询 state,则 Swing Timer是一个不错的选择。 可以在需要时执行更新。 如果有大量更新,这将是一个不错的解决方案,因为您不太可能使 EDT 过载,这可能会导致其他问题。

相反,如果您想推送更新(从汽车上推送,可能通过某种观察者),那么SwingWorker “可以”工作,但似乎有很多开销,只需使用即可更轻松地实现EventQueue.invokeLater 如果汽车实际上没有自己的线程支持,那么SwingWorker将非常有用,但它或多或少会像 Swing Timer一样起作用。

但是,如果您只想知道汽车何时超过某种阈值(即又行驶了 10 公里),那么我可能会想使用SwingWorker来轮询汽车的 state 并在触发阈值时将更新推送到通过publish / process工作流程的 UI。

话虽如此, SwingWorker仅限于 10 个活跃的工作人员,因此您需要考虑这一点。

轮询示例(Swing Timer

此示例通过 Swing Timer使用“轮询”方法,这样做的好处是,汽车的顺序不会改变(由 UI 控制)。 虽然您可以通过JTable控制它,但这非常简单。

在此处输入图像描述

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.NumberFormat;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.Timer;

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

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private List<Car> cars = new ArrayList<>(32);
        private JTextArea ta = new JTextArea(10, 20);

        private Random rnd = new Random();

        private NumberFormat format = NumberFormat.getNumberInstance();

        public TestPane() {
            for (int index = 0; index < 32; index++) {
                Car car = new Car("Car " + index, 40.0 + (rnd.nextDouble() * 180.0));
                cars.add(car);
            }

            setLayout(new BorderLayout());
            add(new JScrollPane(ta));

            Timer timer = new Timer(1000, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    for (Car car : cars) {
                        ta.append(car.getName() + " @ " + format.format(car.getSpeedKPH()) + " ~ " + format.format(car.getLocation()) + "km\n");
                        if (!car.isRunning() && rnd.nextBoolean()) {
                            car.start();
                            ta.append(car.getName() + " got started\n");
                        }
                    }
                }
            });
            timer.start();
        }

    }

    public class Car {
        private String name;
        private double speedKPH;
        private Instant timeStartedAt;

        public Car(String name, double kmp) {
            this.speedKPH = kmp;
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public double getSpeedKPH() {
            return speedKPH;
        }

        public Instant getTimeStartedAt() {
            return timeStartedAt;
        }

        public boolean isRunning() {
            return timeStartedAt != null;
        }

        public void start() {
            timeStartedAt = Instant.now();
        }

        protected double distanceTravelledByMillis(long millis) {
            double time = millis / 1000d / 60d / 60d;
            return getSpeedKPH() * time;
        }

        public double getLocation() {
            Instant timeStartedAt = getTimeStartedAt();
            if (timeStartedAt == null) {
                return 0;
            }
            Duration time = Duration.between(timeStartedAt, Instant.now());
            return distanceTravelledByMillis(time.toMillis());            
        }
    }
}

您还可以控制更新的速度,这将允许您在看到问题之前将解决方案扩展到更多的汽车(数万辆)(尽管JTextArea在此之前是瓶颈)

推送 ( EventQueue.invokeLater )

这是一个更随机的例子,每辆车都有自己的更新间隔,这会通过观察者触发回调。 然后,观察者需要将调用同步回事件调度队列,然后才能将更新添加到文本区域。

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.text.NumberFormat;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

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

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private List<Car> cars = new ArrayList<>(32);
        private JTextArea ta = new JTextArea(10, 20);

        private Random rnd = new Random();
        private NumberFormat format = NumberFormat.getNumberInstance();

        public TestPane() {
            for (int index = 0; index < 32; index++) {
                int timeInterval = 500 + rnd.nextInt(4500);
                Car car = new Car("Car " + index, 40.0 + (rnd.nextDouble() * 180.0), timeInterval, new Car.Observer() {
                    @Override
                    public void didChangeCar(Car car) {
                        updateCar(car);
                    }
                });
                cars.add(car);
                car.start();
            }

            setLayout(new BorderLayout());
            add(new JScrollPane(ta));
        }

        protected void updateCar(Car car) {
            if (!EventQueue.isDispatchThread()) {
                EventQueue.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        updateCar(car);
                    }
                });
            }
            ta.append(car.getName() + " @ " + format.format(car.getSpeedKPH()) + " ~ " + format.format(car.getLocation()) + "km\n");
        }

    }

    public class Car {
        public interface Observer {
            public void didChangeCar(Car car);
        }

        private String name;
        private double speedKPH;
        private Instant timeStartedAt;

        private int notifyInterval;
        private Observer observer;

        private Thread thread;

        public Car(String name, double kmp, int notifyInterval, Observer observer) {
            this.speedKPH = kmp;
            this.name = name;
            this.notifyInterval = notifyInterval;
            this.observer = observer;
        }

        public String getName() {
            return name;
        }

        public double getSpeedKPH() {
            return speedKPH;
        }

        public Instant getTimeStartedAt() {
            return timeStartedAt;
        }

        public boolean isRunning() {
            return timeStartedAt != null;
        }

        public void start() {
            if (thread != null) {
                return;
            }
            timeStartedAt = Instant.now();
            thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(notifyInterval);
                        observer.didChangeCar(Car.this);
                    } catch (InterruptedException ex) {
                    }
                }
            });
            thread.start();
        }

        protected double distanceTravelledByMillis(long millis) {
            double time = millis / 1000d / 60d / 60d;
            return getSpeedKPH() * time;
        }

        public double getLocation() {
            Instant timeStartedAt = getTimeStartedAt();
            if (timeStartedAt == null) {
                return 0;
            }
            Duration time = Duration.between(timeStartedAt, Instant.now());
            return distanceTravelledByMillis(time.toMillis());
        }
    }
}

这给 EDT 带来了大量的开销,因为每辆车都需要在 EDT 上添加一个请求来处理,所以你添加的车越多,EDT 就会越慢。

考虑使用MCV model构建您的代码。 这在 Model、视图和 Controller 之间划分了职责。
每一个(M、V 和 C)都成为定义明确的单一职责 class。起初,类的数量以及它们之间的关系可能看起来令人费解。
在研究和理解结构之后,您会意识到它实际上将手头的任务划分为更小且更容易处理的部分。
我的答案(和代码)基于 MadProgramer综合答案中的“推送”选项:

import java.awt.*;
import java.text.*;
import java.time.*;
import java.util.*;
import java.util.List;
import javax.swing.*;

public class Main {
    public static void main(String[] args) {
        new CarsControler().startCars();
    }
}

class CarsControler implements Observer{

    private final Cars cars;
    private final CarGui gui;
    private final NumberFormat format = NumberFormat.getNumberInstance();
    private boolean stopCars = false;

    public CarsControler() {
        cars = new Cars(32);
        gui = new CarGui();
    }

    public void startCars(){

        Random rnd = new Random();
        gui.update("Strarting cars\n");

        for(Car car : cars.getCars()){
            if (! car.isRunning()) {
                car.start();
            }
            final int moveInterval = 2000 + rnd.nextInt(8000);
            Thread thread = new Thread(() -> {
                try {
                    while(! stopCars){
                        Thread.sleep(moveInterval);
                        carChanged(car);
                    }
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
            });
            thread.start();
        }
    }

    public void setStopCars(boolean stopCars) {
        this.stopCars = stopCars;
    }

    @Override
    public void carChanged(Car car) {
        String message = car.getName() + " @ " + format.format(car.getSpeedKPH()) + " ~ " + format.format(car.getLocation()) + "km\n";
        gui.update(message);
    }
}

/**
 * Model
 * TODO: add thread safety if model is used by multiple threads.
 */
class Cars {

    private final List<Car> cars;
    private final Random rnd = new Random();

    public Cars(int numberOfCars) {
        cars = new ArrayList<>(numberOfCars);

        for (int index = 0; index < 32; index++) {
            Car car = new Car("Car " + index, 40.0 + rnd.nextDouble() * 180.0);
            cars.add(car);
        }
    }

    //returns a defensive copy of cars
    public List<Car> getCars() {
        return new ArrayList<>(cars);
    }
}

class CarGui{

    private final JTextArea ta = new JTextArea(10, 20);

    public CarGui() {
        JFrame frame = new JFrame();
        JPanel testPane = new JPanel(new BorderLayout());
        testPane.add(new JScrollPane(ta));
        frame.add(testPane);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    void update(String s) {
        if (!EventQueue.isDispatchThread()) {
            EventQueue.invokeLater(() -> update(s));
        } else {
            ta.append(s);
        }
    }
}

class Car {

    private final String name;
    private final double speedKPH;
    private Instant timeStartedAt;

    public Car(String name, double speedKPH) {
        this.speedKPH = speedKPH;
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public double getSpeedKPH() {
        return speedKPH;
    }

    public Instant getTimeStartedAt() {
        return timeStartedAt;
    }

    public boolean isRunning() {
        return timeStartedAt != null;
    }

    public void start() {
        timeStartedAt = Instant.now();
    }

    protected double distanceTravelledByMillis(long millis) {
        double time = millis / 1000d / 60d / 60d;
        return getSpeedKPH() * time;
    }

    public double getLocation() {
        Instant timeStartedAt = getTimeStartedAt();
        if (timeStartedAt == null)      return 0;
        Duration time = Duration.between(timeStartedAt, Instant.now());
        return distanceTravelledByMillis(time.toMillis());
    }
}

interface Observer {
    void carChanged(Car car);
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM