Java wait for component to be painted
Asked Answered
S

3

5

I'm trying to create a program in Java that would display set of images one after another adjusting the size of the frame for each one. I'm extending JPanel to display an image like this:

public class ImagePanel extends JPanel{

String filename;
Image image;
boolean loaded = false;

ImagePanel(){}

ImagePanel(String filename){
    loadImage(filename);
}

public void paintComponent(Graphics g){
    super.paintComponent(g);
    if(image != null && loaded){
        g.drawImage(image, 0, 0, this);
    }else{
        g.drawString("Image read error", 10, getHeight() - 10);
    }
}

public void loadImage(String filename){
    loaded = false;         
    ImageIcon icon = new ImageIcon(filename);
    image = icon.getImage();
    int w = image.getWidth(this);
    int h = image.getHeight(this);
    if(w != -1 && w != 0 && h != -1 && h != 0){
        setPreferredSize(new Dimension(w, h));
        loaded = true;
    }else{
        setPreferredSize(new Dimension(300, 300));
    }
}

}

Then in event thread I'm doing main work:

        SwingUtilities.invokeLater(new Runnable(){

        @Override
        public void run(){
            createGUI();
        }
    });

In createGUI() I'm going through the set of images:

        ImagePanel imgPan = new ImagePanel();
    add(imgPan);

    for(File file : files){
        if(file.isFile()){
            System.out.println(file.getAbsolutePath());

            imgPan.loadImage(file.getAbsolutePath());
            pack();

            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } 
        }
    }

The problem is that my program does the resizing properly, so images are loaded correctly but it doesn't display anything. If I display only 1 image it works also it works for the last image. I think the problem is that Thread.sleep() is called before image painting is finished.

How can I wait for my ImagePanel to finish painting and start waiting after that? Or is there another way to solve the problem?

Thanks! Leonty

Sizar answered 9/12, 2010 at 19:21 Comment(0)
N
6

All of your code is executing on the Event Dispatch Thread. This effectively causes all user interaction to sleep, since the Event Dispatch Thread is responsible for all user interaction - both input (events) and output (painting).

You need to get your waiting to happen outside of the EDT. You need to know how to execute events on and off the EDT. You do this by creating a new Runnable and then either calling new Thread(runnable) to execute it off the EDT or SwingUtilities.invokeLater(runnable) to make it execute on the EDT. All interaction with Swing components need to take place on the EDT, since Swing objects are not thread safe. All sleeping, waiting, file access, network access, database access, or anything else that may block for indeterminate periods of time must take place off the EDT.

There are many questions on Stack Overflow having to do with the Event Dispatch Thread. I recommend you review these to find more information and code samples to do what you are looking to do in different ways.

Nerine answered 9/12, 2010 at 19:34 Comment(1)
Thanks! It put me on the right track! I now execute code in main thread and all operations are now synchronous so waiting happens only after painting is finished.Sizar
C
1

Instead of making the thread sleep use a timer and kick off the next image as an event.

Clarisclarisa answered 9/12, 2010 at 19:31 Comment(1)
I tried that and it worked but I needed to use pack() to resize window for every image and I had the same problem - I needed to know when painting is finished to call 'pack()'.Sizar
M
1

It sounds like you want to images appear one by one as they gets loaded (ie as in a web page), is that correct? If so, you wouldn't get that effect with your current code because your UI won't be updated until all images are loaded, this is because you're loading images on EDT (Event Dispatch Thread) which is the same thread that'll do the painting. Painting occurs "when there's time", which in this case means after all images are loaded.

To solve your problem, I'll suggest you create a sub class of SwingWorker like:

public class MyImageLoader extends SwingWorker<Void, String>
{
    // Is executed in background thread, EDT can meanwhile load and paint images
    @Override
    protected Void doInBackground() throws Exception
    {
        for(File file : files)
        {
             if(file.isFile()) // Maybe some more check to see if it's an image
             {
                 publish(file.getAbsolutePath());
                 Thread.sleep(500);
             }
        }
    }

    // Executed on EDT
    @Override
    protected void process(List<String> filePaths)
    {
        for(String path : filePaths)
        {
            // Load ImageIcon to UI
        }
    }
}
Migdaliamigeon answered 9/12, 2010 at 19:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.