Search code examples
javaawtframe

Wait for AWT repaint to finish


I have a Frame that is all one color, but has some text in the corner. I set the color, actually read the color from the monitor, and then do some computations based on those measurements.

The problem is, calling repaint() causes the Frame to be painted after I do the measurements. I'm assuming this is due to repaint() delegating to the EDT, but I'm getting incorrect results due to the measurements occurring before/during the actual painting work.

My initial thought was to put a listener on paint completion, but I repaint to update the text much more frequently than I do for the color and I don't want to listen to those events. How can I wait for the actual painting task to finish before taking my measurement?


Solution

  • Amazing what you can find...

    EventQueue.invokeLater(new Runnable() {
        @Override
        public void run() {
    
            Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
                @Override
                public void eventDispatched(AWTEvent event) {
    
                    PaintEvent pe = (PaintEvent) event;
    
                    String type = "";
                    if (pe.getID() == PaintEvent.PAINT) {
                        type = "PAINT";
                    } else if (pe.getID() == PaintEvent.PAINT_FIRST) {
                        type = "PAINT_FIRST";
                    } else if (pe.getID() == PaintEvent.PAINT_LAST) {
                        type = "PAINT_LAST";
                    } else if (pe.getID() == PaintEvent.UPDATE) {
                        type = "UPDATE";
                    }
    
                    System.out.println(type + "; pe.UpdateRec = " + pe.getUpdateRect() + "; pe.component = " + pe.getComponent());
    
                }
            }, AWTEvent.PAINT_EVENT_MASK);
    
            JFrame frame = new JFrame("Testing");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setSize(200, 200);
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
    
    
        }
    });
    

    Now, because repaint requests can come think and fast, I'd be tempted to place a small "delay" in that would fired shortly after the last request has completed...

    private Timer updateTimer;
    
    // ...
    
    EventQueue.invokeLater(new Runnable() {
        @Override
        public void run() {
    
            updateTimer = new Timer(250, new ActionListener() {
    
                @Override
                public void actionPerformed(ActionEvent e) {
                    // Update compulations here...
                }
            });
            updateTimer.setRepeats(false);
            updateTimer.setCoalesce(true);
    
            Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
    
                @Override
                public void eventDispatched(AWTEvent event) {
                    updateTimer.restart();
                }
            }, AWTEvent.PAINT_EVENT_MASK);
        }
    }
    

    The idea is to allow at least 250 milliseconds between the last repaint request and the start of your compilations. You might like to play around with these values a bit and see what suits you...

    UPDATE

    You could also try JComponent.paintImmediately

    Paints the specified region in this component and all of its descendants that overlap the region, immediately.

    It's rarely necessary to call this method. In most cases it's more efficient to call repaint, which defers the actual painting and can collapse redundant requests into a single paint call. This method is useful if one needs to update the display while the current event is being dispatched.