I don’t know what I did, or what went wrong, but a change I made at some point in the last while has made my JPanel completely invisible. The JFrame it’s nested in still changes in size to house it, and I can still toggle the content in the combobox.
In my desperation, I tried replacing the content of the SnakeSettingsPanel class with a single button, but the same thing happened – completely invisible, yet I can still interact with it. I figured it might be a computer error, so I tried restarting, but still nothing. When I tried adding a button to the frame outside of the JPanel, it worked just fine. What am I doing wrong?
import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.*; public class SnakeSettingsPanel extends JPanel { public boolean quit = false; public boolean play = false; public int width = 20; public int height = 15; public Speed speed = Speed.SLOW; public JTextField wField; public JTextField hField; public JComboBox<Speed> sField; public static void main(String[] args) { JFrame jf = new JFrame(); jf.setTitle("Snake"); SnakeSettingsPanel settings = new SnakeSettingsPanel(); jf.add(settings); jf.pack(); jf.setVisible(true); jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public SnakeSettingsPanel() { setLayout(new GridBagLayout()); // @author Create our labels. JLabel wLabel = new JLabel("Width:"); JLabel hLabel = new JLabel("Height:"); JLabel sLabel = new JLabel("Speed:"); GridBagConstraints p = new GridBagConstraints(); p.gridx = 0; p.gridy = 0; p.insets = new Insets(10, 10, 10, 10); // @author Create the buttons, and add listeners JButton y = new JButton("Play"); JButton n = new JButton("Quit"); y.addActionListener(new PlayListener()); n.addActionListener(new QuitListener()); // @author Create text fields for height/width wField = new JTextField(15); wField.setText("20"); hField = new JTextField(15); hField.setText("15"); // @author Creates a combobox for selecting speed. Speed[] speeds = {Speed.SLOW, Speed.MEDIUM, Speed.FAST}; sField = new JComboBox<Speed>(speeds); // @author Stitch everything into the panel. add(wLabel, p); p.gridx = 1; add(wField, p); p.gridx = 0; p.gridy = 1; add(hLabel, p); p.gridx = 1; add(hField, p); p.gridx = 0; p.gridy = 2; add(sLabel, p); p.gridx = 1; add(sField, p); p.gridx = 0; p.gridy = 3; add(y, p); p.gridx = 1; add(n, p); setVisible(true); } public boolean getPlay() { return play; } public boolean getQuit() { return quit; } // @author Returns all settings as a SnakeSettings object public SnakeSettings getSettings() { return new SnakeSettings(width, height, speed); } public int getWidth() { return width; } public int getHeight() { return height; } public Speed getSpeed() { return speed; } // @author Sends out the word to start a new game. public class PlayListener implements ActionListener { public void actionPerformed(ActionEvent e) { quit = false; play = true; width = Integer.parseInt(wField.getText()); height = Integer.parseInt(hField.getText()); speed = (Speed) sField.getSelectedItem(); } } // @author Sends out the word to shut down the program. public class QuitListener implements ActionListener { public void actionPerformed(ActionEvent e) { quit = true; play = false; } } }
Advertisement
Answer
Let this be a lesson on why you should avoid mixing Model (your application’s data) with View (how it is displayed). Your SnakeSettingsPanel
is currently both.
- As Model, it contains 3 important fields:
width
,height
, andspeed
. - As View, it is a full JPanel. JPanels have a lot of fields which you should avoid touching directly. Including
width
andheight
, usually accessed viagetHeight
andgetWidth
— which you are overwriting with a version that always returns the same built-in values of 20 and 15 (until the user changes those values through a UI that they cannot see).
The fast fix is to rename your current getWidth()
and getHeight()
to avoid clashing with the built-in getWidth()
and getHeight()
methods of the parent JPanel class. Call them getMyWidth()
, getMyHeight()
, and suddenly everything works.
The better fix is to remove those fields and methods entirely, and store your own model attributes in a SnakeSettings
attribute. Update it when the user clicks on play, and return it when it is requested via getSettings()
. Less code for you, less chance of accidental name clashes with your parent JPanel
class. This would look like:
// SnakeSettingsPanel, before public int width = 20; public int height = 15; public Speed speed = Speed.SLOW; public int getWidth() { // <-- clashes with superclass return width; } public int getHeight() { // <-- clashes with superclass return height; } public Speed getSpeed() { return speed; } public SnakeSettings getSettings() { return new SnakeSettings(width, height, speed); } // SnakeSettingsPanel, after SnakeSettings settings = new SnakeSettings(); public SnakeSettings getSettings() { return settings; }