2

I've been looking around and working on making a JPanel able to have an image background. I know there is a ton of information reguarding this ( example 1 and example 2 ). Neither of these are exactly what I'm looking for.

What I am looking for, is a way to just add a function or the functionality to set a background image on a JPanel while maintaining everything else. The problem that I have right now is that I have a few classes that extend JPanel and when I change them to extend my new ImagePanel class, the constructor fails (obviously, the parameters are different). I have the constructor for one of those files going super( new BorderLayout() ); and the only way I can get it to function so far is to ditch either the layout, or the background image. I can't get both to work together.

Here's the latest desperate attempt:

import java.awt.*;
import javax.swing.JPanel;

class ImagePanel extends JPanel {

  private Image img;

  public ImagePanel(Image img, LayoutManager layout) {
    this.img = img;
    Dimension size = new Dimension(img.getWidth(null), img.getHeight(null));
    setPreferredSize(size);
    setMinimumSize(size);
    setMaximumSize(size);
    setSize(size);
    setLayout(null);
    super.setLayout(layout);
  }

  public void paintComponent(Graphics g) {
    g.drawImage(img, 0, 0, null);
  }

}

If anyone could help me out, or point me towards what I'm looking for, that would be great. I don't need any super crazy functionality like background scaling or anything like that. I just want an image to be the background, and for it to maintain its functionality in all other ways.

Thanks.

Community
  • 1
  • 1
mandelbug
  • 1,548
  • 5
  • 20
  • 32
  • Do you strictly want a `JPanel`, or can other components be considered as well? – afsantos May 16 '13 at 20:34
  • Well, I would like it to just be a `JPanel`. That seems easiest, functionality-wise, but then again, I'm coming here for a reason. Whatever works I guess. Had trouble with the `JLabel` icon method, so I would prefer a "super" `JPanel` to take care of it. – mandelbug May 16 '13 at 20:40
  • You could simpply use a `JLabel` and set an appropriate `LayoutManager` on it. Btw, don't call `setPreferredSize(size); setMinimumSize(size); setMaximumSize(size); setSize(size); setLayout(null); ` (using those methods is really not recommended). Instead use an appropriate `LayoutManager`. – Guillaume Polet May 16 '13 at 20:43
  • Both of your examples are bad. They should be calling super.paintComponent in the paintComponent method. Can your provide more sample code (maybe the contractor signatures?) – MadProgrammer May 16 '13 at 20:44
  • Like I said, the setXXXXXXX()` methods were my latest desperate attempt. I'll try that really quick. Will provide more code in a second. – mandelbug May 16 '13 at 20:44
  • You can also take a look at this [example which paints a background image](http://stackoverflow.com/questions/13401109/java-add-background-image-to-frame/13401871#13401871) – Guillaume Polet May 16 '13 at 20:49

2 Answers2

2

Constructors are not transferable between decedents. That is, if you extend a class and don't provide the same constructors as the parent, then you can't use those constructor signatures.

For example, in your sample source code, you couldn't call new ImagePane(new BorderLayout()) as ImagePane doesn't have a constructor matching that signature, despite the fact that JPanel does.

This is important, as it allows you to funnel users to use a specific initialisation path that may be unique to your version of the class.

If you want to re-use the available constructors from the parent class, then you need to provide those signatures within your own class and or call super(...); with the required parameters

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.LayoutManager;
import javax.swing.JPanel;

public class ImagePanel extends JPanel {

    private Image img;

    public ImagePanel(LayoutManager layout) {
        super(layout);
    }

    public ImagePanel(Image img, LayoutManager layout) {
        this(layout);
        this.img = img;
    }

    public Image getImage() {
        return img;
    }

    public void setImage(Image value) {
        if (img != value) {
            Image old = img;
            img = value;
            firePropertyChange("image", old, img);
            revalidate();
        }
    }

    @Override
    public Dimension getPreferredSize() {
        Image img = getImage();
        return img == null ? super.getPreferredSize() : new Dimension(img.getWidth(this), img.getHeight(this));
    }

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (img != null) {
            g.drawImage(img, 0, 0, this);
        }
    }

}

I'd also recommend you take a read through Performing Custom Painting for more details about custom painting...

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • 2
    Shouldn't the `getPreferredSize()` method return the maximum dimension in case there are children which take more space than the image? I agree this may be a bit nitpicking ;-) – Guillaume Polet May 16 '13 at 21:27
  • @GuillaumePolet Its a good nit pick and worthy of not. It will come down to what the OP wants to do. I was (hopefully) demonstrating the use of getPreferredSize ;) – MadProgrammer May 16 '13 at 21:53
  • _I was (hopefully) demonstrating the use of getPreferredSize_ You sure were. And I agree that it will come down to what the Op wants to do. +1 – Guillaume Polet May 16 '13 at 22:05
1

If you're open to suggestions, you might consider a JLayeredPane.

These containers have layers, and each layer is capable of storing components.
So, you may have a JPanel, or JLabel for that matter, with your background image on the bottom layer, and place whatever else on another JPanel, in a higher layer.

If you want to stick to your ImagePanel, you probably should call super.paintComponent as well.

class ImagePanel extends JPanel {
        ...

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawImage(img, 0, 0, this);
    }
}
afsantos
  • 5,178
  • 4
  • 30
  • 54
  • 1
    you should draw your background image after calling `super.paintCOmponent` and you should pass in `this` rather than `null` as an `ImageObserver`. Calling `super.paintComponent` has nothing to do with children components. It only performs what the default UI delegate does by default. For an opaque JPanel, it fills the panel with the background color, for a `JLabel` it paints the text and/or image, etc... – Guillaume Polet May 16 '13 at 20:50
  • Correct me if I'm wrong, but wouldn't a call to `super.paintComponent` prior to `drawImage` render the image *over* the inner components, hence making a foreground image, instead? – afsantos May 16 '13 at 20:53
  • 1
    No. `paintComponent` only paints the current component. the painting of the children is carried on by `paintChildren`. The method `paint` will call (approximatively), in that order: `paintComponent`, `paintBorder` and then `paintChildren`. And so, as your code stands, if your `ImagePanel` is opaque, you will never see the background image. – Guillaume Polet May 16 '13 at 20:55
  • I stand corrected, I just checked that in the docs. I'll edit my answer to reflect these facts. – afsantos May 16 '13 at 20:57
  • Worked beautifully. Overlooked something really small. Sometimes its good to have a second set of eyes on something. Thanks a lot everyone. – mandelbug May 16 '13 at 22:17