简体   繁体   English

根据慢速服务器的结果更新 JPanel(使用线程不阻塞 GUI)

[英]Updating JPanel based on results from slow server (Using threads to not block GUI)

So I have been looking to update one of my panels in a my client code with data that comes from a server in Indonesia.因此,我一直在寻找使用来自印度尼西亚服务器的数据更新我的客户端代码中的一个面板。 The delay is rather long (2-8) sec and Im noticing that my UI is freezing during the time it takes for the response to return from the server.延迟相当长(2-8)秒,我注意到我的用户界面在响应从服务器返回所需的时间内冻结。

The response will be used to draw some points on a map (not yet implemented).响应将用于在地图上绘制一些点(尚未实现)。 I have been looking all over the net to find out how to do it and I have come across:我一直在网上寻找如何做到这一点,我遇到了:

InvokeLater.稍后调用。 SwingWroker.摇摆人。 ScheduledThreadPoolExecutor.调度线程池执行器。 Making the JPanel a runnable to run in its own thread(seems like best option).使 JPanel 可以在自己的线程中运行(似乎是最佳选择)。 http://www.java2s.com/Tutorial/Java/0160__Thread/CreateathreadtoupdateSwing.htm http://www.java2s.com/Tutorial/Java/0160__Thread/CreateathreadtoupdateSwing.htm

But tbh most of the data i find is out dated (more than 5 years old).但是我发现的大部分数据都已经过时了(超过 5 年)。

Here is the JPanel class i want to update based on a server query:这是我想根据服务器查询更新的 JPanel 类:

public class MapPanel extends JPanel implements Pointable, Runnable {

    private static final long serialVersionUID = 1L;
    private List<Shape> shapes = new LinkedList<>();
    private State mapPanelState;
    public Shape selected;
    private BufferedImage image;


    public MapPanel() {
        Commander.getInstance().addShapeContainer(this);
        mapPanelState = NoState.getInstance();
        MouseHandler mouseHandler = new MouseHandler(this);
        KeyListener keyListener = new KeyListener();

        readImage();

        this.addMouseListener(mouseHandler);
        this.addMouseMotionListener(mouseHandler);
        this.addKeyListener(keyListener);
        this.setBackground(Color.white);
        this.setFocusable(true);
        this.requestFocusInWindow();
    }

    private void readImage(){
        try {
            image = ImageIO.read(new File("/MapCoordProject/earthmap1.jpg"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public void setState(State state) {
        mapPanelState = state;
    }

    public List<Shape> getShapes() { return shapes; }

    public void setShapes(List<Shape> shapes) {
        this.shapes = shapes;
    }

    public Shape getLastShape(){ return shapes.get(shapes.size()-1); }

    
    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        g.drawImage(image, 0, 0, null);

        for (Shape shape : shapes)
            shape.draw((Graphics2D) g);
    }

    public void select(Point point) {
        for (Shape shape : shapes) {
            if (shape.intersects(point)) {
                selected = shape;
            }
        }
    }

    @Override
    public Dimension getPreferredSize() {
        if (image == null) {
            return super.getPreferredSize();
        } else {
            int w = image.getWidth();
            int h = image.getHeight();
            return new Dimension(w, h);
        }
    }



    public void pointerDown(Point point)  {
        mapPanelState.pointerDown(point, this);
    }

    public void pointerUp(Point point) {
        mapPanelState.pointerUp(point, this);
        selected = null;
    }

    public void pointerMoved(Point point, boolean pointerDown)  {
        mapPanelState.pointerMoved(point, pointerDown, this);
    }

    @Override
    public void run() {
        
    }
}

I want a method that updates the "Shapes" array in a separate thread to stop everything from freezing.我想要一种在单独的线程中更新“Shapes”数组以阻止所有内容冻结的方法。

Any suggestions?有什么建议么?

Making your JPanel implement Runnable is not the best solution.让您的 JPanel 实现 Runnable 并不是最好的解决方案。 There is no reason to expose a run() method to other classes.没有理由向其他类公开run()方法。

Instead, create a private void method that takes no arguments.相反,创建一个不带参数的私有 void 方法。 A method reference that refers to that method can act as a Runnable, since it will have the same arguments and return type.引用该方法的方法引用可以充当 Runnable,因为它将具有相同的参数和返回类型。 You can then pass it to a Thread constructor.然后,您可以将其传递给 Thread 构造函数。

public MapPanel() {
    // ...

    readImage();
    new Thread(this::readShapes, "Reading shapes").start();

    // ...
}

private void readShapes() {
    try {
        List<Shape> newShapes = new ArrayList<>();

        URL server = new URL("https://example.com/indonesia/data");
        try (InputStream dataSource = server.openStream()) {
            while ( /* ... */ ) {
                Shape shape = /* ... */;
                newShapes.add(shape);
            }
        }

        EventQueue.invokeLater(() -> setShapes(newShapes));
    } catch (IOException e) {
        e.printStackTrace();
        EventQueue.invokeLater(() -> {
            JOptionPane.showMessageDialog(getTopLevelContainer(),
                "Unable to retrieve data:\n" + e, "Load Error",
                JOptionPane.ERROR_MESSAGE);
        });
    }
}

Notice that calls to methods involving Swing objects are always wrapped in a call to EventQueue.invokeLater, to make sure they run on the correct thread.请注意,对涉及 Swing 对象的方法的调用总是包含在对 EventQueue.invokeLater 的调用中,以确保它们在正确的线程上运行。

It is possible to improve this by creating a progress dialog that shows while the data is being loaded, but that would make this answer much longer and would require more knowledge about the Indonesian API you're calling.可以通过创建一个在加载数据时显示的进度对话框来改进这一点,但这会使这个答案变得更长,并且需要更多关于您正在调用的印尼语 API 的知识。

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

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