I'm writing a Java text adventure and using Java Swing as a way to display a simple user GUI as the game play screen. I want to have three different windows, one as a start screen, one for a character creation screen where the user inputs a name and some attributes, and one where the game actually takes place.
I want to write each screen in a separate class to increase the organization and readability of my code. However whenever I click on the JButton to go to the next screen a completely new JFrame opens with the content. How can I write my GUI so that all of the screen changes take place in just one frame? Is Java supposed to function like this?
I've looked through at least a couple dozen Java Swing forum questions but still cant find out what I'm doing wrong with my Java swing implementation.
package guiPackage;
import javax.swing.*;
import thingsPackage.Player;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class StartGame extends JFrame {
JFrame Game;
JPanel buttonMenu, screen;
JButton newGame, settings, exit, loadGame;
public StartGame() {
Game = new JFrame();
Game.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Game.setVisible(true);
Game.setSize(750, 750);
Game.setLocationRelativeTo(null);
screen = new JPanel();
screen.setVisible(true);
TitleScreenHandler ts = new TitleScreenHandler();
buttonMenu = new JPanel();
newGame = new JButton("New Game");
newGame.addActionListener(ts);
loadGame = new JButton("LoadGame");
loadGame.addActionListener(ts);
settings = new JButton("Settings");
settings.addActionListener(ts);
exit = new JButton("Exit");
exit.addActionListener(ts);
Game.add(screen);
screen.add(buttonMenu, BorderLayout.SOUTH);
buttonMenu.add(newGame);
buttonMenu.add(loadGame);
buttonMenu.add(settings);
buttonMenu.add(exit);
}
public class TitleScreenHandler implements ActionListener{
public void actionPerformed(ActionEvent titleScreenEvent) {
if (titleScreenEvent.getSource() == newGame) {
buttonMenu.setVisible(false);
CharacterCreator newCharacter = new CharacterCreator();
}
else if (titleScreenEvent.getSource() == loadGame) {
buttonMenu.setVisible(false);
}
else if (titleScreenEvent.getSource() == settings) {
buttonMenu.setVisible(false);
}
else if (titleScreenEvent.getSource() == exit){
System.out.println("goodbye");
System.exit(0);
}
}
}
}
package guiPackage;
import javax.swing.*;
import java.awt.*;
import java.util.Scanner;
public class CharacterCreator extends StartGame {
JTextField newName;
JPanel creatorScreen;
Scanner scan = new Scanner(System.in);
public CharacterCreator() {
creatorScreen = new JPanel();
//creatorScreen.setVisible(true);
newName = new JTextField("Create a charactor");
//newName.setLocation(null);
screen.add(creatorScreen);
creatorScreen.add(newName);
}
public String setCharacterName(){
String characterName = "";
return characterName;
}
}
Is Java supposed to function like this?
Your code is only doing what you're telling it to, and you've several problems in your code including one problem here:
public class CharacterCreator extends StartGame {
You're misusing inheritance by havng the CharacterCreator class extend from StartGame. You seem to be doing this to allow communication between classes, but that is not what inheritance is for, and in doing this you're child class is calling the parent class's constructor, creating more windows than you want or need. The solution is to not use inheritance here but rather to pass references to where they are needed.
Also please check out The Use of Multiple JFrames, Good/Bad Practice? since your application should have only one main application window or JFrame. Any child windows should be JDialogs, not JFrames.
Another issue: StartGame extends JFrame and also holds a JFrame variable -- too many JFrames, and best to get rid of one or the other. Usually you don't want to create classes that extend a top-level window since you'll find yourself painting yourself in a corner by having your class extend JFrame, forcing you to create and display JFrames, when often more flexibility is called for. In fact, I would venture that most of the Swing GUI code that I've created and that I've seen does not extend JFrame, and in fact it is rare that you'll ever want to do this. More commonly your GUI classes will be geared towards creating JPanels, which can then be placed into JFrames or JDialogs, or JTabbedPanes, or swapped via CardLayouts, wherever needed. This will greatly increase the flexibility of your GUI coding.
Other separate and unrelated issues:
You appear to be combining an event driven GUI with a console GUI since you're using a Scanner object initiated with System.in. This is not a good idea since these two programming paradigms do not easily play nice with each other, and instead it is best to stick with one or the other, either a linear console program or an event-driven GUI.
In short, you should try using jLayeredPane and multiple jPanels, not multiple frames. If you're using Netbeans, check out my guide to making GUIs at: http://philofjava.webstarts.com/
Related
I started not so long ago learning Java through tutorials and videos, and after understanding a few things (how buttons, layouts, audio, and a few other things work) one of my goal now is to create a little interactive game.
I wrote a pretty big part of the game in the Main Class and it was working good, but it got messy after a while.
So I decided to try another time from the beginning using different classes for every part of the game to make the code look more clear and understandable.
But I have a problem from the very beginning and after a few hours of searching tutorials, answers on forums and not finding a precise answer, I think it's the best if you will see exactly my problem (which is very simple!)
-So I just constructed the JFrame in a class (I use the main Class only to launch the frame, and it works fine) :
import javax.swing.*;
import java.awt.*;
public class principalFrame {
public principalFrame(){
JFrame mainFrame = new JFrame();
mainFrame.setVisible(true);
mainFrame.setSize(1200,750);
mainFrame.getContentPane().setBackground(Color.BLACK);
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainFrame.setResizable(false);
}
}
and I created a JPanel in another class :
import javax.swing.*;
import java.awt.*;
public class mainMenu{
public mainMenu(){
JPanel menuPanel = new JPanel();
menuPanel.setSize(300,300);
menuPanel.setBackground(Color.BLUE);
}
}
And my goal is to add the JPanel inside the JFrame. And... I don't understand how to do it.
I tried to add the menuPanel class as an object in the mainFrame class so I could add the JPanel but it did'nt work. Then tried a bunch of other solutions from what I read on older questions but nothing really helped me.
PS : I know that I didn't add any layout manager or any other things in the code here because I want to keep the code very simple for the question.
So if you want to make a separate class for mainMenu, you should make it extend JPanel. This way, your mainFrame class can instantiate it.
Here is what I would tell you about the lessons I learned in building things in swing:
You (almost) never need a separate class to create a JFrame. If you create a class that extends JPanel, it is easy enough to create a JFrame in a static method (like main) to put your JPanel in. That being said, if you also want to use the JLayeredPane or want to add menus on a JMenuBar, there might be a case of subclassing JFrame. The advantage of not subclassing JFrame is that it makes it easier to stick your JPanel into a JFrame, a JDialog (via JOptionPane) or a JWindow.
Unless you have a Component that you think could be useful in other applications, you should build your entire GUI in one class, and also use that class as the Controller. What is an example of a Component that could be used in other applications? Back in the day, I made a "ColorButton" swing class, for color picking. This component has a self-contained model, has its own controller and an API to get the picked color. (https://sourceforge.net/p/tus/code/HEAD/tree/tjacobs/ui/ex/) which makes it reuseable. But normally, the elements of the GUI you are building aren't really reuseable outside of what you are doing in that class.
With that said, to the code you posted above:
Your mainMenu JPanel isn't accessible outside the class, so that is probably a problem. As I said in the paragraph above, if you're not sure that the Component you are creating could be used in another place, it is better to put the entire view and model building in the same class as the controller
You were close. You have to be able to get the JPanel from the MainMenu class.
The JFrame methods must be called in a specific order. This is the order I use for my Swing applications.
Class names start with an upper case character.
Here's one way you could code your MainMenu class.
public class PrincipalFrame {
public PrincipalFrame() {
JFrame mainFrame = new JFrame();
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainFrame.add(new MainMenu().getMenuPanel(), BorderLayout.CENTER);
mainFrame.setSize(1200, 750);
mainFrame.setResizable(false);
mainFrame.setVisible(true);
}
}
public class MainMenu {
private final JPanel menuPanel;
public MainMenu() {
this.menuPanel = new JPanel();
this.menuPanel.setPreferredSize(300, 300);
this.menuPanel.setBackground(Color.BLUE);
}
public JPanel getMenuPanel() {
return menuPanel;
}
}
I am trying to add JPanels and JButtons from a separate class to the frame, which is in the main class. The program compiles with no errors, but only shows a black window. The visual goal is to have a button that changes color when clicked. I create the button in the GUI_1_1 class, and create the frame in the main class. What am I doing wrong?
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class main {
public static void main(String[] args) {
JFrame frame = new JFrame ("Cube GUI");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.setSize(1370,790);
frame.getContentPane().setBackground(Color.BLACK);
frame.add(new GUI_1_1());
frame.setVisible(true);
}
}
The second class is below
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class GUI_1_1 extends JPanel {
private int count;
private JButton button0;
private JPanel mainpanel;
private JPanel panel1;
public GUI_1_1() {
mainpanel = new JPanel();
mainpanel.setLayout(null);
count = 0;
button0 = new JButton("[1][1]");
button0.addActionListener(new ButtonListener());
button0.setOpaque(true);
button0.setBounds(60,310,50,50);
mainpanel.add(button0);
add(mainpanel);
}
private class ButtonListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent event) {
if(count==0){
button0.setBackground(Color.RED);
count = count + 1;
} else if(count==1) {
button0.setBackground(Color.GREEN);
count = count + 1;
} else if(count==2) {
button0.setBackground(Color.ORANGE);
count = count + 1;
} else if(count==3) {
button0.setBackground(Color.BLUE);
count = count + 1;
} else if(count==4) {
button0.setBackground(Color.YELLOW);
count = count + 1;
} else if(count==5) {
button0.setBackground(Color.WHITE);
count = count - 5;
}
}
}
}
OK, there's a lot to explain here of what you're doing wrong, so I won't be able to break it all down for you. I can start with the basics though.
Firstly
Don't take it too hard. Swing is a thing of it's own. Java has a reputation of being very abstract so you don't have to know how it works - just call it and let Java figure it out for you. I don't totally agree with that for Java, but for Swing I absolutely disagree with it. Swing gets a bad reputation for this very reason, and I think its unwarranted 90 percent of the time.
Just because you're calling an abstracted API doesn't mean you don't have to know how it works. Swing is especially this way. That said, I can't even begin to break it down in this answer because there are lots of specifics, a lot of which don't even apply directly to your code but you should know indirectly, so you'll just have to spend time learning it which just takes time and reading. The most important thing to take away though, is that Swing is not just some abstract black box you don't have to understand - you must learn it and not just call random functions, or you will end up finding your code misbehaves if you do.
Secondly
And you might see this a peeve (and not related to your question), but it becomes important later after you're done learning - work on your class naming. Classes should be self-descriptive of what they do, so while JavaDocs are helpful, they shouldn't be 100 percent necessary for someone reading your code to understand what it's doing.
Third
And finally getting to your question.
EDT
Never do GUI work outside of the Event Dispatch Thread (EDT). In any environment that has a GUI, you have one singular thread to do that display work. Your music player has it, your internet browser deals with it - Java is no different. And to really drive it home how important this is - if you can figure out how to do multithreaded GUI work, you stand to make a lot of money and never work another day for the rest of your life.
Your main method is run in a thread of it's own, and the EDT is started implicitly by your program. Your GUI is supposed to be updated in the EDT, and those abstracted things that Java (and Swing) do that I mentioned early, will automatically happen in the EDT. Your code, however, does not. What you want to look at are SwingUtilities.html#invokeLater(java.lang.Runnable) (or alternatively invokeAndWait(...)) where you put your code as
public static void main (String [] args) {
SwingUtilities.invokeLater(()->{
//things you want to happen only in the EDT
});
//other things to happen in your main thread
JFrame
Then there's the line where you say:
frame.add(new GUI_1_1());
So a JFrame is a little different than other containers. You don't want to 'add' things to a JFrame. Instead, you want to set your JPanel as the content pane of your JFrame.
Other than that, the line before that you get the content pane, and then set the background color of it. Which, now that you'll be using your GUI_1_1 as the content pane, you can imagine how that line won't make sense anymore. BTW, with relation to your black screen you claim to be seeing, your content pane not being your display seems to be the issue.
LayoutManager
One of the things you're doing in GUI_1_1's constructor is to set the layout manager to null. This is another whole thing all it's own, which I won't really detail because it's too much to type - and Oracle has already done a nice job with that, which I fully recommend reading to figure out how to use layout managers correctly: https://docs.oracle.com/javase/tutorial/uiswing/layout/visual.html
And on and on
I could keep going for a while on this but this answer is already pretty verbose as it stands. Like I mentioned, Swing is a whole thing all it's own that requires just learning it, as with any API really.
I can keep editing this answer with more information, or links to more reading if you have other questions or other specifics. But I'm going to cut this short to keep more 'general purpose' suggestions out, from the ones I already put above.
I am a 15 year old new to the stackoverflow community. I am currently trying to independently develop a Rock Paper Scissors game using java. I thought working on this project would be an informing experience to help me learn java and its fundamentals. I am somewhat new with the java programming language so please do not criticize me, I am slowly learning by trial and error. For this particular project, I decided to use Eclipse as I like its user interface better than other IDEs. Anyway, I decided to implement JPanel to make the game more visual. My code is as follows copy and pasted from my eclipse project:
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.*;
import java.awt.event.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public abstract class prompt extends JPanel implements ActionListener {
public static void main(String []args) {
JPanel panel = new JPanel();
JButton rockButton = new JButton("ROCK");
JButton scissorsButton = new JButton("SCISSORS");
JButton paperButton = new JButton("PAPER");
JFrame choicePrompt = new JFrame("Rock, Paper, Scissors Game");
choicePrompt.add(panel);
choicePrompt.setSize(300, 300);
choicePrompt.setVisible(true);
panel.add(rockButton);
panel.add(scissorsButton);
panel.add(paperButton);
}
}
PLEASE READ: I made the prompt class an abstract class because apparently it fixed an error. However, what I'm looking for is a way to add that when the rock, paper, or scissors button is clicked on the JPanel, that it registers that click. I know this can be acheived with either a Mouse or ActionListener but I could not figure it out on my own. All the other "shtuff" like the computerchoice I can potentially do on my own.
PLEASE READ: I made the prompt class an abstract class because apparently it fixed an error
It might have fixed the compiler error, but it won't have fixed the problem.
You class implements an interface in this case the ActionListener interface. Basically, an interface is a contractual agreement that means that classes the implement the interface will fulfil the requirements of the interface, in this case, this means providing an implementation of the actionPerformed method declared by the ActionListener interface
Start by having a read through What Is an Interface?, Creating a GUI With JFC/Swing, How to Use Buttons, Check Boxes, and Radio Buttons and How to Write an Action Listener
Why exactly is your class abstract? I assume you are forgetting to implement something, and making it abstract doesn't necessarily fix that.
to add a ActionListener to a JButton ...this is what i usually do...:
public class ButtonHandler implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
if(event.getActionCommand().equals("0"))//.getActionCommand() returns the text in the component
{
//do what you want here...
}
}
}
now to make it function just
btn0.addActionListener(new ButtonHandler());//ButtonHandler is the name of the class that implements the ActionListener, btn0 is the name of the JButton
I have a class "GUI" that extends JPanel. I have another class "Buttons" that extends JFrame. I'm trying to have the JFrame class call a method "clearScreen()" in the JPanel class when the when a JButton "clearB" is pushed on the JFrame.
The only way I could make this work was by building the object for the JPanel class "GUI" right in the actionlistener for the JButton:
clearB.addActionListener(
new ActionListener(){
public void actionPerformed(ActionEvent event){
GUI g = new GUI();
g.clearScreen();
}
}
);
But then when I called the method clearScreen(), which looks like this:
public void clearScreen(){
xs.clear();
ys.clear();
count = 0;
repaint();
}
NOTHING HAPPENED. I'm guessing it's because the repaint() method wasn't working for some reason unknown to me.
Someone PLEASE show me an easier, working way of doing what I'm trying to accomplish here.
Thanks! :D
The reason that your ActionListener isn't working is because the GUI object that you create in there is a new GUI object, one that is completely unrelated to the GUI object that is displayed, and so calling the clearScreen() method on the non-displayed GUI instance will have no effect on the displayed GUI instance.
The solution is for your Buttons class to hold a valid reference to the visualized GUI object and call methods on this reference. The reference can be passed via a setter method or constructor parameter.
i.e.,
public class Buttons {
private GUI gui;
public Buttons (GUI gui) {
this.gui = gui;
}
// in some ActionListener code...
gui.someMethod();
}
A couple of comments:
It is unusual that you should have to have a class that extends JFrame. Myself, I try to avoid doing this unless necessary, but rather usually create my JFrames from the JFrame class itself, and only when needed.
I'm a little surprised that your main window class doesn't already have a GUI variable, since it likely displays the GUI instance.
First of all... I'd like to say that I am not interested in using the card layout for this... Unless it's necessary (which means that not using the card layout would be result in unnecessary workarounds and complex code). This is for learning purposes after all and I will look into the card layout very soon enough anyway...
Okay so my question is pretty basic GUI layout I guess. My code is not working and this whole layouting confuses me quite a lot...
I'm having trouble how to make the transition between JPanels like this:
I have one window. I press a button, the old window is replaced by another window. I press a button and that window will be replaced by another window.
I'd like to add that I am skipping a lot of irrelevant code in my example below...
I start off with a JFrame:
public class StartWindow extends JFrame{
public StartWindow(){
//Add JButton & ActionListener
//When the button is pressed:
add(new NextWindow());
}
public static void main(String [] args){
new StartWindow();
}
}
then I have several JPanels...
public class NextWindow extends JPanel{
public NextWindow(){
//Add a JButton & ActionListener
//When the button is pressed:
add(new NextWindow2());
remove(this);
//This does not work. Nothing happens.
}
}
public class NextWindow2 extends JPanel{ // Stuff and so on}
So, I'd like to know a proper way to handle this situation!
You are adding a panel to itself. You need to remove the panel from the JFrame, add the new one to it, and call revalidate() on the JFrame.