I have found this implementation of a simple calculator in Java, using the Java Swing framework.
The source code is here:
//Imports are listed in full to show what's being used
//could just import javax.swing.* and java.awt.* etc..
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.BorderLayout;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.JButton;
import java.awt.Container;
public class SimpleCalc implements ActionListener{
JFrame guiFrame;
JPanel buttonPanel;
JTextField numberCalc;
int calcOperation = 0;
int currentCalc;
//Note: Typically the main method will be in a
//separate class. As this is a simple one class
//example it's all in the one class.
public static void main(String[] args) {
//Use the event dispatch thread for Swing components
EventQueue.invokeLater(new Runnable()
{
#Override
public void run()
{
new SimpleCalc();
}
});
}
public SimpleCalc()
{
guiFrame = new JFrame();
//make sure the program exits when the frame closes
guiFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
guiFrame.setTitle("Simple Calculator");
guiFrame.setSize(300,300);
//This will center the JFrame in the middle of the screen
guiFrame.setLocationRelativeTo(null);
numberCalc = new JTextField();
numberCalc.setHorizontalAlignment(JTextField.RIGHT);
numberCalc.setEditable(false);
guiFrame.add(numberCalc, BorderLayout.NORTH);
buttonPanel = new JPanel();
//Make a Grid that has three rows and four columns
buttonPanel.setLayout(new GridLayout(4,3));
guiFrame.add(buttonPanel, BorderLayout.CENTER);
//Add the number buttons
for (int i=1;i<10;i++)
{
addButton(buttonPanel, String.valueOf(i));
}
JButton addButton = new JButton("+");
addButton.setActionCommand("+");
OperatorAction subAction = new OperatorAction(1);
addButton.addActionListener(subAction);
JButton subButton = new JButton("-");
subButton.setActionCommand("-");
OperatorAction addAction = new OperatorAction(2);
subButton.addActionListener(addAction);
JButton equalsButton = new JButton("=");
equalsButton.setActionCommand("=");
equalsButton.addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent event)
{
if (!numberCalc.getText().isEmpty())
{
int number = Integer.parseInt(numberCalc.getText());
if (calcOperation == 1)
{
int calculate = currentCalc + number;
numberCalc.setText(Integer.toString(calculate));
}
else if (calcOperation == 2)
{
int calculate = currentCalc - number;
numberCalc.setText(Integer.toString(calculate));
}
}
}
});
buttonPanel.add(addButton);
buttonPanel.add(subButton);
buttonPanel.add(equalsButton);
guiFrame.setVisible(true);
}
//All the buttons are following the same pattern
//so create them all in one place.
private void addButton(Container parent, String name)
{
JButton but = new JButton(name);
but.setActionCommand(name);
but.addActionListener(this);
parent.add(but);
}
//As all the buttons are doing the same thing it's
//easier to make the class implement the ActionListener
//interface and control the button clicks from one place
#Override
public void actionPerformed(ActionEvent event)
{
//get the Action Command text from the button
String action = event.getActionCommand();
//set the text using the Action Command text
numberCalc.setText(action);
}
private class OperatorAction implements ActionListener
{
private int operator;
public OperatorAction(int operation)
{
operator = operation;
}
public void actionPerformed(ActionEvent event)
{
currentCalc = Integer.parseInt(numberCalc.getText());
calcOperation = operator;
}
}
}
I would like to put the OperatorAction class into to a separate class, which I have tried, but then there is a problem getting text from the JTextField, as that is not available in that new class. So how would one do that?
All you need is another constructor argument (and a matching instance field):
class OperatorAction implements ActionListener
{
private int operator;
private SimpleCalc calc;
public OperatorAction(SimpleCalc c, int operation)
{
calc = c;
operator = operation;
}
public void actionPerformed(ActionEvent event) {
calc.setCurrentCalc(Integer.parseInt(
((JTextField)event.getSource()).getText()));
calcOperation = operator;
}
}
And expose the SimpleCalc#currentCalc property through a setter.
This is not very clean design, though, because you still have tight coupling between OperatorAction and SimpleCalc, but it could be a start for you.
Looks like you need to be using the Model View Controller (MVC) pattern! MVC is integral to many languages and systems, so it's very important to learn! What you'll want to do is break the program into 3 layers of classes, *M*odels, *V*iews, and *C*ontrollers (in this case, the project is likely simple enough to use just one of each.)
Models are going to store your data, these can be a few classes, or a whole database depending on the size of your project.
Views are going to display your data.
Controllers are going to manage the rest.
So what you'd do here is have a Model that merely stores the input to the calculator, a Controller that takes the input, does the actual math, and pushes the output to the View.
Related
The following example creates a JFrame with JButton, JTextField and JLabel.
When the button is pressed it increments the value in the text field and label.
This example also creates a 2nd JFrame that is a copy of the first.
The button, text field and label is copied as well.
The issue at hand is the button on the copied frame still updates the text field and label on the original. The 'why' is fairly obvious and is because the code makes specific reference to the text field and label.
Although this isn't written in the best manner but it is a great example of the scenario in which I am addressing.
The objective is, without a major rewrite, what would be the least invasive way to have the copied button action update the copied test field and label instead of the original?
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;
class ButtonTextFieldLabel extends JFrame
{
JButton bnt1 = new JButton("B1");
JTextField tf1 = new JTextField("1");
JLabel lbl1 = new JLabel("100");
public ButtonTextFieldLabel()
{
super("Main Frame");
setLayout(null);
bnt1.setBounds(50,100,120,40);
tf1.setBounds(300,100, 80,40);
lbl1.setBounds(200,100,80,40);
bnt1.addActionListener(new ListenerHolder(this));
add(bnt1);
add(tf1);
add(lbl1);
setSize(500,500);
makeCopy(this);
setVisible(true);
}
private void makeCopy(ButtonTextFieldLabel originalObj)
{
JFrame copyFrame = new JFrame();
copyFrame.setTitle("Copy of " + originalObj.getTitle());
copyFrame.setSize(originalObj.getSize());
copyFrame.setLocation(originalObj.getX()+100, originalObj.getY()+100);
copyFrame.setLayout(null);
JButton copyBnt1 = new JButton();
copyBnt1.setBounds(originalObj.bnt1.getBounds());
copyBnt1.setLabel(originalObj.bnt1.getLabel());
copyFrame.add(copyBnt1);
for (ActionListener al : originalObj.bnt1.getActionListeners())
{
copyBnt1.addActionListener(al);
}
JTextField copyTf1 = new JTextField();
copyTf1.setBounds(originalObj.tf1.getBounds());
copyTf1.setText(originalObj.tf1.getText());
JLabel copyLbl1 = new JLabel();
copyLbl1.setBounds(originalObj.lbl1.getBounds());
copyLbl1.setText(originalObj.lbl1.getText());
copyFrame.add(copyBnt1);
copyFrame.add(copyTf1);
copyFrame.add(copyLbl1);
copyFrame.setVisible(true);
}
public void runThis()
{
tf1.setText( Integer.toString(Integer.parseInt(tf1.getText())+1) );
lbl1.setText( Integer.toString(Integer.parseInt(lbl1.getText())+1) );
}
}
class ListenerHolder implements ActionListener
{
ButtonTextFieldLabel ph;
public ListenerHolder(ButtonTextFieldLabel ph)
{
this.ph = ph;
}
#Override
public void actionPerformed(ActionEvent arg0)
{
ph.runThis();
}
}
public class TestBTL
{
public static void main(String[] args){
new ButtonTextFieldLabel();
}
}
You already know the reason for the problem -- you're copying the original ActionListener, complete with its reference to the original GUI components. The overall solution is not to copy the action listener but rather to create your GUI's to hold and maintain their own unique state. One solution is rather than try to copy components via kludge, to create a self-contained GUI object that holds and updates its own state. You can create multiple GUI's using a factory method if desired.
For example:
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import javax.swing.*;
public class TestBtl2 {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
createAndDisplayFrame("Frame 1").setVisible(true);
createAndDisplayFrame("Frame 2").setVisible(true);
});
}
// Factory method
private static JFrame createAndDisplayFrame(String text) {
BtlPanel btlPanel = new BtlPanel();
JFrame frame = new JFrame(text);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(btlPanel);
frame.pack();
frame.setLocationByPlatform(true);
return frame;
}
}
class BtlPanel extends JPanel {
private int value = 0;
private JButton button1 = new JButton(new ButtonAction("Button 1"));
private JLabel label1 = new JLabel("00");
private JTextField textField1 = new JTextField("00");
public BtlPanel() {
textField1.setFocusable(false);
add(button1);
add(Box.createHorizontalStrut(20));
add(label1);
add(Box.createHorizontalStrut(20));
add(textField1);
setPreferredSize(new Dimension(300, 100));
}
public void incrementValue() {
value++;
String text = String.format("%02d", value);
label1.setText(text);
textField1.setText(text);
}
private class ButtonAction extends AbstractAction {
public ButtonAction(String name) {
super(name);
}
#Override
public void actionPerformed(ActionEvent e) {
incrementValue();
}
}
}
Side Recommendations:
While null layouts and setBounds() might seem to Swing newbies like the easiest and best way to create complex GUI's, the more Swing GUI'S you create the more serious difficulties you will run into when using them. They won't resize your components when the GUI resizes, they are a royal witch to enhance or maintain, they fail completely when placed in scrollpanes, they look gawd-awful when viewed on all platforms or screen resolutions that are different from the original one.
Check out: The Use of Multiple JFrames, Good/Bad Practice?
Using a JTabbedPane, I'd like to capture the moment just immediately before the change in the selection of a new tab is effective and perform an action. It'd be something analogous to a focus-lost event for a swing component. The aim is to save the text of a few JTextFields into an external file when the tab is changed, so everytime the user clicks a different tab, the values of the current tab are written into the external file.
I've been using the ChangeListener to track the change of tabs, but haven't found a way to do what I need. Any ideas in how to achieve it in the next simple example?
import java.awt.BorderLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTabbedPane;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class TabSample {
static void add(JTabbedPane tabbedPane, String label) {
JButton button = new JButton(label);
tabbedPane.addTab(label, button);
}
public static void main(String args[]) {
JFrame frame = new JFrame("TabSample");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JTabbedPane tabbedPane = new JTabbedPane();
String titles[] = { "Geometry", "Materials", "Analysis"};
for (int i = 0, n = titles.length; i < n; i++) {
add(tabbedPane, titles[i]);
}
ChangeListener changeListener = new ChangeListener() {
public void stateChanged(ChangeEvent changeEvent) {
JTabbedPane sourceTabbedPane = (JTabbedPane) changeEvent.getSource();
int index = sourceTabbedPane.getSelectedIndex();
System.out.println("Tab changed to: " + sourceTabbedPane.getTitleAt(index));
}
};
tabbedPane.addChangeListener(changeListener);
frame.add(tabbedPane, BorderLayout.CENTER);
frame.setSize(400, 150);
frame.setVisible(true);
}
}
One possible way, and I'm not 100% sure that this is acceptable to do or not, but is to create your own model for the JTabbedPane, a model which implements SingleSelectionModel (check the SingleSelectionModel API), that overrides the setSelectedIndex(int index) method, the method that I'm pretty sure Swing uses when it wants to tell the JTabbedPane to change tabs. If you create a class that extends from the DefaultSingleSelectionModel, a concrete class that implements the above interface and override this method, you could make method calls before the super's method is called, and thus make GUI calls before the tab changes. For example, your setSelectedIndex method could look like this:
#Override
public void setSelectedIndex(int index) {
if (activated) {
String text = String.format("Before change, old index: %d; new index: %d", super.getSelectedIndex(), index);
JOptionPane.showMessageDialog(gui, text);
}
super.setSelectedIndex(index);
}
And using your code above could be implemented like:
import java.awt.BorderLayout;
import java.awt.Component;
import javax.swing.DefaultSingleSelectionModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTabbedPane;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class TabSample {
static void add(JTabbedPane tabbedPane, String label) {
JButton button = new JButton(label);
tabbedPane.addTab(label, button);
}
public static void main(String args[]) {
JFrame frame = new JFrame("TabSample");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
MySingleSelectionModel selectionModel = new MySingleSelectionModel(frame);
JTabbedPane tabbedPane = new JTabbedPane();
tabbedPane.setModel(selectionModel);
String titles[] = { "Geometry", "Materials", "Analysis"};
for (int i = 0, n = titles.length; i < n; i++) {
add(tabbedPane, titles[i]);
}
ChangeListener changeListener = new ChangeListener() {
public void stateChanged(ChangeEvent changeEvent) {
JTabbedPane sourceTabbedPane = (JTabbedPane) changeEvent.getSource();
int index = sourceTabbedPane.getSelectedIndex();
System.out.println("Tab changed to: " + sourceTabbedPane.getTitleAt(index));
}
};
tabbedPane.addChangeListener(changeListener);
frame.add(tabbedPane, BorderLayout.CENTER);
frame.setSize(400, 150);
frame.setVisible(true);
selectionModel.setActivated(true);
}
private static class MySingleSelectionModel extends DefaultSingleSelectionModel {
private Component gui;
private boolean activated = false;
public MySingleSelectionModel(Component gui) {
this.gui = gui;
}
public void setActivated(boolean activated) {
this.activated = activated;
}
#Override
public void setSelectedIndex(int index) {
if (activated) {
String text = String.format("Before change, old index: %d; new index: %d",
super.getSelectedIndex(), index);
JOptionPane.showMessageDialog(gui, text);
}
super.setSelectedIndex(index);
}
}
}
Note that I use a boolean field, activated to activate the behavior change so that it doesn't fire on GUI creation. I call setActivated(true) on the model after displaying the GUI.
Regarding your edit:
The aim is to save the text of a few JTextFields into an external file when the tab is changed, so everytime the user clicks a different tab, the values of the current tab are written into the external file.
I should have known, it was an XY Problem after all, where you ask how to solve a specific code problem when the best solution is to use a completely different approach. In the future, please give us all pertinent information with the original question so we can avoid wasting time with unnecessary solutions.
There is in fact no need here to do anything before the tab changes since it is perfectly fine to get the data when the tabs change. Your solution is to use a ChangeListener, and there's no need to go through the gymnastics of what I posted above.
I have created part of a Tic-Tac-Toe game, and I am trying to make sure the "Player VS Player" button becomes disabled once a game is started. I am new to Java swing and all of the graphics, so please, any help is appreciated. I have used .removeActionListener, but it seems to not do anything (or anything I notice). My code probably looks very bad to some of you, but as I said, I am new to this. Some of the imports may not be needed now, but I plan on using them later.
Thanks in advance!
import java.util.Scanner;
import java.lang.Object;
import java.awt.Component;
import java.awt.Container;
import java.awt.Window;
import java.awt.Frame;
import javax.swing.JFrame;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JComponent;
import javax.swing.text.JTextComponent;
import javax.swing.JTextField;
import java.lang.Thread;
import java.util.EventObject;
import java.awt.AWTEvent;
import java.awt.event.ComponentEvent;
import java.awt.event.WindowEvent;
import java.awt.Font;
import javax.swing.*;
class ticTacToe implements ActionListener
{
public static JFrame menuFrame = new JFrame("Tic-Tac-Toe");
public static JPanel menu = new JPanel();
public static JButton instruct = new JButton("Instructions"), pvp = new JButton("Player VS. Player"), pvc = new JButton("Player VS. Computer");
public static String pOne = "bla", pTwo = "bla";
public static boolean namesEntered = false;
public static JFrame pvpFrame = new JFrame (pOne+" "+"VS."+" "+pTwo);
public static JPanel board = new JPanel (), turns = new JPanel();
public static JLabel turn1 = new JLabel (pOne+" has taken 0 turn(s)."), turn2 = new JLabel (pTwo+" has taken 0 turn(s).");
public static JButton btn1 = new JButton (), btn2 = new JButton (), btn3 = new JButton (), btn4 = new JButton (), btn5 = new JButton (), btn6 = new JButton (), btn7 = new JButton (), btn8 = new JButton (), btn9 = new JButton ();
public static int choice = 3;
public static Font f = new Font("Arial", Font.PLAIN, 40);
public static void main (String[]args)
{
instruct.addActionListener(new Instructions());
pvp.addActionListener(new playerVSPlayer());
pvc.addActionListener(new Action());
menu();
}
public static void menu ()//the main menu of the game
{
menu.setLayout(new FlowLayout());//arranges the layout of the buttons on the panel
menu.add(instruct);//adds the instruction button
menu.add(pvp);//adds the player vs player button
menu.add(pvc);//adds the player vs computer button
menuFrame.add(menu);//creates the panel
menuFrame.setSize(450, 78);
menuFrame.setLocationRelativeTo(null);//sets the location to the centre of the screen
menuFrame.setVisible(true);//makes the menu visible
menuFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//extis program when window is closed
}
public void actionPerformed (ActionEvent e)//detection of the clicked button
{
if (e.getSource().equals(instruct))
{
instructions();
}
else if (e.getSource().equals(pvp))
{
if (e.getSource().equals(btn1))
{
btn1.setFont(f);
btn1.setText("X");
btn1.removeActionListener(new playerVSPlayer());
}
else
{
players();
if (!pOne.equals("0")&&!pTwo.equals("0"))
{
firstTurn();
}
if (namesEntered==true&&choice==1)
{
gameBoard();
btn1.addActionListener(new playerVSPlayer());
}
pvp.removeActionListener(new playerVSPlayer());
}
}
}
public static void instructions ()
{
JFrame frame = new JFrame ("Instructions");
frame.setVisible(true);
frame.setSize(300,145);
JLabel label = new JLabel ("The goal of this game is to be the first player that ");
JLabel label2 = new JLabel ("gets 3 X's or O's in a row diagonally, vertically, or");
JLabel label3 = new JLabel ("horizontally. It is possible to tie, by having all");
JLabel label4 = new JLabel ("spaces played with no spots left to win. Click a");
JLabel label5 = new JLabel ("space to enter your X or O.");
JPanel panel = new JPanel();
panel.setLayout(new FlowLayout());
frame.add(panel);
panel.add(label);
panel.add(label2);
panel.add(label3);
panel.add(label4);
panel.add(label5);
}
public static void players ()
{
Scanner in = new Scanner (System.in);
System.out.println("Player One, please enter a word no longer than 4 letters, representing your username for this game.");
pOne = in.nextLine();
if (pOne.equals("0"))
{
System.out.println("You have cancelled the match. Please choose an option in the main menu, or close the main menu.");
}
else {
while (pOne.length()>4||pOne.length()<1)
{
System.out.println("Player One, your username MUST be between 1 and 4 letters long.");
pOne = in.nextLine();
}
}
if (!pOne.equals("0"))
{
System.out.println("Player Two, please enter a word no longer than 4 letters, representing your username for this game.");
pTwo = in.nextLine();
if (pTwo.equals("0"))
{
System.out.println("You have cancelled the match. Please choose an option in the main menu, or close the main menu.");
}
else {
while (pTwo.length()>4||pTwo.length()<1)
{
System.out.println("Player Two, your username MUST be between 1 and 4 letters long.");
pTwo = in.nextLine();
}
}
}
if (!pOne.equals("0")&&!pTwo.equals("0"))
{
namesEntered= true;
}
}
public static void gameBoard ()
{
pvpFrame = new JFrame (pOne+" "+"VS."+" "+pTwo);
pvpFrame.setLayout(new GridLayout());
pvpFrame.setVisible(true);
pvpFrame.setSize(600,400);
pvpFrame.setLocationRelativeTo(null);
board.setLayout(new GridLayout(3,3));
turns.setLayout(new FlowLayout());
pvpFrame.add(board);
pvpFrame.add(turns);
turn1 = new JLabel (pOne+" has taken 0 turns.");
turn2 = new JLabel (pTwo+" has taken 0 turns.");
turns.add(turn1);
turns.add(turn2);
board.add(btn1);
board.add(btn2);
board.add(btn3);
board.add(btn4);
board.add(btn5);
board.add(btn6);
board.add(btn7);
board.add(btn8);
board.add(btn9);
}
public static void firstTurn ()
{
Scanner in = new Scanner (System.in);
System.out.println(pOne+" will be X and "+pTwo+" will be O. Enter 1 if you would like to continue. Enter 0 if you would like to cancel this match and return to the main menu.");
choice = in.nextInt();
while (choice!=0&&choice!=1)
{
System.out.println("Your choice did not match 1 or 0. Please enter your choice again.");
choice = in.nextInt();
}
if (choice==0)
{
System.out.println("You have cancelled the match. Please choose an option in the main menu, or close the main menu.");
}
}
}
EDIT 1:
public void actionPerformed (ActionEvent e)//detection of the clicked button
{
if (e.getSource().equals(instruct))
{
instructions(); // This just runs the instruction panel
}
else if (e.getSource().equals(pvp))
{
if (e.getSource().equals(btn1))
{
btn1.setFont(f);
btn1.setText("X");
btn1.removeActionListener(new playerVSPlayer());
}
else
{
players();
if (!pOne.equals("0")&&!pTwo.equals("0"))
{
firstTurn();
}
if (namesEntered==true&&choice==1)
{
gameBoard();
pvp.setEnabled(false); // my goal here is to make the button no longer usable once the
//game starts, but I also need to make sure later that i can use the button once this game is closed
btn1.addActionListener(new playerVSPlayer());
}
}
}
}
The problem is, that does not disable the button
I have several issues with your code, but as far as your question is concerned, there are two main issues:
First as Christian points out, you are not adding and removing the same instance. I'm not sure if this can be alleviated by giving the class an equals and hashCode override, and I must test this.
But even more important, you seem to expect the class's actionPerformed to act, when you don't appear to add it to any buttons. No where do I see addActionListener(this). Having said that, I try to avoid making my GUI "view" classes implement listener interfaces.
A better solution perhaps would be swapping JButton AbstractActions via the setAction(...) method. Think of AbstractActions as if they were ActionListeners on steroids, since they can do everything that an ActionListener can do and then some. Do this and you won't have to worry about removing prior listeners since a button can have one and only one Action.
Other issues:
Your program grossly over-uses the static modifier. While static fields and methods probably won't matter in small programs, they're not a good habit to get into since they don't "scale" well. In other words, if you create large and complex programs (and if you stick with Java, you will), over-use of static fields and methods increases the potential complexity and limits the inheritance potential of your classes of your programs making them very difficult to debug or enhance. It's a good habit to avoid over-use of static modifier except in the certain situations where it is definitely called for.
You're mixing a Swing GUI with a console program which is a dangerous thing to do. Much better would be to get all user input via the GUI, and leave the console output for debugging purposes (if that) only.
Edit
Yep, if you override equals and hashCode so that all Listeners of one type are the same, then you can remove them as you're trying to do.
For example check this test code:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class TestActionListeners extends JPanel {
private JButton button = new JButton("Button");
public TestActionListeners() {
add(button);
button.addActionListener(new Listener1());
}
private static void createAndShowGui() {
TestActionListeners mainPanel = new TestActionListeners();
JFrame frame = new JFrame("Test");
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 Listener1 implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("in listener 1");
AbstractButton button = (AbstractButton) e.getSource();
button.removeActionListener(new Listener1());
button.addActionListener(new Listener2());
}
#Override
public boolean equals(Object obj) {
return obj instanceof Listener1;
}
#Override
public int hashCode() {
return Listener1.class.hashCode();
}
}
class Listener2 implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("in listener 2");
AbstractButton button = (AbstractButton) e.getSource();
button.removeActionListener(new Listener2());
button.addActionListener(new Listener1());
}
#Override
public boolean equals(Object obj) {
return obj instanceof Listener2;
}
#Override
public int hashCode() {
return Listener2.class.hashCode();
}
}
Having said this, I don't recommend that you do this but rather swap AbstractActions via setAction(...).
Edit 2
I'm an idiot for not carefully reading your question. If this is your goal:
and I am trying to make sure the "Player VS Player" button becomes disabled once a game is started:
Then all you need to do is set the button or its Action disabled. i.e.,
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class DisableAButton extends JPanel {
private JButton disableMeButton1 = new JButton("Disable Me 1");
private JButton disableMeButton2 = new JButton(new DisableMe2Action("Disable Me 2"));
public DisableAButton() {
disableMeButton1.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
AbstractButton buttonSource = (AbstractButton) e.getSource();
JOptionPane.showMessageDialog(buttonSource, "DisableMe1 ActionListener!");
buttonSource.setEnabled(false);
}
});
add(disableMeButton1);
add(disableMeButton2);
}
private static void createAndShowGui() {
DisableAButton mainPanel = new DisableAButton();
JFrame frame = new JFrame("DisableAButton");
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 DisableMe2Action extends AbstractAction {
public DisableMe2Action(String name) {
super(name);
}
#Override
public void actionPerformed(ActionEvent e) {
JComponent source = (JComponent) e.getSource();
JOptionPane.showMessageDialog(source, "DisableMe2 Action!");
setEnabled(false);
}
}
you should use the same instance to call add and remove.
therefor store the listeners in a field
I've been making a battleship program that I've been trying to get working with a GUI, but it doesn't want to work. The way in theory it should work is that the GUI starts, it outputs a question to a box(which works), and then the computer waits and executes nothing until you press the button after you've answered your answer to the question. The problem is, my method that waits until you've clicked the button to fetch the data in the text field doesn't do anything. I've written a similar piece of code which demonstrates my problem below.
Test.java (main class)
package taest;
import javax.swing.*;
public class Test {
public static void main(String args[]){
SwingUtilities.invokeLater(new Runnable() {
public void run() {
#SuppressWarnings("unused")
JFrame frame = new Frame();
}
});
Frame.display.setText(getButtonClick());
}
public static String getButtonClick(){
while(true){
if (Frame.hasClicked){
break;
}
}
return Frame.text.getText();
}
}
Frame.java (Frame class)
package taest;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Frame extends JFrame{
JFrame panel = new JFrame("Something");
public static JTextArea text = new JTextArea();
JButton button = new JButton("Click");
public static JTextField display = new JTextField("NOthing");
static boolean hasClicked = false;
static String storage = "";
public Frame(){
setLayout(new BorderLayout());
setSize(400,400);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
button.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
hasClicked = true;
storage = text.getText();
}
});
Container c = getContentPane();
c.add(display, BorderLayout.CENTER);
c.add(text, BorderLayout.PAGE_START);
c.add(button, BorderLayout.PAGE_END);
setVisible(true);
}
}
Static is not your friend and it's use should be greatly limited. It should NEVER be used to provide "easy" access to class fields for inter class communication
You need to turn the concept on it's head and possibly use some kind of Observer Pattern. This is where you have a class which is "observing" changes on your other class. When a change occurs the observed class notifies the observing class of the change. This decouples the responsibility as the observed class shouldn't care beyond notifying interested parties about something that happens
As a really primitive example...
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
public class Test {
public static void main(String args[]) {
new Test();
}
public Test() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
#SuppressWarnings("unused")
JFrame frame = new Frame(new ViewController() {
#Override
public void messageChanged(View view, String msg) {
view.appendLog(msg);
}
});
}
});
}
public interface ViewController {
public void messageChanged(View view, String msg);
}
public interface View {
public void appendLog(String log);
}
public class Frame extends JFrame implements View {
// JFrame panel = new JFrame("Something");
private JTextArea text = new JTextArea(5, 5);
private JButton button = new JButton("Click");
private JTextField display = new JTextField("NOthing");
private String storage = "";
private ViewController viewController;
public Frame(ViewController controller) {
this.viewController = controller;
setLayout(new BorderLayout());
setSize(400, 400);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
storage = text.getText();
viewController.messageChanged(Frame.this, storage);
}
});
System.out.println("display = " + display.hashCode());
System.out.println("text = " + text.hashCode());
Container c = getContentPane();
c.add(display, BorderLayout.CENTER);
c.add(text, BorderLayout.PAGE_START);
c.add(button, BorderLayout.PAGE_END);
setVisible(true);
}
#Override
public void appendLog(String log) {
display.setText(log);
}
}
}
You should also become farmiluar within the concept of Model–view–controller
You are mixing things up,
First things first, the difference between Classes and Objects. A class is a blueprint for an object, so an example of a class is Car. The blueprint of such an object however knows nothing about the state of a particular instance of that class, lets assume that you drive 100 km/u, then you have an instance of Car that stores that it is going at 100 km/u. Blueprints are classes, Objects are instances.
So, public class Car makes an blueprint for cars, and new Car() makes a specific instance of that blueprint in which you can store runtime information.
Now there is a way to tell Java that things belong to the blueprint, static. If a variable is static it is attached to the blueprint. So to keep up with the analogy of cars, a static variable for a car can be its wheelbase, thats something that is defined at compiletime (or in the car analogy at create time).
Back to your problem, you are mixing up classes and objects, what you want to do is have a BattleshipWindow of which instances exist. Of this BattleshipWindow an instance can be created with new, and then its properties can be changed.
Not exactly the answer you want probably, but I hope you now understand the difference between classes and objects, that will help you solve your problem.
I have an array of JButtons which form a keypad interface. After six numbers are entered I want to disable the keypad so that no further numbers can be entered by the user.
I have written the code and the buttons do disable until the mouse hovers above any of them, then the buttons seem to re-enable themselves and run actionEvents added to them.
The full code is available here.
Possible things that I think are wrong.
There is some sort of MouseListener which is ignoring when I set button.setEnabled(false);
I haven't separated attributes from the buildGUI(); correctly, I only did this anyway so that the inner class could access them.
Possibly something to do with the gridLayout as disabling the buttons seems to work for my services JPanel buttons.
The problem lies in how you instantiated your Frame (CashMachine), not (directly) with its implementation.
You are calling buildGUI twice, one in the object's constructor, and then in the Driver class that instantiates the object. As a result, you are creating (and laying out) two sets of buttons.
When the buttons of the first set were eventually disabled, your mousing activity was revealing the second set of buttons. And a flaw in your ActionListener implementation can cause inputCount to take on values greater than 6, so buttons in the second set were not eventually disabled like those from the first set.
buildGUI should be private; it should be called in the CashMachine constructor, and not by your Driver class.
Conversely, in my opinion, CashMachine.setVisible should be called by the Driver class, and not by the CashMachine constructor.
The code works just fine I guess.
One possible source of confusion in your program is mixing number keys with control keys, Clear and Enter. Consider handling number keys separately with a single listener, as suggested in the NumberButton class shown below. Then you can handle the Clear and Enter buttons as desired. Also, using a List<NumberButton> makes the enable and disable loops easier.
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
public class KeyPadPanel extends JPanel implements ActionListener {
private static final int MAX = 6;
private final List<NumberButton> numbers = new ArrayList<NumberButton>();
private final JTextArea text = new JTextArea(1, MAX);
private final JButton clear = new JButton("Clear");
private final JButton enter = new JButton("Enter");
public KeyPadPanel() {
super(new BorderLayout());
JPanel display = new JPanel();
text.setEditable(false);
display.add(text);
this.add(display, BorderLayout.NORTH);
JPanel pad = new JPanel(new GridLayout(4, 4));
for (int i = 0; i < 10; i++) {
NumberButton n = new NumberButton(i);
numbers.add(n);
if (i > 0) {
pad.add(n);
}
}
pad.add(clear);
pad.add(numbers.get(0));
pad.add(enter);
clear.addActionListener(this);
enter.addActionListener(this);
this.add(pad, BorderLayout.CENTER);
}
#Override
public void actionPerformed(ActionEvent e) {
text.setText("");
enableButtons();
}
private void enableButtons() {
for (NumberButton n : numbers) {
n.setEnabled(true);
}
}
private void disableButtons() {
for (NumberButton n : numbers) {
n.setEnabled(false);
}
}
private class NumberButton extends JButton implements ActionListener {
public NumberButton(int number) {
super(String.valueOf(number));
this.addActionListener(this);
}
#Override
public void actionPerformed(ActionEvent e) {
NumberButton b = (NumberButton) e.getSource();
if (text.getText().length() < MAX) {
text.append(b.getText());
}
if (text.getText().length() == MAX) {
disableButtons();
}
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new KeyPadPanel());
f.pack();
f.setVisible(true);
}
});
}
}
Examining the class files was helpful! The problem is in the Driver class:
the buildGUI() method is being called 2 times: once in the constructor of CashMachine and second in the main method after calling the constructor.
public static void main(String args[])
{
CashMachine cashmachine = new CashMachine();
cashmachine.buildGUI();
}
This way you end up with the double number of buttons, that is, a pair of buttons at each position. But only one of each is being disabled.
Just remove the call to buildGUI from main (or from the constructor).
(I would change buildGUI to private as it should not be called from outside the class...)