所以我有一个非常奇怪的问题,根据我的理解,我只是在学习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);
}
}```
您的ActionListener是从处理事件队列的线程中调用的。因此,如果您的actionPerformed
方法花费很长时间,则不会处理任何其他事件,包括绘画事件,直到actionPerformed
方法返回为止。
Thread.sleep(及其类似方法)一定不能直接或间接从ActionListener或任何其他事件侦听器中调用。睡眠调用应始终在其他线程上进行。
有一些简单的方法可以在事件分发线程中定期执行代码,但就您而言,这还不够。
问题是绘画依赖于您的Map对象和Node对象(我认为)。这意味着在事件分配线程之外更新Map或Nodes或其任何后代对象或数据是不安全的。在同时绘制绘画方法的同时更改地图的状态会导致奇怪的视觉行为。
可以通过使用仅代表您的绘画动作的类来解决此问题,该类保留该信息的自己的副本,因此它不依赖于任何其他对象。
例如,如果您的Display类是画线,则可以:
在新线程中调用findPath
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);
}
}