Action Listeners - java

I have rows and columns of jbuttons, and when each button is clicked they should turn red, and when clicked a second time they should return to their original color. So far, the code I have written is as follows:
public class MainPanel extends JPanel
{
private JButton[][] btn1 = new JButton[3][5];
public MainPanel()
{
JPanel MainPanel= new JPanel();
MainPanel.setPreferredSize(new Dimension(700,700));
JPanel p1 = new JPanel();
{
p1.setLayout(new GridLayout(3,5,10,10));
p1.setBackground(Color.WHITE);
for(int i = 0; i < 3; i++)
for(int j = 0; j < 5; j++)
{
btn1[i][j] = new JButton();
btn1[i][j].setBackground(Color.YELLOW);
p1.add(btn1[i][j]);
btn1[i][j].addActionListener
(
new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
btn1[i][j].setBackground(Color.RED);
}
}
);
}
}
}
}
The line btn1[i][j].setBackground(Color.RED); is stating 'local variables referenced from an inner class must be final or effectively final'. Does anyone know how to fix this?

Add listeners in loop:
for(int i = 0; i < 3; i++)
for(int j = 0; j < 5; j++) {
final JButton btn = btn1[i][j];
btn1[i][j].addActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent e) {
btn.setBackground(Color.RED);
}
}
);
}
You added listener only to the first button.

Related

Is there a way to reference the index of the button above of a clicked button? In Java Swing [duplicate]

How to get position(I mean row and column) of the clicked button with gridlayout?
public void init(final Container pane) {
JPanel controls = new JPanel();
int size = (int) Math.sqrt(puzzle.getSize() + 1);
controls.setLayout(new GridLayout(size, size));
for (int i = 0; i < puzzle.getSize(); i++) {
int k = puzzle.getListItem(i);
if (k == puzzle.getEmptyFlag())
controls.add(new JLabel(""));
else {
JButton jb = new JButton(String.valueOf(k));
jb.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
//get row and column
}
});
controls.add(jb);
}
}
pane.add(controls);
}
Never add a MouseListener on a JButton for that purpose. Use an ActionListener instead.
Why not create an array or ArrayList of JButton and then simply iterate through the list to find the proper one?
i.e.,
private JButton[][] buttonGrid = new JButton[ROWS][COLS];
Elsewhere you will need to fill the grid with viable JButton objects and place those JButtons into your GUI.
Then later in program use a nested for loop iterating through the grid comparing the grid button with the getSource() JButton.
i.e. in the JButton's ActionListener
public void actionPerformed(ActionEvent e) {
for (int row = 0; row < ROWS; row++) {
for (int col = 0; col < COLS; col++) {
if buttonGrid[row][col] == e.getSource();
// here you have your row and column
}
}
}
Edit
You ask:
why?
Because it won't work correctly in many situations. ActionListeners have been built to work specifically with JButtons and JMenuItems and have mechanisms that make this function work well l and easily. For example, say you decide to have a JButton that is only enabled once the user has filled two JTextFields, and you use the JButton's setEnabled(boolean enabled) method to do this, disabling the JButton will not stop the MouseListener from working, but it will stop the ActionListener.
Edit 2
For example,
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class ButtonGridEg extends JPanel {
private static final int ROWS = 8;
private static final int COLS = ROWS;
private static final int GAP = 5;
private JButton[][] buttonGrid = new JButton[ROWS][COLS];
public ButtonGridEg() {
setLayout(new GridLayout(ROWS, COLS, GAP, GAP));
ActionListener buttonListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
JButton selectedBtn = (JButton) evt.getSource();
for (int row = 0; row < buttonGrid.length; row++) {
for (int col = 0; col < buttonGrid[row].length; col++) {
if (buttonGrid[row][col] == selectedBtn) {
System.out.printf("Selected row and column: %d %d%n", row, col);
}
}
}
}
};
for (int row = 0; row < buttonGrid.length; row++) {
for (int col = 0; col < buttonGrid[row].length; col++) {
String text = String.format("Button [%d, %d]", row, col);
buttonGrid[row][col] = new JButton(text);
buttonGrid[row][col].addActionListener(buttonListener);
add(buttonGrid[row][col]);
}
}
}
private static void createAndShowGui() {
ButtonGridEg mainPanel = new ButtonGridEg();
JFrame frame = new JFrame("ButtonGridEg");
frame.setDefaultCloseOperation(JFrame.EXIT_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();
}
});
}
}
In this case, you don't even have to search for the indices, because you know them when the button is created:
for (int i = 0; i < puzzle.getSize(); i++) {
int k = puzzle.getListItem(i);
if (k == puzzle.getEmptyFlag())
controls.add(new JLabel(""));
else {
JButton jb = new JButton(String.valueOf(k));
final int rowIndex = i / size;
final int columnIndex = i % size;
// Using an ActionListener, as Hovercraft Full Of Eels already told you:
jb.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("rowIndex "+rowIndex+" columnIndex "+columnIndex);
}
});
controls.add(jb);
}
}

need help accessing variable inside inner class for loop

I am having problem accessing the variable int i inside the inner class
Code:
public class OnColumnSelectPanel {
JFrame jFrame;
JPanel mainJPanel;
//JPanel jPanel1;
//JTextField jTextField;
//JButton jButton;
//JComboBox jComboBox [];
MigLayout layout;
MigLayout frameLayout;
//column names
ColumnNames cn = new ColumnNames();
List<String> listedColumnNames = cn.getColumnNames();
String columnNames[] = listedColumnNames.toArray(new String[0]);
public OnColumnSelectPanel(int n) {
//jPanel1 = new JPanel();
jFrame = new JFrame("Create Structure of Columns");
// mainJPanel = new JPanel();
layout = new MigLayout("flowy", "[center]rel[grow]", "[]10[]");
frameLayout = new MigLayout("flowx", "[center]rel[grow]", "[]10[]");
//mainJPanel.setLayout(new BoxLayout(mainJPanel, BoxLayout.X_AXIS));
//MigLayout mainJPanelLayout = new MigLayout("flowy", "[]rel[grow]", "[]5[]");
// declare & initialize array
JPanel jPanel[] = new JPanel[n];
JComboBox jComboBox[] = new JComboBox[n];
JButton jButton[] = new JButton[n];
final int num = 0;
for (int i = 0; i < n; i++) {
//assign array
jComboBox[i] = new JComboBox(columnNames);
jButton[i] = new JButton("add Sub Heading");
jPanel[i] = new JPanel();
System.out.println("Loop number: " + i);
jButton[i].addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
for (int j = 0; j < n; j++) {
if (j <= n) {
jComboBox[j] = new JComboBox(columnNames);
jPanel[j].add(jComboBox[j]);
jFrame.revalidate();
} else {
JOptionPane.showMessageDialog(null, "You have exceeded your limit");
}
}
}
});
//set layout
jPanel[i].setLayout(layout);
//jPanel[i].setPreferredSize(new Dimension(50, 400));
//mainJPanel.setLayout(mainJPanelLayout);
jFrame.setLayout(frameLayout);
System.out.println(" height " + jPanel[i].getHeight());
jPanel[i].add(jComboBox[i], new CC().newline());
//jPanel.add(jTextField);
jPanel[i].add(jButton[i]);
//mainJPanel.add(jPanel[i], "spany");
jPanel[i].repaint();
jFrame.add(jPanel[i]);
jFrame.pack();
jPanel[i].revalidate();
jFrame.revalidate();
//mainJPanel.revalidate();
//jFrame.revalidate();
}
//mainJPanel.setSize(600, 400);
//jFrame.add(jPanel1);
jFrame.setVisible(true);
jFrame.setSize(600, 400);
jFrame.setAlwaysOnTop(true);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Output Frame
As you can see the output image. The problem here is that when i click on the add subheading button, combobox is added to every jpanel. this is because i can;t pass the value i to the inner class.
It would be interesting to know possible solutions for this.
FYI I am using Mig Layout.
Thanks in advance.
Only effectively final variables can be accessed from anonymous classes.
i is modified by the loop, so it is not effectively final.
A workaround would look like this:
for (int i = 0; i < n; i++) {
...
final int effectivelyFinal = i;
jButton[i].addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
...
// use effectivelyFinal instead of i
});
}
But as other suggested, it would be way better to extract the anonymous class into a real class and pass all required parameters using the constructor. This can look like this:
class MyListener implements ActionListener {
private final int index;
// add more fields for other required parameters
public MyListener(int index) {
this.index = index;
}
#Override
public void actionPerformed(ActionEvent e) {
// use index
}
}
Usage:
jButton[i].addActionListener(new MyListener(i));

Calling an actionlistener from another class or somehow order the multiple actionlisteners that are placed on one button?

For my tic tac toe project, I am have an actionlistener for the buttons in my TTTBoard Class which updates the score when a player has won and an actionlistener in GUI which updates the scoreboard to show the scores.
However when I run the code, the actionlistener for GUI seems to go before the actionlistener for the TTTBoard. So what happens is that I am always one click behind.
Here is what it looks like on console
enter image description here
Here is a part of my TTTBoard Code
public class TTTBoard extends JPanel implements ActionListener
{
private TTTSquare[][] square;
private ScoreBoard gameScore;
private Random generator = new Random();
private int turn = 0;
private int a = 0;
private int p = 0;
public TTTBoard(ScoreBoard sb)
{
super(new GridLayout(3,3));
square = new TTTSquare[3][3];
for(int r = 0; r < 3; r++)
{
for(int c = 0; c < 3; c++)
{
square[r][c] = new TTTSquare();
add(square[r][c]);
square[r][c].addActionListener(this);
}
}
gameScore = sb;
}
public void actionPerformed(ActionEvent e)
{
gameScore = new ScoreBoard();
TTTSquare btn = (TTTSquare)e.getSource();
btn.clicked();
turn++;
if(winningMove(1))
{
a++;
gameScore.setPlayer(a);
}
else
{
if(turn < 5)
{
computersTurn();
if(winningMove(2))
{
p++;
gameScore.setComputer(p);
}
}
else
{
}
}
System.out.println("GameScore From Loop "+gameScore.getPlayer()+" , "+gameScore.getComputer());
System.out.println();
gameScore.update();
}
}
Here is my GUI code
public class GUI extends JFrame
{
private TTTBoard board;
private ScoreBoard score;
private JButton btnGame;
private JButton btnReset;
public GUI()
{
super();
setLayout(new BorderLayout());
JPanel pnlSouth = new JPanel();
score = new ScoreBoard();
pnlSouth.add(score);
board = new TTTBoard(score);
add(board,BorderLayout.CENTER);
for(int r = 0; r < 3; r++)
{
for(int c = 0; c < 3; c++)
{
board.getSquare(r,c).addActionListener
(
new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
System.out.println("GameScore From GUI "+board.getGameScore().getPlayer()+" , "+board.getGameScore().getComputer());
score.setPlayer(board.getGameScore().getPlayer());
score.setComputer(board.getGameScore().getComputer());
}
}
);
}
}
}
}
I could only think of two ways to solve this problem and that is either by somehow ordering the two action events that occur on the button so that the action event from the TTTBoard goes before that in the GUI
Or by calling the actionlistener from TTTBoard in the GUI Class maybe like something at the bottom:
for(int r = 0; r < 3; r++)
{
for(int c = 0; c < 3; c++)
{
board.getSquare(r,c).addActionListener
(
new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
/*
for(int r = 0; r < 3; r++)
{
for(int c = 0; c < 3; c++)
{
board.getSquare(r,c).addActionListener(SOMEHOW CALL ACTIONLISTENER FROM ANOTHER CLASS);
}
}
*/
System.out.println("GameScore From GUI "+board.getGameScore().getPlayer()+" , "+board.getGameScore().getComputer());
score.setPlayer(board.getGameScore().getPlayer());
score.setComputer(board.getGameScore().getComputer());
}
}
);
}
}
I guess my last resort is to bring the huge chunk of code from TTTBoard Class to GUI and see if it works then, but I really do not want to do that. Any help, advise, suggestions is needed and welcomed.
Thank you so much

JButton Array Action Listener work for the last button only

I made a button array and tried to add an ActionListener to each button in a for loop and also listen to all the button using a for loop but the thing is that the only responding button is the last one created. What am I doing wrong?
nums = new JButton[13];
ListenForButton lfb = new ListenForButton();
for (int i = 1; i < 13; i++) {
nums[i].addActionListener(lfb);
}
private class ListenForButton implements ActionListener{
public void actionPerformed(ActionEvent e) {
for(int i=0;i<13;i++){
if( e.getSource() == nums[i]) {
System.out.println("pressed");
}
}
}
}
Method ActionEvent.getSource() returns an instance of type Object, you need to typecast it to JButton for this
if( e.getSource() == nums[i])
to be true, so do this instead of the above statement:
if( ((JButton)e.getSource()) == nums[i])
Also try this:
nums = new JButton[13];
for (int i = 0; i < 13; i++)
{
nums[i] = new JButton();
nums[i].addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae)
{
System.out.println("pressed");
}
});
}
or use ActionCommand, like this:
nums = new JButton[13];
ListenerForButton lfb = new ListenerForButton();
for (int i = 0; i < 13; i++)
{
nums[i] = new JButton();
nums[i].setActionCommand("button"+i);
nums[i].addActionListener(lfb);
}
private class ListenForButton implements ActionListener{
public void actionPerformed(ActionEvent e) {
for(int i=0;i<13;i++){
if( e.getActionCommand().equals("button"+i)) {
System.out.println("pressed" + i); //for getting which button is actually clicked
}
}
I think you should call nums[i].addActionListener(new ListenForButton());
In order to get a new listener for every button

How to remove NullPointerExceptions due to JButton array

I'm terribly unfamiliar with swing, but here I'm trying to make a "clacker" game where each time two dice are rolled, either you click the two (or 1 if they're the same number) buttons representing the two numbers or just one button representing the sum of the two numbers. If a button is clicked, visible is set to false. I have a JButton array to hold the 12 buttons, but every time a reference is made to the array buttons, it throws a NullPointerException. I've tried putting the code making the JButton array within the constructor and actionPerformed, but it still throws the same exception.
Here is the code:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Random;
public class Clacker implements ActionListener
{
JPanel panel;
JFrame frame;
JLabel dieFace1, dieFace2, numTurns;
JButton[] buttons;
JButton rollDie, reset;
String turn="Turns: ";
int turns, numCovered;
public Clacker()
{
frame = new JFrame("clacker");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
panel=new JPanel();
panel.setBorder(BorderFactory.createEmptyBorder(50,30,50,30));
panel.setBackground(Color.gray);
panel.setLayout(new GridLayout(5,10,1,1));
dieFace1=new JLabel(new ImageIcon("die3.jpg"));
dieFace1.setAlignmentX(JLabel.RIGHT_ALIGNMENT);
dieFace1.setBorder(BorderFactory.createEmptyBorder(0,0,10,0));
panel.add(dieFace1);
dieFace2=new JLabel(new ImageIcon("die3.jpg"));
dieFace2.setAlignmentX(JLabel.RIGHT_ALIGNMENT);
dieFace2.setBorder(BorderFactory.createEmptyBorder(0,0,10,0));
panel.add(dieFace2);
rollDie=new JButton("Roll Die");
rollDie.setAlignmentX(JButton.RIGHT_ALIGNMENT);
rollDie.addActionListener(this);
panel.add(rollDie);
numTurns=new JLabel(turn+turns);
numTurns.setAlignmentX(JLabel.LEFT_ALIGNMENT);
numTurns.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
panel.add(numTurns);
setArray();
reset=new JButton("Reset");
reset.setAlignmentX(JButton.LEFT_ALIGNMENT);
reset.addActionListener(this);
panel.add(reset);
frame.setContentPane(panel);
frame.pack();
frame.setVisible(true);
}
public void setArray()
{
JButton[] buttons = new JButton[12];
for (int i=0; i<12; i++)
{
buttons[i]= new JButton(i+1+"");
buttons[i].setActionCommand(i+"");
buttons[i].addActionListener(this);
panel.add(buttons[i]);
buttons[i].setEnabled(false);
System.out.println(buttons[i]);
}
}
public void actionPerformed(ActionEvent event)
{
if (event.getActionCommand().equals("Roll Die"))
{
Random asdf = new Random();
int newRoll= asdf.nextInt(6)+1;
dieFace1.setIcon(new ImageIcon("die"+newRoll+".jpg"));
Random asdf2 = new Random();
int newRoll2= asdf2.nextInt(6)+1;
dieFace2.setIcon(new ImageIcon("die"+newRoll2+".jpg"));
int sum=newRoll+newRoll2;
turns++;
numTurns.setText(turn+turns);
for (int i=0; i<7; i++) ////here
{
if (i==newRoll)
buttons[i].setEnabled(true);
if (i==newRoll2)
buttons[i].setEnabled(true);
}
for (int i=7; i<13; i++)
{
if (sum==i)
buttons[i].setEnabled(true);
}
String eventName = event.getActionCommand();
for (int b=1; b<13; b++)
{
if (eventName.equals(buttons[b].getActionCommand())) ////here
{
if (Integer.parseInt(eventName)>6)
{
buttons[b].setText("");
numCovered++;
buttons[b].setVisible(false);
for (int i=0; i<7; i++)
buttons[i].setEnabled(false);
}
}
}
for (int i=0; i<buttons.length; i++) ////here
buttons[i].setEnabled(false);
if (numCovered==12)
{
rollDie.setEnabled(false);
numTurns.setText("Win! "+turns+" turns");
}
}
else if (event.getActionCommand().equals("Reset"))
{
turns=0;
numTurns.setText(turn+turns);
rollDie.setEnabled(true);
numCovered=0;
for (int i=0; i<buttons.length; i++) ////here
{
buttons[i].setVisible(true);
buttons[i].setEnabled(false);
}
}
}
private static void runGUI()
{ JFrame.setDefaultLookAndFeelDecorated(true); Clacker game = new Clacker();}
public static void main(String[] args)
{ javax.swing.SwingUtilities.invokeLater(new Runnable() {public void run() { runGUI();}});}
}
In setArray you're declaring a local variable instead of referencing the class member variable. Just change
JButton[] buttons = new JButton[12];
to
this.buttons = new JButton[12];

Categories