3

There is this article:


Someone jumped the queue! Every now and then it appears that some swing events are processed in the incorrect order in the Event Queue (and nothing gets my blood boiling like when someone cuts into a queue) resulting in strange behavior. This is best illustrated with a small code snippet. Read the snippet below and think carefully in what order you imagine events will take place.

button.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent arg0) {
         repaint();
         doSomething();
    }
});

Most developers would image that repaint() method will result in a painting operation taking place before the doSomething() method call. However this is actually not the case, the call to repaint() will create a new paint event that will be added to the end of the Event Queue. This new paint event will only be processed (dispatched) after the current Action Event has completed. This means that the doSomething() method will be executed before the new Paint Event on the queue is dispatched.

The key point here is that calls to repaint() will create a new paint event that will be added to the end Event Queue and not processed immediately. This means that no events jump the queue (and my blood can remain at its correct temperature).

(source)


My question is, how can I force Swing to do the repaint(); BEFORE doSomething();?

Also, if there were calls to the repaint() method WITHIN the doSomething(); they would be executed only after doSomething(); is completed. Is there a way I can pause the doSomething(); mid-executin, then throw in the reapaint();, get done with it, and then resume doSomething();?

Only solution I have found so far is this(link), but it's not really practical...

Community
  • 1
  • 1
Karlovsky120
  • 6,212
  • 8
  • 41
  • 94
  • 2
    same question as always (you know that I still think you are doing something fundamentally wrony - it shouldn't matter when _exactly_ the component is painted, sometime-after-a-change should be good enough ;-) - why? – kleopatra Aug 05 '13 at 16:21
  • not cutting you lost only all events, methods and notifiers, – mKorbel Aug 05 '13 at 20:56

2 Answers2

6

Well, you and the author of the quoted article are missing the point here. "repaint" method calls simply inform the repaint manager that:

  1. There is a component to repaint (on which you call "repaint")
  2. It should be repainted with (x,y,w,h) clip (if you call "repaint" w/o specifying rect -it will be the whole component bounds, (0,0,w,h))

So it doesn't really matter when the repaint will occur since you might not even notice it if you are calling A LOT of repaints one by one for the same component. That is also why such consequent calls might get merged. Check this example:

private static int repaintCount = 0;

public static void main ( String[] args )
{
    final JComponent component = new JComponent ()
    {

        protected void paintComponent ( Graphics g )
        {
            try
            {
                // Simulate heavy painting method (10 milliseconds is more than enough)
                Thread.sleep ( 10 );
            }
            catch ( InterruptedException e )
            {
                e.printStackTrace ();
            }

            g.setColor ( Color.BLACK );
            g.drawLine ( 0, 0, getWidth (), getHeight () );

            repaintCount++;
            System.out.println ( repaintCount );
        }
    };
    component.setPreferredSize ( new Dimension ( 200, 200 ) );

    JFrame frame = new JFrame ();
    frame.add ( component );
    frame.pack ();
    frame.setLocationRelativeTo ( null );
    frame.setDefaultCloseOperation ( JFrame.EXIT_ON_CLOSE );
    frame.setVisible ( true );

    new Thread ( new Runnable ()
    {
        public void run ()
        {
            try
            {
                Thread.sleep ( 1000 );
            }
            catch ( InterruptedException e )
            {
                e.printStackTrace ();
            }
            System.out.println ( "Starting repaint calls" );
            for ( int i = 0; i < 100000; i++ )
            {
                component.repaint ();
            }
            System.out.println ( "Finishing repaint calls" );
        }
    } ).start ();
}

This is the approximate output you will see (might vary depending on computer speed, Java version and lots of other conditions):

1
Starting repaint calls
2
3
4
5
6
Finishing repaint calls
7
8

"1" - the initial repaint when frame is displayed.
"2,3,4..." - other seven repaints occured due to the calls from a separate non-EDT thread.

"But i have called 100000 repaints, not 7!" - you will say. Yes, repaint manager merged those that were similar and in the same time in the repainting queue. That is made to optimize repaints and speedup the UI overall.

By the way, you don't need to call repaint from EDT since it doesn't perform any real painting and just queue your component update for future. It is already thread-safe method.

To summ up - there should be no situations when you really need to repaint the component right before doing some other action (that could also cause its repaint again). Just call the repaint when you need to repaint the component (with specified rectangle when possible) - repaint manager will do the rest. That works well unless you put some calculations inside the paint method which is totally wrong and might cause a lot of problems.

Mikle Garin
  • 10,083
  • 37
  • 59
  • What about secondaryLoop? http://sellmic.com/blog/2012/02/29/hidden-java-7-features-secondaryloop/ I believe this enables me to do exactly that (it works, I tested it) – Karlovsky120 Aug 05 '13 at 19:06
  • @Karlovsky120 I just wonder what for do you need a repaint with with such a precise time? The only option i can think of is that you perform some variable manipulations in painting code, which is really really bad thing to do as i already said before. – Mikle Garin Aug 05 '13 at 19:13
  • @Karlovsky120 By the way, thanks for the link to topic describing SecondaryLoop - it is really awesome tool for UI development! – Mikle Garin Aug 05 '13 at 19:35
  • I'm adding elements to screen. How many elements I add depends on how much space there is. So I do it in a loop. And I need to paint everyting on screen after each cycle so I could test for condition. I could calcualte in advance, but this is a bit simpler (less code) method. And it looks better for the user, animation-like... – Karlovsky120 Aug 05 '13 at 20:18
  • SecondaryLoop (quite useless when invokeAndWait is there long time) is only torzo from intention to implement multi EDT in Java7, disagree with this answer, code & comments here, probably simulating only processor time & consuptions untill quite endless Thread.sleep(int) ende, job for AWT container with OpenG(C)L e.i. – mKorbel Aug 05 '13 at 20:51
  • 1
    @Karlovsky120 And why you don't just calculate what space will the painted elements take ahead without relying on the painting mechanism? If you are the one who painting something you should know the element sizes for sure. – Mikle Garin Aug 06 '13 at 06:51
2

repaint() adds a new paint request to the end of the queue of the Event Dispatch Thread (EDT). So making several calls to repaint() within doSomething() will only repaint after doSomething() completes. (I assume doSomething() is always called from the EDT. Your code example calls doSomething() from insideactionPerformed which is always called from the EDT.)

The reason paint() requests are queued up is to decrease the number of times a component is painted. Queuing up repaint() requests allows several methods to mark different components as dirty so they can all be repainted at the same time in one expensive paint() operation.

If you really want to force a repaint immediate, there are methods like paintImmediately(Rectangle r) and paintImmediately(int x, int y,int w,int h)but you will have to know the dimensions to repaint.

You can also always call paint(Graphics g) yourself if you have a reference to the current Graphics that the Swing component is using. (You can also use this technique to create your own graphics object from a Image object, if you want to take a screenshot and write it a picture file).

dkatzel
  • 31,188
  • 3
  • 63
  • 67
  • paintImmediately caused awfull exception from RepaintManager (current JVM instance can be addept for TaskManager), if EDT isn't empty, is required, must be tested, and called only if isEventDispatchThread returns false – mKorbel Aug 05 '13 at 20:44