JFrame在所有代码运行之前不会更新其绘制

问题描述 投票:0回答:1

所以我有一个非常奇怪的问题,根据我的理解,我只是在学习JFrames / Panels,如果我向框架中添加新组件,则必须调用revalidate()来使框架使用所述组件。

在下面所示的A *的实现中,在while()循环中使用repaint()表示算法的进度。

这将显示算法运行时的进度,并且工作正常,直到我决定尝试向gui添加菜单。

因此,现在我需要能够将Display()组件(这是一个JPanel)添加到框架中并使其具有与以前一样的功能,并在运行时显示算法。但是目前有大约一秒钟的停顿,并且仅绘制了算法的最终状态,即好像它立即绘制了while()循环中最后一个repaint()调用一样。

感谢您的任何帮助。

import java.awt.event.*;
import java.io.FileNotFoundException;
import java.util.HashSet;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Set;
import javax.swing.*;

public class aStar {
    /** Width of the GUI */
    private static final int WIDTH = 1280;
    /** Height of the GUI */
    private static final int HEIGHT = 720;

    public static JFrame frame = new JFrame("A* Search Algorithm");

    public static Set<Node> closedSet;
    public static Queue<Node> openSet;

    public static String fileName;

    public static void findPath() {
        try {
            // Initialise the open and closed sets
            closedSet = new HashSet<>();
            openSet = new PriorityQueue<>((a, b) -> Double.compare(a.f, b.f));

            // Process the map
            Map map = new Map(fileName);
            openSet.add(map.start);
            frame.add(new Display(map));
            frame.revalidate();

            /**
             * The A* Algorithm
             */
            while (true) {
                Node current = openSet.poll();

                if (current == map.end) {
                    // We have reached the goal -- render the path and exit
                    renderPath(current, frame);
                    System.out.println("Done!");
                    return;
                }

                // Check through every neighbor of the current node
                for (Node n : map.neighborsOf(current)) {
                    // if its closed or a wall, ignore it
                    if (closedSet.contains(n)) {
                        continue;
                    }

                    // Set the node's h value
                    n.h = heuristic(n, map.end);

                    // Calculate the possible total cost of moving to this node from start
                    double tentativeG = calculateCost(current, n);

                    // Check whether the cost we've calculated is better than the node's current
                    // cost. If so, the path we're currently on is better so we update its g
                    // and add it to openSet
                    if (tentativeG < n.g) {
                        n.setG(tentativeG);
                        n.previous = current;

                        // We need to remove and add the node here in case it already exists
                        // within the PriorityQueue, so that we can force a re-sort.
                        openSet.remove(n);
                        openSet.add(n);
                    }
                }

                // Move current to closedSet
                openSet.remove(current);
                closedSet.add(current);

                // Color the open and closed sets accordingly
                for (Node n : openSet) {
                    n.color = Color.GREEN;
                }
                for (Node n : closedSet) {
                    n.color = Color.RED;
                }

                if (openSet.isEmpty()) {
                    // If openSet is empty, then we failed to find a path to the end
                    // In this case, we render the path to the node with the lowest `h`
                    // value, which is the node closest to the target.

                    Node minHNode = null;
                    for (int x = 0; x < map.width; x++) {
                        for (int y = 0; y < map.height; y++) {
                            Node candidate = map.get(x, y);
                            if (candidate.previous == null)
                                continue;

                            if (minHNode == null) {
                                minHNode = candidate;
                            } else if (candidate.h < minHNode.h) {
                                minHNode = candidate;
                            }
                        }
                    }

                    // Walk through the path we decided on and render it to the user
                    renderPath(minHNode, frame);
                    System.out.println("Failed to reach target. Rendered closest path instead.");
                    return;
                } else {
                    Thread.sleep(10);
                    frame.repaint();
                }
            }
        } catch (FileNotFoundException e) {
            System.err.println("error: Could not find the file \"" + fileName + "\"");
        } catch (InterruptedException e) {
            System.err.println("Error occurred while calling Thread.sleep()");
        } catch (MapException e) {
            System.out.println("error: " + e.getMessage());
        }
    }

    public static void main(String[] args) {
        // Build our GUI
        frame.setPreferredSize(new Dimension(WIDTH, HEIGHT));
        frame.setMinimumSize(new Dimension(WIDTH, HEIGHT));
        frame.setMaximumSize(new Dimension(WIDTH, HEIGHT));
        frame.setResizable(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        // Add the menubar and items
        JMenuBar menubar = new JMenuBar();
        frame.setJMenuBar(menubar);
        JMenu file = new JMenu("File");
        menubar.add(file);
        JMenuItem selectMap1 = new JMenuItem("Map 1");
        file.add(selectMap1);

        class selectMapName implements ActionListener {
            public void actionPerformed(ActionEvent e) {
                JMenuItem menuItem = (JMenuItem) e.getSource();
                JPopupMenu menu = (JPopupMenu) menuItem.getParent();
                int index = menu.getComponentZOrder(menuItem);
                onClick(index);
            }

            public void onClick(int index) {
                switch (index) {
                    case 0:
                        fileName = "map1.txt";
                        break;
                    case 1:
                        fileName = "map2.txt";
                        break;
                    case 2:
                        fileName = "map3.txt";
                        break;
                    case 3:
                        fileName = "map4.txt";
                        break;
                    case 4:
                        fileName = "map5.txt";
                        break;
                }
                findPath();
            }
        }
        // Add all the action listeners to the menu items
        selectMap1.addActionListener(new selectMapName());

        // Show the frame
        frame.setVisible(true);
    }

    private static void renderPath(Node startFrom, JFrame frame) {
        // Walk through the path we decided on and render it to the user
        Node temp = startFrom;
        while (temp.previous != null) {
            temp.color = Color.BLUE;
            temp = temp.previous;
        }

        // Repaint with the newly marked path
        frame.repaint();
    }

    /**
     * The heuristic used to determine the validity of a potential path. Currently
     * just returns the euclidian distance. May be better to use taxicab distance if
     * we are not moving diagonals
     * 
     * @param current The current Node
     * @param end     The end Node
     * @return {@code double} The h value for the current Node
     */
    private static double heuristic(Node current, Node end) {
        return Math.hypot(end.x - current.x, end.y - current.y);
    }

    private static double calculateCost(Node current, Node next) {
        double currentG = current.g == Double.MAX_VALUE ? 0 : current.g;
        return currentG + heuristic(current, next);
    }
}```
java swing jframe jpanel event-dispatch-thread
1个回答
2
投票
[Repainting is one of those events.如果事件队列的处理被长时间运行的事件侦听器阻止,则后续事件将不被处理。

您的ActionListener是从处理事件队列的线程中调用的。因此,如果您的actionPerformed方法花费很长时间,则不会处理任何其他事件,包括绘画事件,直到actionPerformed方法返回为止。

Thread.sleep(及其类似方法)一定不能直接或间接从ActionListener或任何其他事件侦听器中调用。睡眠调用应始终在其他线程上进行。

有一些简单的方法可以在事件分发线程中定期执行代码,但就您而言,这还不够。

问题是绘画依赖于您的Map对象和Node对象(我认为)。这意味着在事件分配线程之外更新Map或Nodes或其任何后代对象或数据是不安全的。在同时绘制绘画方法的同时更改地图的状态会导致奇怪的视觉行为。

可以通过使用仅代表您的绘画动作的类来解决此问题,该类保留该信息的自己的副本,因此它不依赖于任何其他对象。

例如,如果您的Display类是画线,则可以:

在新线程中调用findPath

    具有Display包含java.awt.geom.Line2D对象,而不是对Map的引用
  • 具有findPath在事件分派线程中随着findPath的进行将Line2D对象添加到人机界面
  • 这可能看起来像这样:
  • public void onClick(int index) { switch (index) { // ... } new Thread(() -> findPath()).start(); }

    以及上方的位置:

    Thread.sleep(10);
    EventQueue.invokeLater(() -> {
        display.addLine(new Line2D(start.x, start.y, end.x, end.y));
        display.repaint();
    });
    

    [没有看到Display类如何决定要绘画的内容,我无法提供有关如何创建Line2D对象的示例。但我希望您在Display类中可能有类似以下的代码:

    private final Collection<Line2D> lines = new ArrayList<>();
    
    public void addLine(Line2D line) {
        lines.add(line);
    }
    
    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
    
        Graphics2D g2 = (Graphics2D) g;
        for (Line2D line : lines) {
            g.draw(line);
        }
    }
    
  • © www.soinside.com 2019 - 2024. All rights reserved.