You should be using a JFrame
. Trust me, they aren't that hard to use. Using a JFrame
, you could create multiple panels and switch between them using CardLayout
.
Since you said you aren't sure about how JFrame
works, I gave a short introduction to them at the end of this post. But first, lets first talk about how to solve your problem.
Switching Panels via CardLayout
When you want to switch whats being viewed in the window completely, you're gonna want an entirely different panel for that specific purpose (for example, one for Main Menu, and one for the game). CardLayout
was created for this.
You specify a "deck" panel. This panel will hold all the other panels you wanna switch between (cards):
CardLayout layout = new CardLayout();
JPanel deck = new JPanel();
deck.setLayout(layout);
You'll need to maintain a reference to the layout (via a variable) so you can switch between panels.
Now that we have a "deck" panel, we need to add some cards. We do this by creating more JPanels, and specifying a name when we add it to the frame (constraints):
JPanel firstCard = new JPanel();
JPanel secondCard = new JPanel();
deck.add(firstCard, "first");
deck.add(secondCard, "second");
The first card added to the deck will always be the first one to show.
Once you have all your cards added, you can switch between them by calling layout.show(deck, "second");
. This is how you use CardLayout
to manage multiple panels within your container.
Creating listeners
There's no easier way to manage it. It only gets harder from there (bindings - I highly suggest you look into them). For listeners, there are 2 steps:
- Create the listener
- Add it to the component
Could be 1 if you created the listener "on the fly" using a lambda:
JButton button = new JButton();
//Java 8+
button.addActionListener(event -> {
//whenever you click the button, code in here triggers
});
For those who don't use Java 8, you will need to use an anonymous class:
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
//whenever you click the button, code in here triggers
}
});
Only some components support ActionListener
. Anything that extends AbstractButton
, like JMenuItem
, JButton
, JRadioButton
, and more will support ActionListener
s. JTextField
also supports it, even though it's not an AbstractButton
. Every component supports KeyListener
, though, which can also be used to listen for input events from the user.
If you have any questions about this, let me know and I'll be glad to answer them.
Using Swing Components
JFrame
You initialize a JFrame
:
JFrame frame = new JFrame();
You then want to set the defaultCloseOperation
, to determine what happens when the window closes:
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
There are different options, but EXIT_ON_CLOSE
will terminate your program after closing the window. If you do not set the defaultCloseOperation
, then your window will close, but your program will still be running. BIG NO NO. If you don't want the entire program to exit when you close the frame, use DISPOSE_ON_CLOSE
.
After you have the close operation, you might be tempted to set the size. This is bad practice. Your frame should size depending on what's inside of it. To do this, you add your components to your frame, then call pack()
:
JButton button = new JButton("Button");
frame.add(button);
//add other components
frame.pack();
This will ensure your frame sizes to fit what's inside of it. You should always design GUI from the inside out, to ensure you always have enough room for what you need.
Containers
JFrame
is a Container
. Containers are just components that hold other components. All containers should have a LayoutManager
to manage how components are laid out and, if needed, sized. JFrame
isn't the only container though. JPanel
is actually a container that's meant to be added to another container (or window). You can add things to a JPanel
, then add that panel to another container. This is how you keep things neatly organized. Containers are pretty straight forward, so there's not much to talk about.
Content Pane
When you create a JFrame
, it comes along with something called the contentPane
. It is a JPanel
nested within the JFrame. When you do frame.add(button)
, you'll notice that add
actually refers to the contentPane:
//in Container class
public Component add(Component comp) {
addImpl(comp, null, -1); //where it's added
return comp;
}
//In JFrame class (overriding)
protected void addImpl(Component comp, Object constraints, int index) {
if(isRootPaneCheckingEnabled()) {
getContentPane().add(comp, constraints, index); //adds to content pane instead
} else {
super.addImpl(comp, constraints, index); //if root panes were not supported, which they currently are
}
}
You too can grab the contentPane from the frame using
Container pane = frame.getContentPane();
The reason why the contentPane is in Container
form is to ensure a strong API (if JPanels were no longer going to be used for this, we wouldn't need to worry about changing the method type of getContentPane). Although, since it IS a JPanel, you can cast it back to it's original form:
JPanel pane = (JPanel) frame.getContentPane();
Although it's usually not needed. Using it as a Container
tends to work just fine.
Layouts
JFrame
, by default, uses BorderLayout
, but you can change this by calling the setLayout
method of the container:
FlowLayout layout = new FlowLayout();
frame.setLayout(layout);
Before jumping into layouts, I want to mention that JPanel
s use FlowLayout
as default, except for the frame's default contentPane, which you can also change by doing frame.setContentPane(...)
. The contentPane uses BorderLayout
as default.
Now, lets talk about a couple, starting with the JFrame
default: BorderLayout
.
Some layouts require what are called constraints, which tell the layout how to handle that specific component that's being added. You specify these constraints when you add the component to the container:
frame.add(button, BorderLayout.SOUTH);
BorderLayout
is pretty simple. You have 5 options: NORTH, SOUTH, EAST, WEST, or CENTER. (there are other values such as PAGE_START, which are interchangeable. It's best to just use the easier form)
All constraints for BorderLayout
are static field variables that you call similar to how I did. BorderLayout
is actually an easy layout to use, seeing how there's not much to it. Even though it's simplicity limits what you can do (you can only put it in a certain position like NORTH), you'd usually combine layouts to get the result you want. This layout can be very powerful when combined with other layouts.
FlowLayout
is pretty straight forward, as well as other layouts out there. One of the less straight-forward ones would be GridBagLayout
, which can be a really flexible layout. It can also be pretty complex, though, as the documentation even states.
When using GridBagLayout
, you need to create a constraints object: GridBagConstraints
. To work with GridBagLayout
, you set the constraints using the constraints object, then add both the component and the constraints to the container:
frame.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
JButton button = new JButton("yoyoyo");
frame.add(button, gbc);
Even if we don't adjust the constraints, we MUST still specify it when adding a component to the container.
Lets say we had 4 buttons, and we wanted to put them side by side. You would adjust the constraint's gridx
and gridy
values:
JButton button = new JButton("1");
JButton button2 = new JButton("2");
JButton button3 = new JButton("3");
JButton button4 = new JButton("4");
frame.add(button, gbc);
gbc.gridx = 1; //or gridx += 1, or gridx = 1. gridx starts at 0
frame.add(button2, gbc);
gbc.gridx = 0; //must reset the value back to normal
gbc.gridy = 1;
frame.add(button3, gbc);
gbc.gridx = 1;
//since gridy already == 1, no need to change it
frame.add(button4, gbc);
We can use the same constraints object, as long as we reset values when needed.
GridBagLayout
starts centered, and works from the center out, unless you specify otherwise. You cannot skip grid spaces either. Also, as you'll notice, all your buttons will be touching. If you wanted a little space between each component, you can set the insets
of the constraints:
int top = 5, left = 5, bottom = 1, right = 1;
gbc.insets.set(top, left, bottom, right);
There is a LOT more to this layout, and sadly I just don't feel this is the best place to give the explanation to it all, seeing how it's already documented (I even added the link).
There are many other layouts out there. Get familiar with as many as you possibly can, then find the ones that'll help suit your needs. Positioning should ALWAYS rely on the LayoutManager
that's being used. As for sizing, that kinda depends on the layout you're using.