Kevin1031
Kevin1031

Reputation: 9

(Java) repaint() executes at random times

While working with GUI in Java, I have noticed that the repaint() method of Component class exhibits unusual behaviors. The code below updates the content pane after the printing of 1 and before 2, or so was my intention.

public class ErrorTest {

    public static void main(String[] args) throws InterruptedException {

        JFrame F = new JFrame();
        F.setSize(100,100);

        Panel P = new Panel();
        F.setContentPane(P);
        F.setVisible(true);

        while(true) {
            System.out.println("\n1");
            P.repaint();
            System.out.println("2");
            Thread.sleep(100);

        }
    }
}

class Panel extends JPanel {

    @Override
    protected void paintComponent(Graphics G) {
        System.out.println("painted");
        G.drawOval(10, 10, 20, 30);
    }
}

If Thread.sleep(100); is included, The output of this code is:

1
2
painted

1
2
painted

and so on. If not, the output is:

1
2

1
2
painted

and so on, usually just printing 1 and 2 and rarely printing painted in random locations. The output I'm looking for is:

1
painted
2

1
painted
2

repeated. It seems to be the case that this update() method is being called at the beginning of each iteration, regardless of where I actually write it. There also seems to be some delay before the method is executed. What can I do to achieve my desired output?

Upvotes: 0

Views: 146

Answers (1)

rzwitserloot
rzwitserloot

Reputation: 103903

TL;DR: What you want is impossible.

There is a thing called the 'EDT' - the Event Dispatch Thread. The swing (and most UI libraries) work is that there is a single thread (the EDT), and the system works by injecting events into a queue; the thread continually takes the topmost event off the queue and deals with it.

User presses a button? The job of invoking any actionlistener defined on that button is put in the queue. It means that any code in an event listener is run in the EDT.

That repaiont call? It doesn't call paintComponent directly at all. It injects an event in the queue to do the repaint, and the repaint is then done in the EDT, and THAT will call paintComponent.

(The rule for swing, and other frameworks, is that you cannot edit any widget from any thread except the EDT, see the documentation of swing).

In other words, your main method is one thread, your EDT is another, and the repaint() call is communicating from one thread to another: Please run this event, and will then continue.

A further rule for the EDT is that it must never 'block' (wait on disk, network, or another thread, or pause execution for any reason). If you do block on the EDT, the app looks completely unresponsive, and soon the OS will pop up a notification that the app appears to have crashed. That's because various GUI interactions, such as updating the mouse cursor to a different shape if floating over a text box, are also dealt with by the EDT, so if the EDT is frozen, none of that works.

Thus, we arrive at the conclusion: What you want cannot be done - the only way to get a reliable 'this occurs before that' where 'this' is a job in one thread and 'that' is a job in another, is with locking, which causes freezes, which is not an acceptable thing to do in an EDT*.

But, nobody releases an app that 'invokes the paint method before continuing on after repaint is invoked'. Clearly you had some job you wanted to do, you thought that having repaint() and paint() be synced up is the way to get there, and now you are asking questions about that second thing. But it was the wrong strategy - there is an answer, but this 'sync up paint and repaint calls' is a dead end.

Secondarily, the paintComponent method is invoked anytime it is needed to paint your component. Doing something as simple as dragging another application's window across your application's window will cause events that will call paintComponent to do the job. You have no control over such actions. So even if you wave your magic wand and do the impossible (sync up repaint() and paintComponent() calls), you'll still find a bunch of paintComponent calls that aren't caused by repaint() at all.

*) You can have your main thread which calls repaint() wait on the EDT to have done at least one paint, I guess, with a latch, but this doesn't sound like a good plan, you have no guarantees when the repaint actually occurs, and you have no idea if some call to paintComponent is in fact because you called repaint, or because the OS decided it was time to ask your app about which pixels it wants to show.

Upvotes: 1

Related Questions