views:

29

answers:

4

I have a funny problem trying to export custom Java JPanels to a PNG file. The export process of the components I've been writing up until now have worked flawlessly.

My JPanels include custom-written JComponents (e.g., override paintComponent(Graphics g) and write what I have to).

The export process looks like the following (of the extended JPanel I have):

 public void export(File file, int width, int height)
  throws IOException
{
     Dimension size = getSize();

     BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
     Graphics2D g2 = image.createGraphics();
     draw (g2, new Rectangle (0, 0, width, height));

     try {
         ImageIO.write(image, "png", file);
     } catch (FileNotFoundException e) {
         throw new IOException ("Unable to export chart to ("
               + file.getAbsolutePath() + "): " + e.getLocalizedMessage());
     } finally {
         g2.dispose();
     }
}

The 'draw()' method above causes all of the JPanel's child components to be re-drawn using the new size of the image to be exported. Works very well.

The problem I have today is that I have one custom JPanel that includes some Swing components (a JScrollPane wrapping a JEditorPane). This JPanel includes one of my custom JComponents and then this second JComponent with the JScrollPane on it.

About 75% of the time, this second JComponent with the JScrollPane is not positioned correctly in the exported image when I perform the export. It is positioned at Point (0, 0) and the size is what it looks like on the screen. The 'draw()' method for this JComponent looks like the following:

public void draw(Graphics2D g2, Rectangle componentArea) {

    scrollPane.setBounds(componentArea);
    textArea.setText(null);
    sb.append("<html>");
    sb.append("<h1 style=\"text-align:center;\">" + "XXXXXXXXX  XXXXXXX" + "</h1>");
    textArea.setText(sb.toString());

    super.paintComponents(g2);
}

But about 25% of the time this works - this JComponent with the scrollpane is correctly positioned in my exported image. The re-draw the componment works.

It is like there is some double-buffering going on here that I can't figger out....

Ideas?

A: 

Do you by any change modify the Transform object of the provided Graphics object in your custom Component? If you do make sure to save it first and then modify a new instance for your purposes and when you are done, set the old Transform back.

Savvas Dalkitsis
No, i don't. The custom JComponents have just painted themselves on the graphics context of the new image. they did (do) all of the low-level positioning of bits themselves using the Rectangle passed in.
redBeard
A: 

I would try invoking the paint() method, not paintComponents().

Maybe because you are setting the text of the editor pane the text hasn't been properly parsed and the Document isn't in a final state when you attempt to paint the component. Or maybe because you are dynamically setting the bounds of components you have problems. Try wrapping the super.paint() method in a SwingUtilities.invokeLater().

The ScreenImage class is what I use to create images. But I've always used it to create images of static GUI's. That is I don't alter the bounds of components at the same time a try to create an image.

camickr
Hmmm, didn't work. Same results.....
redBeard
A: 

Layout of Swing components usually occurs lazily rather than immediately so that might be causing your intermittent behaviour. You could try calling scrollPane.doLayout() directly - usually this is a bad idea but it should guarantee that scrollPane is laid out before you paint it.

Also for painting to an off-screen image you should probably call printAll(g) rather than paintComponents(g) as that avoids double buffering issues.

Russ Hayward
Hmm, no changes. same results....
redBeard
A: 

Found a solution!! Your comments about 'scheduling painting events' rang around in my head for a bit. I had a problem like this some years ago and forgot about that. Old age will do that....

The solution is to wrap the 'draw()' method in a 'SwingUtilities.invokeAndWait()'. Voila! My 'export()' method now looks like:

    public void export(File file, final int width, final int height)
    throws IOException
{

    BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
    final Graphics2D g2 = image.createGraphics();

    //  Must wait for the bloody image to be drawn as Swing 'paint()' methods
    //  merely schedule painting events.  The 'draw()' below may not complete
    //  the painting process before the 'write()' of the image is performed.

    //  thus, we wait....

    try {
        SwingUtilities.invokeAndWait(new Runnable() {
            public void run() {
                draw (g2, new Rectangle (0, 0, width, height));
            }
        });
        ImageIO.write(image, "png", file);
    } catch (FileNotFoundException e) {
        throw new IOException ("Unable to export chart to ("
                + file.getAbsolutePath() + "): " + e.getLocalizedMessage());
    } catch (InterruptedException e) {
        e.printStackTrace();
        throw new IOException ("Unable to export chart to ("
                + file.getAbsolutePath() + "): " + e.getLocalizedMessage());
    } catch (InvocationTargetException e) {
        e.printStackTrace();
        throw new IOException ("Unable to export chart to ("
                + file.getAbsolutePath() + "): " + e.getLocalizedMessage());
    } finally {
        g2.dispose();
    }
}

Whew!

redBeard
That will hang the GUI if called on the EDT! Which makes me think the issue is really that the export code is calling paint on a non-EDT thread. Probably, somewhere deep in the bowels of Swing, some method determined it was not on the EDT and make an invokeLater call, thereby deferring the actual painting.
Devon_C_Miller