JButton that won't transition between Cards while using CardLayout - java

So I'm new to programming and I've been making a program that uses multiple JPanels on a JFrame and other JPanels. I'm using CardLayout to go between different JPanels and I have it working on two different JButtons, but I can't get the last one to return to the main screen.
I've looked for answers, but it seems like most people just forget to use an ActionListener, something that I know I've done. Here's some of the involved classes in my code (There are a lot so I won't include them all, but I can provide any others that are needed).
Here's the JFrame class:
import javax.swing.*;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.event.*;
public class ElephantCare extends JFrame {
private static final long serialVersionUID = 1L;
private final String MAIN_STRING = "Main";
public JPanel container, main;
private Staff1Panel staff1 = new Staff1Panel();
private Staff2Panel staff2 = new Staff2Panel();
private StaffConfirmPanel staffConfirm = new StaffConfirmPanel();
private WelcomePanel welcome = new WelcomePanel();
private StaffPanel staff = new StaffPanel();
private GallonsPanel gallons = new GallonsPanel();
private ToysPanel toys= new ToysPanel();
private ActionPanel action = new ActionPanel();
public CardLayout card = new CardLayout();
public ElephantCare() {
setSize(400,300);
setTitle("Elephant Care");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
buildPanel();
add(container);
setVisible(true);
}
private void buildPanel() {
main = new JPanel();;
container = new JPanel(card);
container.add(main, MAIN_STRING);
container.add(staff1, "Staff 1");
container.add(staff2, "Staff 2");
main.setLayout(new BorderLayout());
main.add(welcome, BorderLayout.NORTH);
main.add(staff, BorderLayout.WEST);
main.add(gallons, BorderLayout.CENTER);
main.add(toys, BorderLayout.EAST);
main.add(action, BorderLayout.SOUTH);
staff.getStaff1Button().addActionListener(new Staff1Listener());
staff.getStaff2Button().addActionListener(new Staff2Listener());
staffConfirm.getConfirmButton().addActionListener(new ConfirmButtonListener());
}
private class Staff1Listener implements ActionListener {
public void actionPerformed(ActionEvent e) {
card.show(container, "Staff 1");
}
}
private class Staff2Listener implements ActionListener {
public void actionPerformed(ActionEvent e) {
card.show(container, "Staff 2");
}
}
private class ConfirmButtonListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
card.show(container, MAIN_STRING);
}
}
}
Here's the JPanel with the Button:
import javax.swing.*;
public class StaffConfirmPanel extends JPanel{
private static final long serialVersionUID = 1L;
public JButton confirm;
public StaffConfirmPanel() {
confirm = new JButton("OK");
add(confirm);
}
public JButton getConfirmButton() {
return confirm;
}
}
And here's the JPanels where the button is used:
import java.awt.BorderLayout;
import javax.swing.*;
public class Staff1Panel extends JPanel{
private static final long serialVersionUID = 1L;
private Staff1NamePanel name = new Staff1NamePanel();
private Staff1JobPanel job = new Staff1JobPanel();
private StaffConfirmPanel confirm = new StaffConfirmPanel();
public Staff1Panel() {
setLayout(new BorderLayout());
add(name, BorderLayout.WEST);
add(job, BorderLayout.EAST);
add(confirm, BorderLayout.SOUTH);
}
}
And:
import java.awt.BorderLayout;
import javax.swing.*;
public class Staff2Panel extends JPanel{
private static final long serialVersionUID = 1L;
private Staff2NamePanel name = new Staff2NamePanel();
private Staff2JobPanel job = new Staff2JobPanel();
private StaffConfirmPanel confirm = new StaffConfirmPanel();
public Staff2Panel() {
setLayout(new BorderLayout());
add(name, BorderLayout.WEST);
add(job, BorderLayout.EAST);
add(confirm, BorderLayout.SOUTH);
}
}
Thanks for any help!

There are a lot of things happening in this code that are not quite right, and they contribute to your issue. So lets address the biggest issue and then you will need to do the rest yourself.
First edit this code to have some debug text:
private class ConfirmButtonListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
//NEW LINE OF CODE
System.out.println("ConfirmButtonListener was triggered");
card.show(container, MAIN_STRING);
}
}
Now when you run your code you will notice that the message "ConfirmButtonListener was triggered" will never be printed to the console, meaning that the code never runs, so you can never return to the main screen.
This happens because you create a StaffConfirmPanel named staffConfirm in your ElephantCare class. Then you add an action listener to staffConfirm. The problem is that you never use that staffconfirm panel anywhere because you create a new StaffConfirmPanel inside Staff1Panel and Staff2Panel so the action listener you have made will do nothing.
So the solution is to move the whole ConfirmButtonListener method and the staffConfirm.getConfirmButton().addActionListener line into the StaffConfirmPanel class like so:
import javax.swing.*;
public class StaffConfirmPanel extends JPanel{
private static final long serialVersionUID = 1L;
public JButton confirm;
public StaffConfirmPanel() {
confirm = new JButton("OK");
add(confirm);
//NEW LINE: SET THE ACTION LISTENER
confirm.addActionListener(new ConfirmButtonListener());
}
public JButton getConfirmButton() {
return confirm;
}
//NEW CODE, MOVE THE ACTION LISTENER METHOD HERE
//ACTION LISTENER MOVED HERE
private class ConfirmButtonListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
//NEW LINE OF CODE
System.out.println("ConfirmButtonListener was triggered");
card.show(ElephantCare.container, ElephantCare.MAIN_STRING);
}
}
}
Now it should work correctly and return to the main screen.
EDIT: you will need to make MAIN_STRING a public variable in the ElephantCare class.

Related

Where to put action listeners?

I'm a little confused about where the best class is for ActionListener instances in a small Swing application.
If I have a Controller, a MainFrame for the main JFrame and various JPanel containers such as LeftPanel, RightPanel etc, each with buttons, a list etc.
Where's the best place to put the action listeners? Should I have them in the same class as the components (as an inner class or class implementing action listener), in the MainFrame as this is the 'parent' of all the panels, or in the Controller, which really only has the main() method and a few other Swing details?
Each approach seems to have its pros and cons. I am trying to un-couple features from functionality but I'm finding it hard to work this out.
Or have I missed another (better) way?
One possible for the panels to communicate with each other is through the main frame. Each panel would have a reference to the main frame and the main frame provides all the operations such as reading data from the form, updating data to the form. Below is an example:
class UserForm extends JPanel {
private final MyApp app;
private final JTextField userNameField = new JTextField(20);
public UserForm(MyApp app){
this.app = app;
this.add(userNameField);
}
public String getUserName(){
return userNameField.getText();
}
public void setUserName(String name){
userNameField.setText(name);
}
}
class FormControl extends JPanel {
private final MyApp app;
private final JButton randNameButton = new JButton("Random");
private final JButton saveButton = new JButton("Save");
private final String[] names = {"john", "mike", "bill", "joe"};
private final Random r = new Random();
public FormControl(MyApp app){
this.app = app;
this.setLayout(new FlowLayout());
this.add(randNameButton);
this.add(saveButton);
randNameButton.addActionListener(e->app.setUserName(names[r.nextInt(4)]));
saveButton.addActionListener(e->app.save());
}
}
public class MyApp extends JFrame{
private final UserForm form = new UserForm(this);
private final FormControl control = new FormControl(this);
public MyApp(){
this.setTitle("Demo");
this.setSize(200, 100);
JPanel pane = new JPanel();
pane.setSize(200,100);
pane.setLayout(new BorderLayout());
pane.add(form, BorderLayout.NORTH);
pane.add(control, BorderLayout.SOUTH);
this.setContentPane(pane);
}
// all the operations are here.
public String getUserName(){
return form.getUserName();
}
public void setUserName(String userName){
form.setUserName(userName);
}
public void save(){
System.out.println("Saving");
System.out.println(getUserName());
System.out.println("Done saving.");
}
public static void main(String[] args) {
new MyApp().setVisible(true);
}
}
I would use lambda for action listener. See example below:
public class JavaApplication7 extends JFrame{
public JavaApplication7(){
this.setTitle("Demo");
this.setSize(100, 100);
JButton b = new JButton("Click");
b.addActionListener(e->{
System.out.println("hello");
System.out.println("from " + this.getTitle());
});
this.setContentPane(b);
}
public static void main(String[] args) {
new JavaApplication7().setVisible(true);
}
}

How to count number of pushes on Button

I'm trying to count the number of pushes on a button in my program.
I want it to change everytime I push the button, to show the number of pushes done by the user.
Here is my code:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*; // needed for listeners
public class PushCounter3 {
public static void main(String[] arg) {
PushGUI myGui = new PushGUI();
}
}
class PushGUI extends JPanel{
private JFrame theWindow;
private int nbPushes;
private JButton myButton;
private JLabel myLabel;
private JPanel myPanel;
PushGUI(){
theWindow = new JFrame("Push Counter that counts!");
theWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
nbPushes = 0;
myButton = new JButton("Push Me!");
myLabel = new JLabel("Pushes: " + Integer.toString(nbPushes));
// let's register the event listener
myButton.addActionListener(new ButtonListener());
myPanel = new JPanel();
theWindow.add(myPanel);
myPanel.add(myButton);
myPanel.add(myLabel);
theWindow.pack();
theWindow.setVisible(true);
}
private class ButtonListener implements ActionListener {
public void actionPerformed (ActionEvent event) {
nbPushes++;
myLabel.setText("Pushes: " + Integer.toString(nbPushes));
}
}
}
Everything shows on the screen, but doesn't get updated everytime I click on the button.
make nbPushes variable static and see if it updates correctly

Type a text in JTextArea from another class

I have a problem with updating a textArea from another class.
I need a textArea to show a text while pressing a button.
So when I press a buton I make a method actionPerformed() in ParceListener to print a text in a textArea which is located in MainFormAppearance class. But it doesn't do that. Could you please help me?
public class Main {
public static void main(String[] args) {
JFrame.setDefaultLookAndFeelDecorated(true);
JFrame jFrame = new JFrame("Title");
MainFormAppearance demo = new MainFormAppearance();
jFrame.setContentPane(demo.createContentPanel());
jFrame.setDefaultCloseOperation(jFrame.EXIT_ON_CLOSE);
jFrame.setSize(400,300);
jFrame.setVisible(true);
}
}
MainFormAppearance
package com.company;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class MainFormAppearance {
public JPanel totalGui;
public JTextArea frame;
public JLabel blueLabel;
public JButton parceButton;
public JButton mailButton;
public ParceListener parceListener;
public JPanel createContentPanel(){
totalGui = new JPanel();
frame = new JTextArea();
blueLabel = new JLabel("Some program");
parceButton = new JButton("Button 1");
mailButton = new JButton("Button 2");
parceListener = new ParceListener();
totalGui.setLayout(null);
//set program window
blueLabel.setLocation(10,10);
blueLabel.setSize(400,20);
blueLabel.setHorizontalAlignment(SwingConstants.CENTER);
blueLabel.setForeground(Color.blue);
totalGui.add(blueLabel);
//set Button 1
parceButton.setLocation(270, 50);
parceButton.setSize(100,30);
totalGui.add(parceButton);
//Pressing the Button 1
parceButton.addActionListener(parceListener);
//set Button 2
mailButton.setLocation(270, 100);
mailButton.setSize(100, 30);
totalGui.add(mailButton);
frame.setLocation(20, 115);
frame.setSize(200, 15);
totalGui.add(frame);
totalGui.setOpaque(true);
return totalGui;
}
public void setTextArea(String myString){
frame.append(myString);
}
}
ParceListener
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class ParceListener implements ActionListener {
public String text = "some text";
MainFormAppearance mainFormAppearance = new MainFormAppearance();
public void actionPerformed(ActionEvent e) {
mainFormAppearance.setTextArea(text);
}
}
It shows NullPointerException at frame.append(myString); in MainFormAppearance class.
Calling MainFormAppearance mainFormAppearance = new MainFormAppearance(); in your ParceListener is creating a new instance of MainFormAppearance which has nothing to do with what is actually been presented on the screen.
You need some way to return information back to the main UI from ParceListener.
This is best accomplished using an Observer Pattern, where ParceListener generates notifications/events when something changes. It shouldn't care about "who" is interested, only that they are.
Let's start with a simple interface...
public interface ParceObserver {
public void parceChanged(String text);
}
MainFormAppearance can now implement this interface and make what ever updates it needs.
public class MainFormAppearance implements ParceObserver {
//...
public void parceChanged(String text) {
frame.append("\n" + text);
}
}
Then thing here is, ParceListener, doesn't care what happens after it's posted the notification.
Now, you just need to pass an instance of ParceObserver to ParceListener
parceListener = new ParceListener(this);
And update ParceListener to make use of it...
public class ParceListener implements ActionListener {
private ParceObserver observer;
public String text = "some text";
public ParceListener(ParceObserver observer) {
this.observer = observer;
}
public void actionPerformed(ActionEvent e) {
if (observer == null) {
return null;
}
observer.parceChanged(text);
}
}
Now it's nicely de-coupled and re-usable.
And, if someone tells you to just pass a reference of the JTextArea or MainFormAppearance to ParceListener, please don't listen to them. It's inappropriate, tightly couples your code and exposes the components to the risk of been modified in ways you never intended them to be

Run new GUI window from an event of another class

I have 2 classes. Both implements runnable to create the GUI. The first one is the main, and the second one is the secondary class.
I want within the actionlistener of the main class to startup the secondary class.
Here is the code (the two classes are separated files):
public class Main implements Runnable
{
private JTextField txt1, txt2;
private JLabel lbl1, lbl2;
public void run()
{
JFrame frame = new JFrame("Secondary");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container pane = frame.getContentPane();
JPanel background = new JPanel();
background.setLayout(new BoxLayout(background, BoxLayout.LINE_AXIS));
.........
// Horizontally adding the textbox and button in a Box
Box box = new Box(BoxLayout.Y_AXIS);
......
background.add(box);
pane.add(background);
frame.pack();
frame.setVisible(true);
}
private class SListener implements ActionListener
{
public void actionPerformed(ActionEvent a)
{
Secondary s = new Secondary();
}
}
public static void main (String[] args)
{
Main gui = new Main();
SwingUtilities.invokeLater(gui);
}
}
public class Secondary implements Runnable
{
private JTextField txt1, txt2;
private JLabel lbl1, lbl2;
public Secondary()
{
Secondary gui = new Secondary();
SwingUtilities.invokeLater(gui);
}
public void run()
{
JFrame frame = new JFrame("Secondary");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container pane = frame.getContentPane();
JPanel background = new JPanel();
background.setLayout(new BoxLayout(background, BoxLayout.LINE_AXIS));
.........
// Horizontally adding the textbox and button in a Box
Box box = new Box(BoxLayout.Y_AXIS);
......
background.add(box);
pane.add(background);
frame.pack();
frame.setVisible(true);
}
}
I want to keep the code in two files, I don't want to mixed the two classes in one file.
As you can see from the code, in the Secondary class, in it's constructor I create an Instance of the Secondary class and I run the gui so that when the Instance of this class is created in the Main class, to run the gui.
Unfortunately this technique is not working.
Any ideas?
Thanks
The following line are complety wrong:
public Secondary(){
Secondary gui = new Secondary();
SwingUtilities.invokeLater(gui);
}
Each time you call new Secondary() somewhere in your code, the above code will be triggered, which in turn calls new Secondary() again, and again, and again, ... and your program is blocked.
You probably want to replace it either by
public Secondary(){
SwingUtilities.invokeLater(this);
}
which will avoid the loop, but this is weird behaviour for a constructor.
It makes much more sense to switch to an empty constructor (or delete it all together)
public Secondary(){
}
and rewrite your listener to
public void actionPerformed(ActionEvent a){
Secondary s = new Secondary();
SwingUtilities.invokeLater( s );
}
I would recommend that you completely re-design your program. I find that it is most helpful to gear my GUI's towards creation of JPanels, not top level windows such as JFrame, which can then be placed into JFrames or JDialogs, or JTabbedPanes, or swapped via CardLayouts, wherever needed. I find that this greatly increase the flexibility of my GUI coding, and is exactly what I suggest that you do. So...
Your first class creates a JPanel that is then placed into a JFrame.
In the first class's ActionListener, create an instance of the 2nd class, place it into a JDialog (not a JFrame), and then display it.
For example,
import java.awt.Component;
import java.awt.Dialog.ModalityType;
import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.*;
public class TwoWindowEg {
public TwoWindowEg() {
// TODO Auto-generated constructor stub
}
private static void createAndShowGui() {
GuiPanel1 mainPanel = new GuiPanel1();
JFrame frame = new JFrame("Main GUI");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
class GuiPanel1 extends JPanel {
private static final int PREF_W = 800;
private static final int PREF_H = 650;
private GuiPanel2 guiPanel2 = new GuiPanel2(); // our second class!
private JDialog dialog = null; // our JDialog
public GuiPanel1() {
setBorder(BorderFactory.createTitledBorder("GUI Panel 1"));
add(new JButton(new LaunchNewWindowAction("Launch New Window")));
add(new JButton(new DisposeAction("Exit", KeyEvent.VK_X)));
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
private class LaunchNewWindowAction extends AbstractAction {
public LaunchNewWindowAction(String name) {
super(name);
}
#Override
public void actionPerformed(ActionEvent e) {
if (dialog == null) {
// get the Window that holds this JPanel
Window win = SwingUtilities.getWindowAncestor(GuiPanel1.this);
dialog = new JDialog(win, "Second Window", ModalityType.APPLICATION_MODAL);
dialog.add(guiPanel2);
dialog.pack();
}
dialog.setVisible(true);
}
}
}
class GuiPanel2 extends JPanel {
public GuiPanel2() {
setBorder(BorderFactory.createTitledBorder("GUI Panel 1"));
add(new JLabel("The second JPanel/Class"));
add(new JButton(new DisposeAction("Exit", KeyEvent.VK_X)));
}
}
class DisposeAction extends AbstractAction {
public DisposeAction(String name, int mnemonic) {
super(name);
putValue(MNEMONIC_KEY, mnemonic);
}
#Override
public void actionPerformed(ActionEvent e) {
Component comp = (Component) e.getSource();
Window win = SwingUtilities.getWindowAncestor(comp);
win.dispose();
}
}
Alternatively, you could swap JPanel "views" using a CardLayout, but either way, you will want to avoid showing two JFrames. Please have a look at The Use of Multiple JFrames, Good/Bad Practice?.

Background color won't change in panel

The following code produces a window with buttons, but an error message pops up when I run i and actally press the button. According to the Spring tooltip:
Cannot make a static reference to the non-static method setBackground(Color) from the type JComponent
This program is literally entered from my Java textbook line for line, as far as I can tell. It's an older book, so there might be incompatibility, but it doesn't seem likely.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ButtonTest
{
public static void main(String[] args)
{
final ButtonFrame frame = new ButtonFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.show();
}
}
class ButtonFrame extends JFrame
{
public ButtonFrame()
{
setTitle("Button Test");
setSize(Default_width, Default_height);
//panel
ButtonPanel panel = new ButtonPanel();
Container contentPane=getContentPane();
contentPane.add(panel);
}
public static final int Default_width = 300;
public static final int Default_height = 200;
}
class ButtonPanel extends JPanel
{
public ButtonPanel()
{
JButton yellowButton = new JButton("Yellow");
JButton blueButton = new JButton("Blue");
JButton redButton = new JButton("Red");
add(yellowButton);
add(blueButton);
add(redButton);
ColorAction yellowAction= new ColorAction(Color.YELLOW);
ColorAction redAction = new ColorAction(Color.RED);
ColorAction blueAction = new ColorAction(Color.BLUE);
yellowButton.addActionListener(yellowAction);
blueButton.addActionListener(blueAction);
redButton.addActionListener(redAction);
}
}
class ColorAction implements ActionListener
{
public ColorAction(Color c)
{
backgroundColor=c;
}
public void actionPerformed(ActionEvent event)
{
ButtonPanel.setBackground(backgroundColor);
}
private Color backgroundColor;
}
One approach is to nest ColorAction as an inner class in ButtonPanel, where it has implicit access to the enclosing panel.
Addendum: As noted in comments by #Andrew Thompson and #nachokk, the implicit accessibility can be made explicit by qualifying this using the enclosing class name. See JLS ยง15.8.4. Qualified this for details. In this example, these two invocations are equivalent:
setBackground(backgroundColor);
ButtonPanel.this.setBackground(backgroundColor);
As an more general alternative, consider encapsulating the target panel and color in an Action, as outlined here.
class ButtonPanel extends JPanel {
public ButtonPanel() {
JButton yellowButton = new JButton("Yellow");
JButton blueButton = new JButton("Blue");
JButton redButton = new JButton("Red");
add(yellowButton);
add(blueButton);
add(redButton);
ColorAction yellowAction = new ColorAction(Color.YELLOW);
ColorAction redAction = new ColorAction(Color.RED);
ColorAction blueAction = new ColorAction(Color.BLUE);
yellowButton.addActionListener(yellowAction);
blueButton.addActionListener(blueAction);
redButton.addActionListener(redAction);
}
private class ColorAction implements ActionListener {
public ColorAction(Color c) {
backgroundColor = c;
}
#Override
public void actionPerformed(ActionEvent event) {
setBackground(backgroundColor);
}
private Color backgroundColor;
}
}
ButtonPanel.setBackground()
is not a static method so you can't call it as one. You need a concrete instance of ButtonPanel to set the background.
ButtonPanel bp = new ButtonPanel();
bp.setBackground(backgroundColor);
Also change in look and feel can help:
//UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());

Categories

Resources