Width and height of a JPanel are 0 (Specific Situa

2020-02-16 03:45发布

问题:

Please pardon me if this is hard to follow, but I have a specific problem that I need help solving. I have done a ton of research, and I have tried numerous solutions but none of them are working.

My issue is that I have an ImagePanel class that is extending JPanel (code below), this class needs to use width and height to scale images (I am making a program where users can create custom tutorials including images). When I instantiate this I get an error saying that the width and height must be nonzero. I understand that this is because the layout manager has not yet passed the ImagePanel a preferred size, however I do not know how to get that size to the panel. The ImagePanel is inside a JPanel which is inside a JSplitPane inside of a JScrollPane inside of a JPanel inside of a JTabbedPane inside of a JSplitPane inside of a JFrame. A graphical representation of this in decreasing container order is as follows:

  1. JFrame (GridLayout)
  2. JSplitPane (Default SplitPane Layout)
  3. JTabbedPane (Deault JTabbedPane Layout)
  4. JPanel (GridLayout)
  5. JScrollPane (Default ScrollPane Layout)
  6. JSplitPane (Default SplitPane Layout)
  7. JPanel (GridLayout);
  8. ImagePanel

The code for the ImagePanel is as follows:

import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JPanel;

public class ImagePanel extends JPanel{

    private BufferedImage i;
    private ImageIcon miniature;
    private Image paint = null;

    public void createImage(String path){
        try {                
            i = ImageIO.read(new File(path));
        } catch (IOException ex) {
            ex.printStackTrace();
        }

        if(i != null){
            int width = (int)((double)i.getWidth() * ((double)getWidth()/i.getWidth()));
            int height = (int)((double)i.getHeight()*i.getHeight()/i.getWidth()*((double)this.getHeight()/i.getHeight()));
            miniature = new ImageIcon(i.getScaledInstance(width, height, Image.SCALE_SMOOTH));
            paint = miniature.getImage();
        }

    }


    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);

        if (paint!=null){
            g.drawImage(paint, 0, 0, null);}     
    }

}

How can I get the proper size to the ImagePanel. I would like the image to change size with the size of the JFrame, which is why I don't just use setPreferedSize();.

回答1:

There are at least two ways this might be achieved, the first would be to allow the paintComponent to check the state of the paint and rescale the image appropriatly when it is null

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);

    if (i != null && paint == null){
        generateScaledInstance();
    }
    if (paint != null) {
        g.drawImage(paint, 0, 0, this);
    }
}

This will work because paintComponent should never be called unless the component has a size greater than 0 and is attached to a native peer (on the screen).

This is not a great idea as scaling can take time and you don't want to slow down the paint process if you can avoid it.

You could use a ComponentListener attached to the ImagePanel and monitor the componentResized event

addComponentListener(new ComponentAdapter() {
    @Override
    public void componentResized(ComponentEvent e) {
        if (i != null) {
            generateScaledInstance();
        }
    }
});

This might be called a number of times in quick succession, so be careful.

In this case, what I tend to do is use a javax.swing.Timer set to small delay to coalse the updates down to as few a calls as possible, for example...

private Timer resizeTimer;
//...
// Probably in you classes constructor
resizeTimer = new Timer(250, new ActionListener() {
    public void actionPerformed(ActionEvent evt) {
        // Actually perform the resizing of the image...
        generateScaledInstance();
    }
});
// Don't want a repeating event...
resizeTimer.setRepeats(false);

//...
public void componentResized(ComponentEvent evt) {
    resizeTimer.restart();
}

This allows componentResized to be called a number of times in quick succession, but if the time between exceeds 250 milliseconds, the generateScaledInstance can be called, as an example...

You should also provide a preferredSize value of a non 0 size by default (remember, the default preferred size of a panel is 0x0). Depending on the layout manager, this could be ignored, but is generally used as a basis for most layout managers...



回答2:

I would like the image to change size with the size of the JFrame

Instead of doing custom painting in your own component you can use Darryl's Stretch Icon. The Icon will automatically be resized based on the space available.

which is why I dont just use 'setPreferedSize();'.

If you do use custom painting then you should NOT use setPreferredSize(). You SHOULD be overriding the getPreferredSize() method to return the size of the image. Remember the preferred size is just a suggestion to the layout manager. The layout manager can use or ignore the size.

If you want to scale the image automatically in your paintComponent() method then the code should be:

Dimension d = getSize();
g.drawImage(paint, 0, 0, d.width, d.height, this);

So the real challenge in your code (no matter which solution you choose) is to make sure you are using a layout manger that will give all available space to your component so the image can be scaled automatically.