Last image on JLabel is shown for all after adding JLayeredPane in loop

69 views Asked by At

Sorry for the bad title, could not think of anything better.

I am building a card game.

The below method takes a Card object as argument and returns me a JLayeredPane with a JLabel having the image for the card.

private JLayeredPane getCardPane(Card card) {
    JLayeredPane cardPane = new JLayeredPane();

    GridBagLayout gblCardPane = new GridBagLayout();
    cardPane.setLayout(gblCardPane);

    cardImageIcon = card.getImageIcon();

    JLabel lblCard = new JLabel() {
        @Override
        public void paintComponent(Graphics g) {
            g.drawImage(cardImageIcon.getImage(), 0, 0, null);
            super.paintComponent(g);
        }
    };

    GridBagConstraints gbcLblCard = new GridBagConstraints();
    cardPane.add(lblCard,gbcLblCard);

    return cardPane;
}

I have a section like below adding all cards in my hand to another JLayeredPane jlpMyCards:

for (int i = 0; i < hand.getCardCount(); i++) {
    JLayeredPane thisCard = getCardPane(hand.getCard(i));

    //JOptionPane.showMessageDialog(null,thisCard);

    jlpMyCards.add(thisCard);
}

and finally add the jlpMyCards to the frame.

On the rendered frame I see all cards in hand (as per count) but all cards are displaying the last image that is loaded. - WHY?

I tried printing the cards with

JOptionPane.showMessageDialog(null,thisCard);

The popup dialog is showing the correct images.

NOTE: I guess the below method from Card class might also create the issue.

public ImageIcon getImageIcon() {
    BufferedImage img = null;
    try {
        img = ImageIO.read(new File(this.getClass().getResource(
                    getValueAsString().toLowerCase() + "_of_"+ 
                    getSuitAsString().toLowerCase() + ".png").toURI()));
        //Rescaling Image
        Image dimg = img.getScaledInstance(100, 146, Image.SCALE_SMOOTH);
        return new ImageIcon(dimg);
    } catch (IOException e) {
        e.printStackTrace();
    } catch (URISyntaxException e) {
        e.printStackTrace();
    }
    return null;
}
2

There are 2 answers

0
Sam On BEST ANSWER

Considering all suggestions I changed the card.getImageIcon(). Instead I am now using getCardImagePane() which returns a JLayeredPane after setting the image to it.

Below is the code I am using now:

public CardImagePane getCardImagePane(boolean small) {
    return new CardImagePane(this.getClass().getResource(
            getValueAsString().toLowerCase() + "_of_"
                    + getSuitAsString().toLowerCase() + ".png"), small);
}

class CardImagePane extends JLayeredPane {

    private static final long serialVersionUID = 6829613165978637891L;

    private Image image;

    public CardImagePane(URL imgURL, boolean small) {
        Image tempImg = new ImageIcon(imgURL).getImage();
        this.image = tempImg.getScaledInstance(Constants.CARD_WIDTH,
                Constants.CARD_HEIGHT, Image.SCALE_SMOOTH);

        Dimension size = new Dimension(small ? Constants.CARD_WIDTH_SMALL
                : Constants.CARD_WIDTH, Constants.CARD_HEIGHT);
        setPreferredSize(size);
        setMinimumSize(size);
        setMaximumSize(size);
        setSize(size);
        setLayout(null);
    }

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

Thanks to all. Do tell me if something better could be done.

4
Krzysztof Kosmatka On

In each iteration of the loop you assign value to field cardImageIcon. And when rendering all labels calls getImage() (in their paintComponent method) on the same cardImageIcon object (which was set during last iteration).

Instead of keeping it as a field, you can keep it as a local variable:

final ImageIcom cardImageIcon = card.getImageIcon();

JLabel lblCard = new JLabel() {
    @Override
    public void paintComponent(Graphics g) {
        g.drawImage(cardImageIcon.getImage(), 0, 0, null);
        super.paintComponent(g);
    }
};

It is important to declare it as final to use it in paintComponent method.

OR

perhaps you can just use JLabel constructor that takes Icon as an argument in constructor:

JLabel lblCard = new JLabel(card.getImageIcon());