Sam Mitchell
Sam Mitchell

Reputation: 194

Using removeActionListener but not removing - JAVA

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

Upvotes: 1

Views: 694

Answers (2)

Hovercraft Full Of Eels
Hovercraft Full Of Eels

Reputation: 285405

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);
   }
}

Upvotes: 4

Christian Dietrich
Christian Dietrich

Reputation: 11868

you should use the same instance to call add and remove. therefor store the listeners in a field

Upvotes: 1

Related Questions