I'm working on a Swing application where I have a list of shapes (List shapesToDraw1) that I update from a non-EDT thread using SwingUtilities.invokeLater(). The code for updating the list is executed on the EDT, but the changes are not being reflected when the paintComponent method is called for repainting.
I've verified that the code for adding shapes is executed on the EDT, and the repaint() method is called, but the changes are not visible in the paintComponent method.
I've also checked that the paintComponent method is being called, and the list is not empty when the method is called.
Is there something I'm missing or a common pitfall related to Swing painting that might be causing this issue? Any suggestions on how to troubleshoot or fix this would be greatly appreciated.
Additional information:
- Java version: 21
- Swing component used: JPanel, JFrame, JButton
- Project Structure: Server/Client system where clients can draw geometry on their respective windows and have the changes be shown on all other client windows as well by broadcasting a DrawingEvent between all the other clients via the Server. This entire problem is happening clientside.
DrawingPanel:
package de.miun;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.swing.*;
import io.reactivex.rxjava3.subjects.PublishSubject;
/**
* <h1>DrawingPanel</h1> Creates a Canvas-object for displaying all graphics
* already drawn.
*
* @author --YOUR NAME HERE--
* @version 1.0
* @since 2022-09-08
*/
@SuppressWarnings("serial")
public class DrawingPanel extends JPanel {
private Drawing drawing;
private java.awt.Point startPoint;
private java.awt.Point endPoint;
private PublishSubject<MouseEvent> observable = PublishSubject.create();
private Shape geoObjToDraw;
private volatile Color currentColor = Color.BLACK;
private String shapeToDraw = "";
private volatile int currentThickness = 2;
private DrawingEvent drawingEvent= new DrawingEvent();
private ClientMain clientMain;
private Graphics2D drawingPanel;
private static final CopyOnWriteArrayList<Shape> shapesToDraw1 = new CopyOnWriteArrayList<>();
private java.util.List<Line2D> freehandLines = new ArrayList<>();
private boolean isDrawn= false;
private Shape test;
public DrawingPanel(ClientMain instance) {
this.clientMain= instance;
addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent event) {
startPoint = event.getPoint();
drawingEvent.setStartX(startPoint.getX());
drawingEvent.setStartY(startPoint.getY());
//endPoint = event.getPoint();
observable.onNext(event);
}
@Override
public void mouseReleased(MouseEvent event) {
System.out.println("mouse Pressed"+ shapeToDraw);
endPoint = event.getPoint();
drawingEvent.setEndX(endPoint.getX());
drawingEvent.setEndY(endPoint.getY());
observable.onNext(event);
try {
setCurrentShape(shapeToDraw, true);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
drawingEvent.setShape(shapeToDraw);
drawShape(geoObjToDraw);
}
});
addMouseMotionListener(new MouseAdapter() {
@Override
public void mouseDragged(MouseEvent event) {
endPoint = event.getPoint();
drawingEvent.setEndX(endPoint.getX());
drawingEvent.setEndY(endPoint.getY());
observable.onNext(event);
if(shapeToDraw.equals("freehand")) {
drawFreeHand(startPoint, endPoint);
startPoint = endPoint;
; }
}
});
// drawing = new Drawing();
}
public void drawFreeHand(java.awt.Point startPoint, java.awt.Point endPoint){
Graphics2D g2d = (Graphics2D) getGraphics();
g2d.setStroke(new BasicStroke(currentThickness));
g2d.setColor(this.currentColor);
g2d.drawLine(
(int) startPoint.getX(),
(int) startPoint.getY(),
(int) endPoint.getX(),
(int) endPoint.getY()
);
}
public void drawFreeHand(double startPointx ,double startPointy , double endPointx, double endPointy){
Graphics2D g2d = (Graphics2D) getGraphics();
g2d.setStroke(new BasicStroke(currentThickness));
g2d.setColor(this.currentColor);
g2d.drawLine(
(int) startPoint.getX(),
(int) startPoint.getY(),
(int) endPoint.getX(),
(int) endPoint.getY()
);
repaint();
}
public void setShapeToDraw(String shape) {
drawingEvent.setShape(shape);
this.shapeToDraw = shape;
}
public void redraw() {
repaint();
}
public void setDrawing(Drawing d) {
drawing = d;
repaint();
}
public Drawing getDrawing() {
return drawing;
}
public void drawShape(Shape geoObjToDraw) {
// super.paintComponent(geoObjToDraw);
if (geoObjToDraw != null) {
Runnable r = () -> {
System.out.println("the object to draw " + geoObjToDraw);
synchronized (shapesToDraw1)
{
if (!shapesToDraw1.contains(geoObjToDraw))
System.out.println("Successfully added shape: " + shapesToDraw1.add(geoObjToDraw));
System.out.println("Already contained shape: "+geoObjToDraw);
}
System.out.println("Is executed on EDT: " + SwingUtilities.isEventDispatchThread());
System.out.println("element count of shapesToDraw1 in drawShape: " + shapesToDraw1.size());
System.out.println("shapesToDraw1 as String in drawShape: " + shapesToDraw1);
isDrawn = true;
repaint();
updateUI();
};
// Make sure code is executed on EDT
if (SwingUtilities.isEventDispatchThread()) r.run();
else SwingUtilities.invokeLater(r);
}
// Repaint the panel to trigger paintComponent
/*
* if (geoObjToDraw != null) { Graphics2D g2d = (Graphics2D) getGraphics();
*
*
* System.out.println("g2d "+ g2d); g2d.setColor(currentColor);
* g2d.setStroke(new BasicStroke(currentThickness)); g2d.draw(geoObjToDraw); }
*/
// Trigger repaint to draw the shape
}
/*@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// drawing.draw(g);
drawShape(geoObjToDraw);
}
*/
@Override
protected void paintComponent(Graphics g) {
System.out.println("Entering paintComponent");
Graphics2D g2d = (Graphics2D) g;
g2d.setStroke(new BasicStroke(currentThickness));
g2d.setColor(currentColor);
System.out.println("g2d here "+ g2d);
System.out.println("shapesToDraw1 to String in paintComponent: "+shapesToDraw1.toString());
System.out.println("element count of shapesToDraw1 in paintComponent: " + shapesToDraw1.size());
if(!shapesToDraw1.isEmpty()) {
System.out.println("shapesToDraw first element: "+shapesToDraw1.get(0));
}
synchronized (shapesToDraw1)
{
for (int i = 0; i < shapesToDraw1.size(); i++)
{
Shape shape = shapesToDraw1.get(i);
if (shape == null) continue;
System.out.println("Drawing shape: " + shape);
g2d.draw(shape);
}
}
/*
* if (geoObjToDraw != null) { g2d.draw(geoObjToDraw); }
*/
System.out.println("Exiting paintComponent");
}
public void clear() {
startPoint = null;
endPoint = null;
geoObjToDraw = null;
repaint();
}
public void setCurrentShape(String shape, boolean sendToServer) throws IOException {
System.out.println("before Switch: " + shape);
DrawingEvent event = this.drawingEvent;
if ( event != null) {
System.out.println("In Switch: " + shape);
switch (shape) {
case "rectangle":
geoObjToDraw = new Rectangle2D.Double(
Math.min(event.getStartX(), event.getEndX()),
Math.min(event.getStartY(), event.getEndY()),
Math.abs(event.getEndX() - event.getStartX()),
Math.abs(event.getEndY() - event.getStartY()));
System.out.println("current shape in currentShape method "+ shape);
//this will recursively call itself. maybe create another similar method from handleIncommingEvent
drawShape(geoObjToDraw);
System.out.println(isDrawn);
if(isDrawn && sendToServer) {
clientMain.sendDrawingEvent(event);
System.out.println("Sent event");
isDrawn=false;
}
break;
case "oval":
geoObjToDraw = new Ellipse2D.Double(
Math.min(event.getStartX(), event.getEndX()),
Math.min(event.getStartY(), event.getEndY()),
Math.abs(event.getEndX() - event.getStartX()),
Math.abs(event.getEndY() - event.getStartY()));
drawShape(geoObjToDraw);
if (isDrawn && sendToServer) {
clientMain.sendDrawingEvent(event);
System.out.println("Sent event");
isDrawn = false;
}
break;
case "line":
geoObjToDraw = new Line2D.Double(
event.getStartX(),
event.getStartY(),
event.getEndX(),
event.getEndY());
drawShape(geoObjToDraw);
if (isDrawn && sendToServer) {
clientMain.sendDrawingEvent(event);
System.out.println("Sent event");
isDrawn = false;
}
break;
case "freehand":
drawFreeHand(event.getStartX(),
event.getStartY(),
event.getEndX(),
event.getEndY());
startPoint = endPoint;
break;
default:
geoObjToDraw = null;
break;
}
}
repaint();
this.updateUI();
}
public void handleIncommingEvent(DrawingEvent event) throws InvocationTargetException, InterruptedException {
System.out.println("handleIncommingEvent"+ event.getShape());
System.out.println("handleIncommingEvent"+ event.getStartX());
SwingUtilities.invokeLater(() -> {
// SwingUtilities.invokeAndWait (() -> {
if (event.getShape() == null ) {
return;
};
this.drawingEvent = event;
try {
System.out.println("setcurrentShape"+ event.getShape());
setShapeToDraw(event.getShape());
setCurrentShape(event.getShape(), false);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
});
// });
}
public void setColour(String colour) {
Runnable r = () ->
{
switch (colour)
{
case "black":
this.currentColor = Color.BLACK;
break;
case "blue":
this.currentColor = Color.BLUE;
break;
case "red":
this.currentColor = Color.RED;
break;
case "yellow":
this.currentColor = Color.YELLOW;
break;
case "green":
this.currentColor = Color.GREEN;
break;
};
};
if (SwingUtilities.isEventDispatchThread()) r.run();
else SwingUtilities.invokeLater(r);
}
public void setThickness(Integer thickness) {
Runnable r = () ->
{
this.currentThickness = thickness;
};
if (SwingUtilities.isEventDispatchThread()) r.run();
else SwingUtilities.invokeLater(r);
};
}
ClientMain:
package de.miun;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class ClientMain extends JPanel{
private ObjectOutputStream outputStream;
private ObjectInputStream objStream;
private InetAddress adress;
private int port;
private Socket socket;
private DrawingPanel drawingPanel;
ClientMain(int port, InetAddress localHost) {
this.drawingPanel = new DrawingPanel(this);
this.adress = localHost;
this.port = 8080;
}
public void listenServerEvents () throws IOException, InvocationTargetException, InterruptedException, ClassNotFoundException
{
System.out.println("listenServerEvents");
while (true) {
Object obj = objStream.readObject();
if (obj instanceof DrawingEvent) {
DrawingEvent event = (DrawingEvent) obj;
System.out.println("listenServerEvents1"+event.getShape() );
System.out.println("listenServerEvents2"+event.getStartX() );
SwingUtilities.invokeLater(() -> {
try {
drawingPanel.handleIncommingEvent(event);
} catch (InvocationTargetException | InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
});
}
}
}
public void connect () throws UnknownHostException, IOException {
socket = new Socket(adress, port);
outputStream = new ObjectOutputStream(socket.getOutputStream());
objStream = new ObjectInputStream(socket.getInputStream());
Thread listenServerThread = new Thread(()->{
try {
try {
listenServerEvents();
} catch (InvocationTargetException | InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
});
System.err.println(socket.toString());
listenServerThread.start();
}
public void sendDrawingEvent(DrawingEvent event) throws IOException {
if (outputStream == null) {
outputStream = new ObjectOutputStream(socket.getOutputStream());
}
System.out.println("sendDrawingEvent "+ event.getShape());
System.out.println("Sending drawing event: " + event.getShape());
System.out.println("Sending drawing event: " + event.getStartX());
System.out.println("Sending drawing event: " + event.getEndX());
outputStream.writeObject(event); // Send the DrawingEvent to the server
outputStream.flush(); // Flush the stream to ensure data is sent immediately
}
public void callMainFrame(ClientMain instance) {
SwingUtilities.invokeLater(() -> {
try {
new MainFrame(instance).setVisible(true);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// new ServerWindow().setVisible(true);
});
}
public static void main(String[] args) throws UnknownHostException, IOException {
int port = 8080;
InetAddress localHost = InetAddress.getByName("127.0.0.1");
ClientMain instance = new ClientMain(port, localHost);
instance.connect();
instance.callMainFrame(instance);
}
}