问题
The goal of my program is to draw squares from randomly generated points. I want to display a set of squares as soon as they are generated by each thread. However, only one set of squares displays, once all the threads are done running. I have used swinginvoke, and am curious as if there is a problem with repaint() since all the threads reach repaint but don't paint until the final thread is done, and end up overlapping each other. I also don't want the "storedata" variable to be shared between each thread but every instance of it keeps the data in it. I tried to fix this by clearing it, but it hasn't worked. The program consists of a custom thread class, a main class where the threads get started, a GUI class, and a custom jpanel where the squares are drawn.
import java.awt.*;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;
class Action extends Thread {
private Random rand = new Random();
private static CopyOnWriteArrayList<Point> storedata = new CopyOnWriteArrayList<>();
static volatile CopyOnWriteArrayList<CopyOnWriteArrayList<Point>> finallist = new CopyOnWriteArrayList<>();
private GUI g;
Action(GUI g) {
this.g = g;
}
private void generatePoint() {
int x = rand.nextInt(500);
int y = rand.nextInt(500);
Point p = new Point(x,y);
storedata.add(p);
}
public void run() {
for (int i = 0; i < 5; i++) {
generatePoint();
}
CopyOnWriteArrayList<Point> copy = new CopyOnWriteArrayList<>(storedata);
finallist.add(copy);
SwingUtilities.invokeLater(() -> {
try {
Thread.sleep(500);
System.out.println("ARRAY = " + copy.toString());
g.setSolution(copy);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
storedata.clear();
}
}
public class Main {
public static void main(String[] args) {
GUI g = new GUI();
g.setVisible(true);
Thread[] threads = new Thread[5];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Action(g);
threads[i].start();
}
}
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.concurrent.CopyOnWriteArrayList;
public class GUI extends JFrame implements ActionListener {
private CustomPanel c;
GUI() {
initialize();
}
private void initialize() {
this.setLayout(new FlowLayout());
c = new CustomPanel(500,500);
this.setSize(1000,1000);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.add(c);
this.setTitle("Seat Placement Program");
}
@Override
public void actionPerformed(ActionEvent e) {
}
public void setSolution(CopyOnWriteArrayList<Point> room) {
c.setRoom(room);
c.setPaint(true);
c.repaint();
}
import javax.swing.*;
import javax.swing.border.Border;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.concurrent.CopyOnWriteArrayList;
public class CustomPanel extends JPanel implements ActionListener {
private Border blackline = BorderFactory.createLineBorder(Color.black);
private boolean paint;
private CopyOnWriteArrayList<Point> room;
public CustomPanel(int h, int w) {
room = new CopyOnWriteArrayList<>();
paint = false;
this.setPreferredSize(new Dimension(w,h));
this.setBorder(blackline);
}
@Override
public void actionPerformed (ActionEvent e) {
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
if(paint) {
for(Point p : room) {
g.drawRect((int) p.getX(),(int) p.getY(),20,20);
}
}
}
void setPaint(boolean b) {
paint = b;
}
public void setRoom(CopyOnWriteArrayList<Point> room) {
this.room = room;
}
}
回答1:
In swing, painting should only be done in the Event Dispatching Thread. The way to achieve what you want is when the thread has finished, add the shape (along with the color, possibly) to a collection in the JComponent that you want it in (you need to create a custom JComponent that has such a collection, and you should synchronize around it when adding shapes and in paintComponent when painting the shapes). Then in your non-EDT thread, call myJComponent.repaint(). It is okay to call this in all of your non-EDT threads.
Example:
public class MyPanel extends JPanel {
Collection<Shape> myShapes = ...;
public void paintComponent(Graphics g) {
... // do any other non-shape related painting
Graphics2D g2 = (Graphics2D) g;
synchronized (myShapes) {
for (Shape s : myShapes) {
//TODO: do you want to set a color?
g.draw(s);
//TODO: do you want to fill the shapes?
g.fill(s);
}
}
}
}
Then in your threads:
public void run() {
...
Shape newShape = ...;
synchronized (myPanel.myShapes) {
//add newShape to the collection
}
myPanel.repaint();
}
来源:https://stackoverflow.com/questions/64232795/how-to-paint-in-java-swing-in-every-concurrent-thread