0

I have a JFrame. It uses a JPanel as its content pane, and that JPanel uses GridBagLayout as its LayoutManager. That JPanel contains two more items: a button, and another JPanel. On program start, an image is loaded from file into the lowest-level JPanel as a BufferedImage using ImageIO.read(...). Here is where everything goes to pieces.

The image loads correctly, I can see a small corner of it on screen (14px square as specified in debugger). There is nothing I can figure out that will cause the layout to grow and fit the entire image in the lowest level JPanel on screen. The image in debuggers shows correct size of 500px. The preferred size of the CardImagePanel shows up correctly as the same size as the image. But the layout will not respect the preferred size unless I manually set the CardImagePanel size using setSize(...) which I'm pretty sure is not supposed to be necessary with GBL.

I have tried putting revalidate() and repaint() calls on every single JFrame, JPanel, layout, grid bag, image, etc throughout the entire program and just can't find the correct place or time to call them to make this thing work. Currently I've been trying to just let the image load incorrectly and use the button to force revalidation and repaint, but even this explicit call is not doing anything.

I'm losing my mind, I'll do anything to get this thing working.

Here is all my code for the whole stupid thing (minus imports and package specification.

P1s1.java:

public class P1s1 {

    public static void main(String[] args) {
        // TODO code application logic here
        build();
    }

    public static void build()
    {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(new Dimension(640, 480));
        frame.setContentPane(new GuiPanel(frame));
        frame.setVisible(true);
    }
}

GuiPanel.java:

public class GuiPanel extends JPanel {

    JFrame parentFrame;
    JButton imageLoaderButton;
    CardImagePanel cardImagePanel;
    LayoutManager layout;
    GridBagLayout gridBagLayout;
    GridBagConstraints constraints;

    public GuiPanel(JFrame frame)
    {
        parentFrame = frame;

        constraints = new GridBagConstraints();
        gridBagLayout = new GridBagLayout();

        layout = gridBagLayout;
        this.setLayout(layout);

        this.setBorder(BorderFactory.createLineBorder(Color.black));

        setupImageLoaderButton(imageLoaderButton);

        cardImagePanel = new CardImagePanel();
        this.add(cardImagePanel);
    }

    private void setupImageLoaderButton(JButton button)
    {
        button = new JButton("Click to load image!");
        ActionListener imageLoaderListener;
        imageLoaderListener = new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent ae) {
                System.out.println("Button clicked.");

                cardImagePanel.revalidate();
                cardImagePanel.repaint();

                GuiPanel.this.revalidate();
                GuiPanel.this.repaint();

                parentFrame.revalidate();
                parentFrame.repaint();
            }
        };
        button.addActionListener(imageLoaderListener);
        this.add(button);
    }
}

CardImagePanel.java:

public class CardImagePanel extends JPanel {

    BufferedImage cardImage;

    public CardImagePanel()
    {
        this.setBorder(BorderFactory.createLineBorder(Color.black));

        try {
            cardImage = ImageIO.read(new File("c:\\dev\\cards\\2_of_clubs.png"));
            this.setPreferredSize(new Dimension(cardImage.getWidth(), cardImage.getHeight()));
        } catch (IOException ex) {
            System.out.println("Exception trying to load image file.");
        }
    }

    // The getPreferredSize() override was suggested by MadProgrammer.
    // It did not solve the issue, but see MadProgrammer's updated,
    // accepted answer below for the correct solution.  The rest of the
    // code reflects my original attempt to solve the issue.
    @Override
    public Dimension getPreferredSize()
    {
        return cardImage != null ? new Dimension(cardImage.getWidth(), cardImage.getHeight()) : super.getPreferredImage();
    }

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

        g.drawImage(cardImage, 0, 0, this);
    }
}
Steverino
  • 2,099
  • 6
  • 26
  • 50

2 Answers2

3

You CardImagePanel has no preferred size, so the layout manager doesn't know how to handle the size properly.

A couple of solutions:

  1. There is no need to create a custom class to display the image. Just use a JLabel to display the image. The JLabel will return the preferred size of the image.
  2. If you do use the CardImagePane, then you need to override the getPreferredsize() method of the CardImagePanel to return the size of the image.
camickr
  • 321,443
  • 19
  • 166
  • 288
  • Definitely much easier with a `JLabel` – MadProgrammer Sep 21 '15 at 05:18
  • Setting the preferred size of the JPanel manually doesn't actually set it? When I review the preferred size of the JPanel in the debugger after loading the image, I do see it reporting a preferred size of the dimensions I expect, but I'll try this solution anyway to see if it helps. – Steverino Sep 21 '15 at 05:26
  • I should have added that this is for a school project. It is a project constraint that we extend JPanel to create something like a CardImagePanel to house our image. As for item 2, I tried this and it had no effect. I'll update my post to reflect the change. – Steverino Sep 21 '15 at 05:36
  • @fts_acer, `As for item 2, I tried this and it had no effect.` - then your code is wrong. Post your code showing what your tried. – camickr Sep 21 '15 at 14:36
  • @camickr the answer marked as accepted solved the issue. The code in my post was already updated to reflect updates in my attempts. – Steverino Sep 21 '15 at 21:55
  • @fts_acer, the code in your current posting was copied from MadProgrammers suggestion. When you made the comment I quoted above, you did not post the code that your tried yourself. So how do you expect us to guess what you did wrong if you don't post the code that you tried and didn't work? – camickr Sep 22 '15 at 00:34
  • No, the code in my current posting is mine, with the exception of modifications on 3 lines as per MadProgrammer's first, failed suggestion which had no effect on symptoms. MadProgrammer posted his suggestion based on my original code, I didn't copy his code wholesale. I don't know why you're so quick with the accusations, you should be careful with that. – Steverino Sep 22 '15 at 05:18
3

GridBagLayout relies on a component telling it what size it would like to be (along with it's minimum and maximum size when it's relievent). You need to override the getPreferredSize method of the CardImagePanel, returning the size you would like the component to be

public class CardImagePanel extends JPanel {

    BufferedImage cardImage;

    public CardImagePanel() {
        this.setBorder(BorderFactory.createLineBorder(Color.black));

        try {
            cardImage = ImageIO.read(new File("c:\\dev\\cards\\2_of_clubs.png"));
        } catch (IOException ex) {
            System.out.println("Exception trying to load image file.");
        }
    }

    @Override
    public Dimension getPreferredSize() {
        return cardImage != null ? new Dimension(cardImage.getWidth(), cardImage.getHeight()) : super.getPreferredSize();
    }

    @Override
    public Dimension getMinimumSize() {
        return getPreferredSize();
    }

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

        g.drawImage(cardImage, 0, 0, this);
    }
}

Have a look at How to Use GridBagLayout for more details

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • I tried your suggestion for kicks but it had no effect. As explained in my post the preferred size is returning the expected dimensions already. I'll spend some time reading your linked article for further hints. – Steverino Sep 21 '15 at 05:34
  • Your code works okay for me, what might be of concern if what happens when the image is to large for the available space of the container, which means it will resort to it's minimum size instead. I'd also recommend changing `frame.setSize(new Dimension(640, 480));` to `frame.pack()` – MadProgrammer Sep 21 '15 at 05:39
  • Is it considered good practice to set the minimum size manually to the image size in this sort of situation? Edit: sorry, just saw your code update. That looks promising. – Steverino Sep 21 '15 at 05:43
  • If you mean by calling `setMinimumSize`, then no, see [Should I avoid the use of set(Preferred|Maximum|Minimum)Size methods in Java Swing?](http://stackoverflow.com/questions/7229226/should-i-avoid-the-use-of-setpreferredmaximumminimumsize-methods-in-java-swi) for more details. Generally, it's good practice to override these methods based on your needs and expectations – MadProgrammer Sep 21 '15 at 05:44