1

I am working on a concurrent canvas written in Java which will make the users think that they are drawing on the canvas in parallel.

In order to achieve the user perceived parallelism, I get them to create these Runnable objects that I go ahead and put on the EventQueue using SwingUtilities.invokeLater().

In order to test it, I simulated the users using a couple of Threads and added a bit of delay(around 50ms) between each call to invokeLater(), in order to see if it actually looks like the drawing is happening in parallel.

The problem is that while it worked fine with that added delay between the invokeLater() calls, taking out that delay results in the drawings being drawn properly sometimes, partially being drawn and vanishing sometimes and just not being drawn at other times.

I am pretty stumped about what could be going wrong so any if anyone has any ideas, please let me know.

Following is the code with the delay commented out:

public void run(){
    //add tasks on to the event queue of the EDT 
    for(int i = 0; i<numLines; i++){
        DrawLineTask task = new DrawLineTask(g, x1, y1+i, x2, y2+i, lineColor);
        SwingUtilities.invokeLater(task);
//          try {
//    Thread.sleep(new Double(Math.random()*50).longValue());//random sleeping times to             make it appear more realistic
//          } catch (InterruptedException e) {
//              e.printStackTrace();
//          }
    }

Cheers

EDIT: Here is the code for the DrawLineTask as requested. Its pretty simple as its just an extension of the Runnable class that draws a line using the standard Java function at the given parameters.

public class DrawLineTask implements Runnable {
Graphics g;
int x1 = 0;
int y1 = 0;
int x2 = 0;
int y2 = 0;
Color color = Color.BLACK;

public DrawLineTask(Graphics g, int x1, int y1, int x2, int y2){
    this.g = g;
    this.x1 = x1;
    this.y1 = y1;
    this.x2 = x2;
    this.y2 = y2;
}

public DrawLineTask(Graphics g, int x1, int y1, int x2, int y2, Color color){
    this.g = g;
    this.x1 = x1;
    this.y1 = y1;
    this.x2 = x2;
    this.y2 = y2;
    this.color = color;
}

@Override
public void run() {
    g.setColor(color);
    g.drawLine(x1, y1, x2, y2);
}

}
freakii
  • 27
  • 7
  • Just a guess here, but i'm guessing `Thread.sleep` made it easier for the runtime to switch to the EDT to do the drawing, meaning it would happen pretty much immediately. – cHao Jun 02 '12 at 14:47
  • 1
    Agree with the first comment, but I suspect there is another problem as well. Can you post the code of DrawLineTask. – Hakan Serce Jun 02 '12 at 20:01
  • Thanks for your replies guys. As you can see above, the drawing code is really simple and even if it was somehow computationally intensive, the EDT might slow down a bit but it would still end up drawing the lines. – freakii Jun 02 '12 at 22:10
  • However, what you said does seem right as the drawing works perfectly with even a 1ms sleep delay. As soon as I make it 0 ms though, it starts acting up again. – freakii Jun 02 '12 at 22:12

1 Answers1

4

AFAIK, you're not supposed to keep a reference to a Graphics object and draw on it when you want to. Instead, you're supposed to wait for the paintComponent() method to be called by Swing, and do your drawings in this method.

So, your task should just change the state of your component, and ask for an asynchronous or synchronous repaint (using repaint() or paintImmediately()). Swing will then invoke the paintComponent() method with a Graphics object that you can use to paint the appropriate lines, based on the state of your component.

See http://java.sun.com/products/jfc/tsc/articles/painting/ for more details and explanations.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • I understand that. However, I am attempting to decouple the drawing task(command) from the component its meant to be drawn on so that anyone from the outside can generate a drawing task and have it drawn on the component. Having said that though, since the entity generating the tasks will have a reference to the component they are meant to be drawn on, I will go ahead and try your suggestion and get back to you. Thanks! – freakii Jun 02 '12 at 22:26
  • So I did some more testing and it turns out that sometimes the drawings appear and other times they don't is because sometimes a repaint() call is made after the drawing tasks have been drawn, which results in the component being cleared. I understand that one way to ensure that the previous drawings don't get lost is to put my drawing code in paintComponent(), but I expect the users to keep drawing so eventually wouldn't redrawing the drawings over and over again become a bit taxing for the system? – freakii Jun 02 '12 at 23:55
  • Also how can I ensure that previous drawings get kept in the paintComponent(), as they are all wrapped up as tasks? Will I have to maintain some sort of list in the paintComponent() method? – freakii Jun 02 '12 at 23:58
  • I stored all my drawing tasks received in the paintComponent() method in a synchronized list and just iterated over that list to call a draw() method on the tasks. That works perfectly! – freakii Jun 03 '12 at 03:13