ulw1 ulw1 - 7 days ago 6
Java Question

Pack method called from actionPerformed functions only sometimes

Problem

Upon compiling and running my program multiple times, sometimes

pack()
works and the components of
newGamePanel
are compressed, and sometimes it doesn't work, and
newGamePanel
expands to fill the
JFrame
values set by
setSize()
. I have been unable to reliably reproduce either result-- it really seems to be random.

Note: As I cut down on the amount of GUI formatting to have a reasonable amount of code to review, the GUI is pretty trash. However, the problem is still easy to identify. When the
JPanel
that's supposed to be packed is shown by
CardLayout
, sometimes the
JFrame
is the "packed" size and sometimes it matches the
setSize()
values I set in the beginning. Now that I cut the GUI, the
newGamePanel
components don't move to fill their container, but that's just because I removed all their constraint values.

Suspicions and design

I am calling
pack()
from class
TankEvent
, which implements
ActionListener
. Game is a
TankApplication
object (
TankApplication
extends
JFrame
) passed to
TankEvent
in the
TankEvent
constructor, which is called by
TankDisplay
(
TankDisplay
extends
JPanel
).

JFrame
instantiates
JPanel
, passes instance of self.
JPanel
instantiates
ActionListener
, passes instance of self.
ActionListener
modifies
JFrame
using
pack()
.

The following is the code executed when a button is pressed.

CardLayout layOut = (CardLayout)(display.getLayout()); //display is an object of TankDisplay
layOut.show(display, "newGamePanel");
game.pack(); //game is an object of TankApplication
game.setResizable(false);
break;


I wonder if the issue is in my design. I'm making a huge assumption that
pack()
repaints the
JFrame
(are
JFrames
even repainted? Perhaps revalidates/updates?). But as long as I reset the size of
JFrame
when I'm done, I'm not sure why it would be an issue..

(Side question, I'm not sure why I need to cast
display.getLayout()
as a
CardLayout
. This is the suggested implementation from docs.oracle, but why does
getLayout()
return a LayoutManager and not the actual LayoutManager...?)

Shortened Relevant Code

Tank Display

package Tanks;
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import java.io.*;
import java.net.URL;
import javax.imageio.*;

public class TankDisplay extends JPanel{
JCheckBox unlimitedAmmoCB, unlimitedTimeCB;
JTextField playerOneTF, playerTwoTF;
JPanel menuPanel, newGamePanel;

public TankDisplay(TankApplication g){
TankEvent listener = new TankEvent(this, g); //passing an instance of self and previously received instance of TankApplication to TankEvent
setLayout(new CardLayout()); //Hihghest level of GUI after JFrame. CardLayout for overall display JPanel, need for switching functionality in TankEvent

menuPanel = new JPanel(new GridBagLayout()); //Second highest level of GUI. Will eventually display a picture instead of a black JPanel. Has button "New Game" and "Load Game"

JPanel mainMenuImageP = new JPanel();
GridBagConstraints conMainMenuImageP = new GridBagConstraints();
mainMenuImageP.setBackground(Color.BLACK);
conMainMenuImageP.fill = GridBagConstraints.BOTH;
conMainMenuImageP.gridy = 0;
conMainMenuImageP.gridx = 0;
menuPanel.add(mainMenuImageP, conMainMenuImageP); //adding menuPanel components

JButton newGameB = new JButton("New Game");
GridBagConstraints conNewGameB = new GridBagConstraints();
conNewGameB.fill = GridBagConstraints.NONE;
conNewGameB.gridy = 1;
conNewGameB.gridx = 0;
menuPanel.add(newGameB, conNewGameB); //adding menuPanel components

JButton loadGameB = new JButton("Load Game");
GridBagConstraints conLoadGameB = new GridBagConstraints();
conLoadGameB.fill = GridBagConstraints.NONE;
conLoadGameB.gridy = 1;
conLoadGameB.gridx = 1;
menuPanel.add(loadGameB, conLoadGameB); //adding menuPanel components

//action listners for mainPenu panel components
newGameB.addActionListener(listener);

add(menuPanel, "menuPanel"); //menuPanel is added to higher display JPanel

newGamePanel = new JPanel(new GridBagLayout()); //creating second higher level container. To achieve certain functionality,
//this panel contains four other panels, that each contain their own
JPanel playerOneSetUp = new JPanel(new GridBagLayout()); //components. newGamePanel uses GridBagLayout, and so do the panels
GridBagConstraints conPlayerOneSetUp = new GridBagConstraints();//that it's managing. GridBayLayout managaing GridBagLayout
conPlayerOneSetUp.fill = GridBagConstraints.BOTH;
conPlayerOneSetUp.gridy = 0;
conPlayerOneSetUp.gridx = 0;

JLabel playerOneL = new JLabel("Player One Name");
GridBagConstraints conPlayerOneL = new GridBagConstraints();
conPlayerOneL.fill = GridBagConstraints.HORIZONTAL;
conPlayerOneL.gridy = 0;
conPlayerOneL.gridx = 0;
playerOneSetUp.add(playerOneL, conPlayerOneL);

playerOneTF = new JTextField();
GridBagConstraints conPlayerOneTF = new GridBagConstraints();
conPlayerOneTF.fill = GridBagConstraints.HORIZONTAL;
conPlayerOneTF.gridy = 1;
conPlayerOneTF.gridx = 0;
playerOneSetUp.add(playerOneTF, conPlayerOneTF);

JButton playerOneJColorChooser = new JButton("Player One Color");
GridBagConstraints conPlayerOneJColorChooser = new GridBagConstraints();
conPlayerOneJColorChooser.fill = GridBagConstraints.HORIZONTAL;
conPlayerOneJColorChooser.gridy = 2;
conPlayerOneJColorChooser.gridx = 0;
playerOneSetUp.add(playerOneJColorChooser, conPlayerOneJColorChooser);

newGamePanel.add(playerOneSetUp, conPlayerOneSetUp); //adding newGamePanel components

JPanel playerTwoSetUp = new JPanel(new GridBagLayout());
GridBagConstraints conPlayerTwoSetUp = new GridBagConstraints();
conPlayerTwoSetUp.fill = GridBagConstraints.BOTH;
conPlayerTwoSetUp.gridy = 1;
conPlayerTwoSetUp.gridx = 0;

JLabel playerTwoL = new JLabel("Player Two Name");
GridBagConstraints conPlayerTwoL = new GridBagConstraints();
conPlayerTwoL.fill = GridBagConstraints.HORIZONTAL;
conPlayerTwoL.gridy = 0;
conPlayerTwoL.gridx = 0;
playerTwoSetUp.add(playerTwoL, conPlayerTwoL);

playerTwoTF = new JTextField();
GridBagConstraints conPlayerTwoTF = new GridBagConstraints();
conPlayerTwoTF.fill = GridBagConstraints.HORIZONTAL;
conPlayerTwoTF.gridy = 1;
conPlayerTwoTF.gridx = 0;
playerTwoSetUp.add(playerTwoTF, conPlayerTwoTF);

JButton playerTwoJColorChooser = new JButton("Player Two Color");
GridBagConstraints conPlayerTwoJColorChooser = new GridBagConstraints();
conPlayerTwoJColorChooser.fill = GridBagConstraints.HORIZONTAL;
conPlayerTwoJColorChooser.gridy = 2;
conPlayerTwoJColorChooser.gridx = 0;
playerTwoSetUp.add(playerTwoJColorChooser, conPlayerTwoJColorChooser);

newGamePanel.add(playerTwoSetUp, conPlayerTwoSetUp); //adding newGamePanel components

JPanel options = new JPanel(new GridBagLayout());
GridBagConstraints conOptions = new GridBagConstraints();
conOptions.fill = GridBagConstraints.BOTH;
conOptions.gridy = 0;
conOptions.gridx = 1;

JLabel optionsL = new JLabel("Game Options");
GridBagConstraints conOptionsL = new GridBagConstraints();
conOptionsL.fill = GridBagConstraints.HORIZONTAL;
conOptionsL.gridy = 0;
conOptionsL.gridx = 0;
options.add(optionsL, conOptionsL);

unlimitedAmmoCB = new JCheckBox("Unlimited Ammunition");
GridBagConstraints conUnlimitedAmmoCB = new GridBagConstraints();
conUnlimitedAmmoCB.fill = GridBagConstraints.HORIZONTAL;
conUnlimitedAmmoCB.gridy = 1;
conUnlimitedAmmoCB.gridx = 0;
options.add(unlimitedAmmoCB, conUnlimitedAmmoCB);

unlimitedTimeCB = new JCheckBox("Unlimited Time");
GridBagConstraints conUnlimitedTimeCB = new GridBagConstraints();
conUnlimitedTimeCB.fill = GridBagConstraints.HORIZONTAL;
conUnlimitedTimeCB.gridy = 2;
conUnlimitedTimeCB.gridx = 0;
options.add(unlimitedTimeCB, conUnlimitedTimeCB);

newGamePanel.add(options, conOptions); //adding newGamePanel components

JButton startGameB = new JButton("START");
GridBagConstraints conStartGameB = new GridBagConstraints();
conStartGameB.fill = GridBagConstraints.BOTH;
conStartGameB.gridy = 1;
conStartGameB.gridx = 1;

newGamePanel.add(startGameB, conStartGameB); //adding newGamePanel components

add(newGamePanel, "newGamePanel"); //newGamePanel is added to higher level display JPanel

}
}


Tank Application

package Tanks;
import javax.swing.*;
import java.awt.*;
public class TankApplication extends JFrame{
public static void main (String args[]){
TankApplication GUI = new TankApplication();
}

public TankApplication(){
super("Tanks");
add(new TankDisplay(this));
setSize(800, 600);
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
}
}


Tank Event

package Tanks;
import java.awt.*;
import java.awt.event.*;

import javax.swing.JColorChooser;
public class TankEvent implements ActionListener{

TankApplication game;
TankDisplay display;

public TankEvent(TankDisplay d, TankApplication g){ //I found this was necesarry because I didn't want to call the constructors of panels
display = d; //and frames. I'm not sure why that caused errors, but it does. And when I tried to
game = g; //create overloaded constructors for TankApplication and TankDisplay, their references
} //didn't have the information I needed. This is likely because I kept most of the components
//as local variables in the constructors, instead of creating variables in their respective classes, and using
public void actionPerformed(ActionEvent e){ //the constructors to modify them
CardLayout layOut = (CardLayout)(display.getLayout()); //<---Why do I need to do this?
switch(e.getActionCommand()){
case "New Game":
layOut.show(display, "newGamePanel");
game.pack(); //<<<---Root problem. Sometimes newGamePanel is packed, the JFrame is smaller, sometimes newGameaPanel is not packed. Seems random
game.setResizable(false); //for this JPanel only, I don't want to be able to resize the window. I will change this when the user flips
break; //to another JPanel
}
}
}


Robert seems to have asked a similar question, but didn't seem to get a satisfactory answer. Why do threads have anything to do with this?

Answer

You are not using CardLayout correctly.

When you use a CardLayout on a panel the preferred size of the panel is the size of the largest child panel added to the CardLayout.

Swapping from one panel to another will not alter the preferred size of panel and therefore the frame. So the pack() method will have no effect.

I suggest you don't worry about packing the frame. Just create the "menu panel" so that its components are centered. Then when you start the game all that changes is that you display the "game panel".

Comments