views:

179

answers:

3

I need a way to wait until a (Swing) JComponent is fully painted. This actual problem arises from an openmap application: The task is to draw a map (mapBean) with a couple of layers and create an image from that map.

Unfortunatly, and it's clearly documented, the image formatter takes the current state from the map to create the picture, and there's a chance, especially when maps become complex, that the formatter ist called before the mapBean, a JComponent, is painted.

Although explained with this openmap application, the problem is quite general and supposedly Swing related. Right now, I just wait a fixed time (one second) but that does not eliminate the risk of creating incomplete maps...

Edit

Some more details - I have to start with constructing a (OpenMap) MapPanel, which internallz creates a MapBean (JComponent subclass) and a MapHandler. Then I feed the MapHandler with geographical Layers and the Framework starts painting the geographical data 'on' the JComponent type MapBean.

After adding all layers to the Map, I use another framework class to create a JPG image (or: the byte[] that holds the image data). And this can cause problem, if I don't wait: this 'image creator' creates the image from the current state of the map bean, and if I call this 'image creator' to early, some map layers are not painted and missing. Pretty annoying...

+1  A: 

It sounds like you need to synchronize updating your JComponent's underlying data with Swing's paint cycle. You can subclass your JComponent and decorate the paintComponent method, you might also look at ImageObserver.imageUpdate() though i'm not sure if that is going to tell you what you want.

public class DrawingCycleAwareComponent extends MyOtherJComponent {
 private final Object m_synchLock = new Object();

 protected void paintComponent(Graphics g) {
  synchronized (m_synchLock) {
   super.paintComponent(g);
  }
 }

 protected void updateData(Object data) {
  if (SwingUtilities.isEventDispatchThread()) {
    reallySetMyData(data);  // don't deadlock yourself if running in swing
  }
  else {
   synchronized (m_synchLock) {
    reallySetMyData(data);
   }
  }
 }
}
Justin
I don't follow why you don't need the synchronisation in one case there.
Tom Hawtin - tackline
The caller might be updating their components underlying model from another thread, or from the SWMET. We synchronize so that if they call setData() while the component is drawing, it waits until the draw is finished.
Justin
Actual Problem is, that the JComponent subclass (the mapBean) is constructed by the framework, and I don't believe that I can provide a custom implementation (a MapBean subclass)
Andreas_D
+2  A: 

java.awt.EventQueue.invokeLater will allow you to run a task after the paint operation has finished. If it is doing some kind of asynchronous load, then it will be API specific (as MediaTracker does for Image).

Tom Hawtin - tackline
This would work also.
Justin
That's what I try first. Thanks!
Andreas_D
+1 for suggesting the simple and correct approach. :)
David Moles
+1  A: 

You might also try using an off screen buffer to render your image in:

 MapBean map = new MapBean();
 map.setData(...);

 BufferedImage bi = new BufferedImage(...);
 map.paintComponent(bi.getGraphics());

 writeToFile(bi);
Justin
Sounds promising, but I don't have control over the mapBean painting, this is done by the framework, I just add geographical layers
Andreas_D
Which framework, swing? Do you want to make a JPEG of what the mapBean currently shows on the screen, or something else? Calling mapBean.paintComponent(offscreenBuffer) will force it to draw to the offscreen buffer.
Justin