So I have a JPanel with a CardLayout.
this CardLayout, as expected, manages the switching of panels in the frame.
The switching is done by two buttons: "Back" and "Next".
I want to know if there is a way to close the whole application (i.e. call System.exit(0)) when it is on the last card and "Next" is pressed again.
I have looked for a solution everywhere, but I can't find anything.
The problem is: I don't know how to check which is the last one.
Here is the listener excerpt of my code:
public void actionPerformed(ActionEvent arg0) {
CardLayout l = (CardLayout) holder.getLayout();
if(arg0.getSource() == opt[1]){ //opt[1] is the "Next" button
//Insert if statement here to check if
//the CardLayout is on the last card
{
System.exit(0);
} else {
l.next(holder); //holder is the JPanel with the CardLayout
}
}
}
What about dispose() which is inherited from Window? Make sure you set:
jframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JFrame frame = ...
// ...
frame.setVisible(false); // hide the GUI
frame.dispose(); // destroy and release the GUI resources
For example:
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class CardLayoutGUI
{
private JFrame frame;
private JButton btnBack;
private JButton btnNext;
private CardLayout cLayout;
private JPanel panUp;
private JPanel panDown;
private static final String[] cards =
{"card1", "card2", "card3", "card4", "card5"};
private int currentCard = 0;
public void init()
{
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
((JPanel)frame.getContentPane()).setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
btnBack = new JButton("Back");
btnNext = new JButton("Next");
btnBack.addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
btnNext.setText("Next");
currentCard--;
cLayout.show(panUp, cards[currentCard]);
if(currentCard == 0) btnBack.setVisible(false);
}
});
btnNext.addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
btnBack.setVisible(true);
currentCard++;
if(currentCard == cards.length - 1) // last card
{
btnNext.setText("Exit");
cLayout.show(panUp, cards[currentCard]);
}
else if(currentCard >= cards.length)
{
frame.setVisible(false);
frame.dispose();
}
else
{
cLayout.show(panUp, cards[currentCard]);
}
}
});
cLayout = new CardLayout();
panUp = new JPanel(cLayout);
panDown = new JPanel();
frame.add(panUp, BorderLayout.CENTER);
frame.add(panDown, BorderLayout.SOUTH);
panDown.add(btnBack);
panDown.add(btnNext);
for(int i = 0; i < cards.length; i++) createPanels(panUp, cards[i]);
frame.pack();
frame.setLocationRelativeTo(null);
btnBack.setVisible(false);
}
public void showGUI()
{
frame.setVisible(true);
}
private void createPanels(JPanel container, String label)
{
JPanel pan = new JPanel();
pan.add(new JLabel(label));
container.add(pan, label);
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
CardLayoutGUI clg = new CardLayoutGUI();
clg.init();
clg.showGUI();
}
});
}
}
I extended CardLayout to add a few features. One of the features is an isNextCardAvailable() method. See Card Layout Focus for all the features.
The issue is determining which card is the last one. You could use a card String array index to manage the current position of the and use the show method to display the next "card". When you exceed the card array index you can then dispose your JFrame.
If you run the System.exit(0), that close all aplication, but if you only close the JFrame you can use JFrameObject.dispose().
Related
i have java swing Class that create JButton
it is working but what i need is when i pressed a JButton lets seed that it is number 1 the code in the ActionEvent is to change the Background of the JButton but what i need is if i pressed another JButton the first one i need it to go back to red Color :
Example :
package Classes;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class testbtn {
public JFrame frame = new JFrame();
public int copcounter = 5;
public testbtn() {
JPanel jdb = new JPanel();
jdb.setLayout(new FlowLayout());
for (int x = 1; x <= copcounter; x++) {
JButton btn = new JButton();
btn.setText(String.valueOf(x));
if (x == 1) {
btn.setBackground(Color.yellow);
} else {
btn.setBackground(Color.red);
}
btn.putClientProperty("id", x);
btn.addActionListener((ActionEvent e) -> {
btn.setBackground(Color.yellow);
System.out.println(e.getID());
});
jdb.add(btn);
}
frame.add(jdb);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new testbtn();
}
});
}
}
this code will show form like this :
when i press JButton 4 the output become like this :
but i need it to be like this so the other button will become red color but what i pressed i need it to become yellow ! :
i know that i can use id but how to get the id for them ! or if there is better way ?
No need for an id, simply put all the JButtons into a List<JButton>, say called buttonList, and then iterate through the list in the button's ActionListener, turning backgrounds red for all the buttons in the list, and then turn the current button's background yellow.
And then in the code that uses it:
public void actionPerformed(ActionEvent e) {
// iterate through the list
for (JButton button : buttonList) {
button.setBackground(Color.RED);
}
// then set *this* button's color yellow:
((JButton) e.getSource).setBackground(Color.YELLOW);
}
That's it
or for your code...
public class TestBtn {
public JFrame frame = new JFrame();
public int copcounter = 5;
private List<JButton> buttonList = new ArrayList<>();
public TestBtn() {
JPanel jdb = new JPanel();
jdb.setLayout(new FlowLayout());
for (int x = 1; x <= copcounter; x++) {
JButton btn = new JButton();
// add this
buttonList.add(btn);
btn.setText(String.valueOf(x));
if (x == 1) {
btn.setBackground(Color.yellow);
} else {
btn.setBackground(Color.red);
}
// btn.putClientProperty("id", x);
btn.addActionListener((ActionEvent e) -> {
// iterate through the list
for (JButton button : buttonList) {
button.setBackground(Color.RED);
}
// then set *this* button's color yellow:
((JButton) e.getSource).setBackground(Color.YELLOW);
// show button text
System.out.println(e.getActionCommand());
});
jdb.add(btn);
}
frame.add(jdb);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new testbtn();
}
});
}
}
Also, the ActionEvent's actionCommand String should match the button's text (with some exceptions).
I have recently started working with Java+Swing building an UI and I currently have an issue with JTextField placed on JPanel with FlowLayout.
In my example I have a window, containing panel with button. Clicking the button adds a component derived from JPanel and containing JTextField.
The problem is that when I type in JTextField it does not get updated (does not get resized). However when I resize the window or do anything else which forces window/panel redraw, the text field being resized (just what I expect to happen automatically).
When I change base class from JPanel to JTextField it works in the way I try to achieve, but I need to have JPanel as the base class so that I can take advantages of putting child components to it.
I have checked different questions here as well as I have Googled trying to find the solution, however it did not work for me. I have tried validate/invalidate/revalidate/repaint in different combinations and for different components, as well as trying to enforce revalidation for each typed character, which does not sound as the right way for me. So far I understoon that it is something to do with Layout Managers.
Could anyone please help me with understanding how that works and what should I read about how Swing UI, layout management and redrawing is working?
Also, I would be glad if someone could help me with my particular issue with my code.
Thanks in advance!
Here is my code below:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
class TagVisual extends JPanel /*JTextField*/ {
private JTextField editField;
public TagVisual() {
FlowLayout layout = new FlowLayout();
layout.setHgap(0);
layout.setVgap(0);
setLayout(layout);
editField = new JTextField();
editField.setBackground(Color.RED);
editField.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
editField.setSize(editField.getSize());
editField.revalidate();
remove(editField);
add(editField);
revalidate();
repaint();
}
});
add(editField, FlowLayout.LEFT);
}
public void place(JPanel panel) {
panel.add(this);
editField.grabFocus();
}
}
public class MainWindow {
private JPanel mainPanel;
private JButton btnPlace;
private JFrame frame;
public MainWindow(JFrame frame) {
mainPanel = new JPanel(new FlowLayout());
btnPlace = new JButton();
btnPlace.setText("Place");
mainPanel.add(btnPlace);
this.frame = frame;
btnPlace.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
TagVisual v = new TagVisual();
v.place(mainPanel);
mainPanel.revalidate();
mainPanel.repaint();
mainPanel.updateUI();
frame.revalidate();
frame.repaint();
}
});
}
public static void main(String[] args) {
JFrame frame = new JFrame("TextFieldUpdateIssue");
frame.setContentPane(new MainWindow(frame).mainPanel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
}
If i were you, i would not try to resize the textfields when the user enters some text.
I suggest you to give them a fixed size using JTextField (int columns) constructor, this will allow you to create some textfields which are "wide enough".
If you still want to make them wider when some text is entered, you can't use an ActionListener, since it will fire an event when the user presses ENTER key, not based on the text entered.
For this purpose you can register a Document Listener on your textfield's document.
You also could override getPreferredSize () method to calculate and return an appropriate size. In the example below i use a JLabel for convenience to calculate the preferred width, but you could use FontMetrics.
If you are adding multiple tags to your panel, you should also consider using a JScrollPane in order to make scrollbars appear when your panel needs more space.
See this example (i changed a bit your code because it would not compile and the general design was bad, now i think it is better, but not still good) :
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
public class MainWindow
{
public static void main (String [] a) {
SwingUtilities.invokeLater (new Runnable () {
#Override public void run () {
try {
UIManager.setLookAndFeel (UIManager.getSystemLookAndFeelClassName ());
createAndShowGUI ();
}
catch (Exception e) {
JOptionPane.showMessageDialog (null, "An unexpected error occurred: " + e.getClass ().getSimpleName (), "Error", JOptionPane.ERROR_MESSAGE);
}
}
});
}
private static void createAndShowGUI () {
JFrame frame = new JFrame ("TextFieldUpdateIssue");
frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
frame.setContentPane (new MainPanel ());
frame.setExtendedState (JFrame.MAXIMIZED_BOTH);
frame.setLocationRelativeTo (null);
frame.setVisible (true);
}
}
class MainPanel extends JPanel
{
private JPanel tagsPanel;
public MainPanel () {
super (new BorderLayout (0, 10));
add (new JButton (new AbstractAction ("Add tag") {
#Override public void actionPerformed(ActionEvent e) {
addNewTag ();
}
}), BorderLayout.NORTH);
tagsPanel = new JPanel ();
tagsPanel.setLayout (new FlowLayout (FlowLayout.CENTER, 10, 0));
add (tagsPanel, BorderLayout.CENTER);
}
private void addNewTag () {
TagVisual v = new TagVisual ();
tagsPanel.add (v);
v.grabFocusOnField ();
revalidate ();
}
}
class TagVisual extends JPanel
{
private JTextField editField;
public TagVisual() {
super (new FlowLayout (FlowLayout.CENTER, 0, 0));
add (editField = createNewTextField (null), FlowLayout.LEFT);
}
private JTextField createNewTextField (String text) {
JTextField textField = new JTextField (text) {
#Override public Dimension getPreferredSize () {
Dimension d = super.getPreferredSize ();
return new Dimension (new JLabel (getText ()).getPreferredSize ().width + 10, d.height);
}
};
textField.setBackground (Color.RED);
textField.getDocument ().addDocumentListener (new DocumentListener () {
#Override public void changedUpdate (DocumentEvent e) {
revalidate ();
}
#Override public void insertUpdate (DocumentEvent e) {
revalidate ();
}
#Override public void removeUpdate (DocumentEvent e) {
revalidate ();
}
});
return textField;
}
public void grabFocusOnField () {
editField.grabFocus ();
editField.setCaretPosition (editField.getText ().length ());
}
}
Screenshot (short text):
Screenshot (Longer text):
Please review the code and note comments:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class MainWindow {
private JPanel mainPanel;
private JButton btnPlace;
public MainWindow(){
JFrame frame = new JFrame("TextFieldUpdateIssue");
//you can't use components before initializing them
btnPlace = new JButton("Button");
frame.add(btnPlace, BorderLayout.NORTH);
mainPanel = new JPanel();
frame.add(mainPanel, BorderLayout.CENTER);
btnPlace.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
TagVisual v = new TagVisual();
mainPanel.add(v); //add it to main panel
//v.place(mainPanel);
//mainPanel.revalidate();
//mainPanel.repaint();
//mainPanel.updateUI();
//frame.revalidate();
//frame.repaint();
frame.pack();
}
});
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
new MainWindow();
}
}
class TagVisual extends JPanel /*JTextField*/ {
private JTextField editField;
public TagVisual() {
FlowLayout layout = new FlowLayout();
layout.setHgap(0);
layout.setVgap(0);
setLayout(layout);
editField = new JTextField();
//give it a preferred size to be used by layout manager
editField.setPreferredSize(new Dimension(150,25));
editField.setBackground(Color.RED);
editField.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
//not sure what you want to do here
//not relevant to the question
}
});
add(editField, FlowLayout.LEFT);
}
}
I have a quick question.
I'm getting a little bit of experience with Swing and the easiest way to do this was to draw up a reasonably big GUI.
As part of the GUI, I want to have Forward and Back Buttons. The Approach I'm trying to take is to implement methods that will push the current JPanel to a stack and retrieve the previous value (Be that in a forwards or reverse direction (hence 2 stacks)). I can't get it to work though. Perhaps I'm going about it completely the wrong way or maybe a stack can't be used int the way I'm using it. In either case, it's really bugging me. I imagine there are probably easier ways like a card layout but I think this approach should work and that's what's so annoying.
It may be worth noting that I'm using a JFrame "base class" and changing the central JPanel depending on the screen. The nav bar is constant as a part of the "base class" however
The code of this "base class":
public class Main_Frame extends JFrame{
static JPanel nav_bar_panel;
JButton home;
JButton back;
JButton forward;
JPanel currentPanel;
static Stack<JPanel> previousPanels;
static Stack<JPanel> forwardPanels;
public Main_Frame(){
super("DEMO");
setSize(800,600);
setLayout(new BorderLayout());
setVisible(true);
add(nav_bar(), BorderLayout.NORTH);
currentPanel = init_display();
add(currentPanel, BorderLayout.CENTER);
previousPanels = new Stack<JPanel>();
forwardPanels = new Stack<JPanel>();
}
private JPanel nav_bar(){
ButtonPressHandler handler = new ButtonPressHandler();
nav_bar_panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 10));
back = new JButton("Back");
back.addActionListener(handler);
home = new JButton("Home");
home.addActionListener(handler);
forward = new JButton("Forward");
forward.addActionListener(handler);
nav_bar_panel.add(back);
nav_bar_panel.add(home);
nav_bar_panel.add(forward);
return nav_bar_panel;
}
private JPanel init_display(){
Home_Panel home_panel = new Home_Panel();
return home_panel;
}
public void change_display(JPanel myPanel){
invalidate();
remove(currentPanel);
previousPanels.push(currentPanel);
currentPanel = myPanel;
add(currentPanel);
validate();
}
public void previous_display(){
if(!previousPanels.empty()){
invalidate();
remove(currentPanel);
forwardPanels.push(currentPanel);
currentPanel = previousPanels.pop();
add(currentPanel);
validate();
}
}
public void forward_display(){
if(!forwardPanels.empty()){
invalidate();
remove(currentPanel);
previousPanels.push(currentPanel);
currentPanel = forwardPanels.pop();
add(currentPanel);
validate();
}
}
private class ButtonPressHandler implements ActionListener
{
public void actionPerformed( ActionEvent event )
{
if(event.getSource() == back){
previous_display();
System.out.print("You selected back");
} else if(event.getSource() == forward){
forward_display();
System.out.print("You selected forward");
}
} // end method actionPerformed
} // end private inner class TextFieldHandler
}
Here's an example using CardLayout.
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.util.Random;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
/** #see http://stackoverflow.com/questions/5654926 */
public class CardPanel extends JPanel {
private static final Random random = new Random();
private static final JPanel cards = new JPanel(new CardLayout());
private final String name;
public CardPanel(String name) {
this.name = name;
this.setPreferredSize(new Dimension(320, 240));
this.setBackground(new Color(random.nextInt()));
this.add(new JLabel(name));
}
#Override
public String toString() {
return name;
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
create();
}
});
}
private static void create() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
for (int i = 1; i < 9; i++) {
CardPanel p = new CardPanel("Panel " + String.valueOf(i));
cards.add(p, p.toString());
}
JPanel control = new JPanel();
control.add(new JButton(new AbstractAction("\u22b2Prev") {
#Override
public void actionPerformed(ActionEvent e) {
CardLayout cl = (CardLayout) cards.getLayout();
cl.previous(cards);
}
}));
control.add(new JButton(new AbstractAction("Next\u22b3") {
#Override
public void actionPerformed(ActionEvent e) {
CardLayout cl = (CardLayout) cards.getLayout();
cl.next(cards);
}
}));
f.add(cards, BorderLayout.CENTER);
f.add(control, BorderLayout.SOUTH);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
the idea of making whatever I get reusable is a good one. Pity Swing didn't have this functionality built in though
Check out Card Layout Actions which is may attempt at making the Card Layout a little easier to use for something like this.
The way I usually do it is as follows:
I've got a StepManager class (write it once, use it forever) which handles all logic related to the steps. It got methods like next(), previous(), reset(), isFirst() and isLast().
I've then got 'Next' and 'Previous' buttons with appropriate actions (or whatever you choose to use to listen for user interaction).
The code related to the 'Next' button calls stepManager.next() to retrieve the index for the next step. Then (when I've got the next step) I simply invoke (another method) showStep(int index) to display the actual step user interface corresponding to the current step index.
Each step is a separate JPanel (Step01, Step02, Step03...).
public void showStep(int index) {
ContentPanel.removeAll();
ContentPanel.setLayout(new BorderLayout());
switch (index) {
case 0:
ContentPanel.add(Step01, BorderLayout.CENTER);
break;
case 1:
ContentPanel.add(Step02, BorderLayout.CENTER);
break;
case 2:
ContentPanel.add(Step03, BorderLayout.CENTER);
break;
case 3:
ContentPanel.add(Step04, BorderLayout.CENTER);
}
ContentPanel.validate();
ContentPanel.repaint();
}
I have a JFrame, and whenever I switch from one JFrame using a JButton it starts out normally, but whenever I create a new instance of the first JFrame, the JButton is in an incorrect location and is the wrong size.
Example on startup
and when another one is created
Code:
public class Menu extends JFrame implements Runnable {
private static final long serialVersionUID = 1L;
public static int Number_of_Participants = 0;
protected JPanel window = new JPanel();
double p;
private JButton Participants;
private Rectangle rParticipants;
protected int Button_width = 240;
protected int Button_height = 48;
boolean running = false;
Thread thread;
JFrame frame = new JFrame();
public Menu() {
window.setBackground(Color.BLUE);
frame.setSize(new Dimension(800, 600));
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
frame.getContentPane().add(window);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
Image image = null;
try {
image = ImageIO.read(new File("res/BG.jpg"));
} catch (IOException e) {
e.printStackTrace();
}
generateFiles();
drawButtons();
startMenu();
frame.repaint();
}
public void drawButtons() {
rParticipants = new Rectangle(520, 12, Button_width, Button_height);
Participants = new JButton("A");
Participants.setBounds(rParticipants);
window.add(Participants);
Participants.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
frame.dispose();
new Participant(Number_of_Participants);
}
});
}
}
Participant.java extends Menu.java
int Participant_ID;
public Participant(int Participant_ID) {
super();
this.Participant_ID = Participant_ID;
}
makes a JButton that goes back to Menu.java
As mentioned in the comment, your problem is most likely related to the call to setVisible(true). This should always be the LAST call in the constructor. Particularly, it should only be called AFTER all components have been added to the frame.
Apart from that, from the code that you posted, it seems like you want to switch through a seqence of frames, starting with a "main" menu, and then going through one frame for each "Participant". This intention could already be considered as questionable, because closing and disposing a JFrame just in order to create a new one does not seem to be very elegant. Most likely, a more elegant solution would be possible with a CardLayout : http://docs.oracle.com/javase/tutorial/uiswing/layout/card.html
However, some general hints:
Create the GUI on the Event Dispatch Thread
Don't extend JFrame. Instead, create a JFrame and fill it as needed
Don't implement Runnable with your top level class
Obey the standardJavaNamingConventions!
Don't try to do manual layouts with setBounds
This code is still not "beautiful", but at least shows how the goal of switching through several frames might be achieved, taking into account these points
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class MenuExample
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
JPanel mainMenuPanel = new MainMenuPanel();
createAndShowFrame(mainMenuPanel);
}
});
}
static void createAndShowFrame(JPanel panel)
{
JFrame frame = new JFrame();
frame.getContentPane().add(panel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(new Dimension(800, 600));
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
static JButton createNextParticipantButton(
final JComponent container, final int nextID)
{
JButton nextParticipantButton = new JButton("New Participant");
nextParticipantButton.addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
Window window =
SwingUtilities.getWindowAncestor(container);
window.dispose();
ParticipantPanel participantPanel =
new ParticipantPanel(nextID);
createAndShowFrame(participantPanel);
}
});
return nextParticipantButton;
}
}
class MainMenuPanel extends JPanel
{
public MainMenuPanel()
{
setBackground(Color.BLUE);
add(MenuExample.createNextParticipantButton(this, 0));
}
}
class ParticipantPanel extends JPanel
{
private final int participantID;
public ParticipantPanel(int participantID)
{
this.participantID = participantID;
add(new JLabel("Add the contents for participant "+participantID));
add(MenuExample.createNextParticipantButton(this, participantID+1));
}
}
I'm still very new to java programming, so please help me to correct any mistakes I might have overlooked or give tips on how to improve this program.
Okay, so a lot of problems have been solved, and now I have a CardLayout, but I still have questions about how I should make my pipes show inside it.
When I tried to add in my refresh rate timer and my speed timer, I have problems about how I need to declare and initialize boolean variables.
Also, when I compile and run this game, I get files such as Game$1.class. Is there a way for me to clean this up, and could someone explain why this happens? Do these have an affect on the finished product? (When the game is compiled and packaged into a JAR.)
I want to set playerIsReady to true when the play button is clicked. And from there, when the if statement is true, then switch to a panel that displays the pipes, and start moving the pipe across the screen. Preferably 3 instances of that pipe, each starting at different times, but whatever you can help with is fine.
Some of this code needs work, so I have commented some parts out and left notes.
My other questions about this game can be found here.
This is my current code
Game.java
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.border.EmptyBorder;
import javax.swing.SwingUtilities;
public class Game {
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
// the GUI as seen by the user (without frame)
final CardLayout cl = new CardLayout();
final JPanel gui = new JPanel(cl);
// remove if no border is needed
gui.setBorder(new EmptyBorder(10,10,10,10));
JPanel menu = new JPanel(new GridBagLayout());
JButton playGame = new JButton("Play!");
ActionListener playGameListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
cl.show(gui, "game");
}
};
playGame.addActionListener(playGameListener);
Insets margin = new Insets(20, 50, 20, 50);
playGame.setMargin(margin);
menu.add(playGame);
gui.add(menu);
cl.addLayoutComponent(menu, "menu");
final JPanel pipes = new Pipes();
gui.add(pipes);
cl.addLayoutComponent(pipes, "game");
JFrame f = new JFrame("Pipes Game");
f.add(gui);
// Ensures JVM closes after frame(s) closed and
// all non-daemon threads are finished
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// See https://stackoverflow.com/a/7143398/418556 for demo.
f.setLocationByPlatform(true);
// ensures the frame is the minimum size it needs to be
// in order display the components within it
f.pack();
// should be done last, to avoid flickering, moving,
// resizing artifacts.
f.setVisible(true);
/*if (playerIsReady) {
Timer speed = new Timer(10, new ActionListener() { //pipe speed
#Override
public void actionPerformed(ActionEvent e) {
pipes.move();
}
});
speed.start();
Timer refresh = new Timer(30, new ActionListener() { //refresh rate
#Override
public void actionPerformed(ActionEvent e) {
pipes.repaint();
}
});
refresh.start();
}*/
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency
SwingUtilities.invokeLater(r);
}
}
Pipes.java
// What import(s) do I need for ArrayList?
public class Pipes {
List<Pipe> pipes = new ArrayList<Pipe>();
public Pipes() {
pipes.add(new Pipe(50, 100));
pipes.add(new Pipe(150, 100));
pipes.add(new Pipe(250, 100));
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for ( Pipe pipe : pipes ){
pipe.drawPipe(g);
}
}
}
PipeObject.java
import java.awt.Graphics;
public class PipeObject {
//Declare and initialiaze variables
int x1 = 754; //xVal start
int x2 = 75; //pipe width
//total width is 83
int y1 = -1; //yVal start
int y2 = setHeightVal(); //pipe height
int gap = 130; //gap height
public void drawPipe(Graphics g) {
g.clearRect(0,0,750,500); //Clear screen
g.drawRect(x1,y1,x2,y2); //Draw part 1
g.drawRect(x1-3,y2-1,x2+6,25); //Draw part 2
g.drawRect(x1-3,y2+25+gap,x2+6,25); //Draw part 3
g.drawRect(x1,y2+25+gap+25,x2,500-y2-49-gap); //Draw part 4
}
public void move() {
x1--;
}
public int getMyX() { //To determine where the pipe is horizontally
return x1-3;
}
public int getMyY() { //To determine where the pipe is vertically
return y2+25;
}
public int setHeightVal() { //Get a random number and select a preset height
int num = (int)(9*Math.random() + 1);
int val = 0;
if (num == 9)
{
val = 295;
}
else if (num == 8)
{
val = 246;
}
else if (num == 7)
{
val = 216;
}
else if (num == 6)
{
val = 185;
}
else if (num == 5)
{
val = 156;
}
else if (num == 4)
{
val = 125;
}
else if (num == 3)
{
val = 96;
}
else if (num == 2)
{
val = 66;
}
else
{
val = 25;
}
return val;
}
}
The best way to approach this is using a CardLayout.
Notes
A button with an ActionListener is far better than a MouseListener over a rectangle.
The button will show focus when the mouse is pointed at it, or the component is tabbed to via the keyboard.
The button is keyboard accessible.
The button has facility to support multiple icons built in (e.g. for 'initial look', focused, pressed etc.)
White space in the GUI is provided around the menu panel and game by adding an EmptyBorder
The button is made larger by setting a margin.
Adjust margins, borders and preferred size according to need. These sizes were set by me so as not to make the screenshots too large.
See more tips in the code comments.
Code
Here is the MCTaRE (Minimal Complete Tested and Readable Example) that produced the above screenshots.
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
public class PipesGame {
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
// the GUI as seen by the user (without frame)
final CardLayout cl = new CardLayout();
final JPanel gui = new JPanel(cl);
// remove if no border is needed
gui.setBorder(new EmptyBorder(10,10,10,10));
JPanel menu = new JPanel(new GridBagLayout());
JButton playGame = new JButton("Play!");
ActionListener playGameListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
cl.show(gui, "game");
}
};
playGame.addActionListener(playGameListener);
Insets margin = new Insets(20, 50, 20, 50);
playGame.setMargin(margin);
menu.add(playGame);
gui.add(menu);
cl.addLayoutComponent(menu, "menu");
JPanel pipes = new Pipes();
gui.add(pipes);
cl.addLayoutComponent(pipes, "game");
JFrame f = new JFrame("Pipes Game");
f.add(gui);
// Ensures JVM closes after frame(s) closed and
// all non-daemon threads are finished
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// See https://stackoverflow.com/a/7143398/418556 for demo.
f.setLocationByPlatform(true);
// ensures the frame is the minimum size it needs to be
// in order display the components within it
f.pack();
// should be done last, to avoid flickering, moving,
// resizing artifacts.
f.setVisible(true);
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency
SwingUtilities.invokeLater(r);
}
}
class Pipes extends JPanel {
Pipes() {
setBackground(Color.BLACK);
setForeground(Color.WHITE);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawString("Pipes game appears here..", 170, 80);
}
#Override
public Dimension getPreferredSize() {
// adjust to need
return new Dimension(500,150);
}
}
"Is there a way for me to add my GameMenu jpanel to my jframe, and then replace it with the Pipes jpanel?"
As other have suggested, for this you want a CardLayout. It is very simple to you. Personally, I always wrap my CardLayout in a JPanel rather than the JFrame, just force of habit.
What you want to do is have a mainPanel that will have the CardLayout
CardLayout card = new CardLayout();
JPanel mainPanel = new JPanel();
Then you want to add your panels to the mainPanel. What the CardLyaout does is layer the panels, making just one visible at a time. The first one you add, will the in the foreground. Also when you add the panel, you'll also want to issue it a key it can be called from. The key, can be any String you like.
mainPanel.add(gameMenu, "menu");
mainPnael.add(pipes, "pipe");
Now gameMenu is the only panel shown. To show pipes, all you do is use this method
public void show(Container parent, String name) - Flips to the parent that was added to this layout with the specified name, using addLayoutComponent. If no such component exists, then nothing happens.
So you'd use, card.show(mainPanel, "pipes");
Whatever even you want to trigger the showing of pipes, just add that line in that event handler. You could add a button or something to the GameMenu that will allow movement to the Pipes panel.
This works with a mouse click on the menu. You can change it later, to a click on some button or whatever you want.
I added a MouseListener to the Game class. When the user presses the mouse on the menu JPanel, it adds the Pipes JPanel to JFrame and calls the pack method.
Game.java:
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class Game {
GameMenu menu = new GameMenu();
Pipes game;
boolean start = false;
JFrame f;
Rectangle2D menuRect = new Rectangle2D.Double(20, 20, 60, 40);
public Game() {
f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(menu);
f.setTitle("Pipe Game");
f.setResizable(false);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
menu.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
Point click = new Point(e.getX(), e.getY());
System.out.println("Clicked on the Panel");
if(menuRect.contains(click))
{
System.out.println("Clicked inside the Rectangle.");
start = true;
menu.setVisible(false);
game = new Pipes();
f.add(game);
f.pack();
Timer timer = new Timer(10, new ActionListener() { //pipe speed
#Override
public void actionPerformed(ActionEvent e) {
game.move();
}
});
timer.start();
Timer refresh = new Timer(30, new ActionListener() { //refresh rate
#Override
public void actionPerformed(ActionEvent e) {
game.repaint();
}
});
refresh.start();
}
}
#Override
public void mouseReleased(MouseEvent e) {
}
});
}
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Game();
}
});
}
}