2

I am having a massive issue in the for loop below, I am trying to create action listeners for a series of JButtons, and each JButton has to assign a different value to 'Column' and 'Row', but, for all the buttons 'Column' and 'Row' are only becoming the highest possible value of the for loop, as in if in the for loop m went up to 7, 'Column' would equal 7 for all the buttons, it does not incriment. In other words, I want the first button 'but[0][0]' to make 'Column' equal to 0, but I also want 'but[1][0]' make 'Column' equal to 1, and so on. Any help would be greatly appreciated.

The purpose of the buttons is to change the value of 'Column' and 'Row' which are declared elsewhere in the program, so each button needs to make 'Column' and 'Row' equal something different.

for (m = 0; m < width; m++){
            for (n = 0; n < width; n++){
                but[m][n].addActionListener(new ActionListener()
                { 
                    public void actionPerformed(ActionEvent e){
                        Column = m;
                        Row = n;
                        enablenumbers();
                        disablecolumns();
                        disablerows();
                        choose.setText("Now Choose The Nummber You Want To Put In This Square");
                    }
                }
                );
            }
        }

4 Answers 4

4

I guess m and n are declared as fields in the surrounding class (else the ActionListener could not access them, i.e. there would be a compiler error).

The actionPerformed method is invoked when the action is clicked. At that time, the for loops have completed execution, leaving the maximum column and row numer in the fields m and n. Put differently, all action listeners access the same m and n, but they should all see different values.

That already hints at the solution: each ActionListener needs its own variables m and n. In code:

        final int col = m;
        final int row = n;
        but[m][n].addActionListener(new ActionListener()
            { 
                public void actionPerformed(ActionEvent e){
                    Column = col;
                    Row = row;
                    enablenumbers();
                    disablecolumns();
                    disablerows();
                    choose.setText("Now Choose The Nummber You Want To Put In This Square");
                }
            }
        );

Note that the variables are declared within the inner for loop; they only exist for a single iteration. Because they are final, they are visible to the anonymous inner class (under the hood, the values of these final variables will be copied into fields of the anonymous class (Evidence for this claim is given in the appendix to this answer)

Alternatively, you can take the explicit route and use an inner class:

class MyActionListener implements ActionListener {
    final int col;
    final int row;

    // constructor goes here

    // impl of actionPerformed goes here, using col and row instead of m and n
}

which you would attach using

for (m = 0; m < width; m++){
    for (n = 0; n < width; n++){
        but[m][n].addActionListener(new MyActionListener(m,n));
    }
}

Appendix: Implementation of enclosing variable access by inner classes

For each final local variable declared in an enclosing scope an inner class accesses, the compiler automatically creates an additional field in the inner class to hold its value, and amends the constructor to assign the field from a new constructor parameter. This can be verified by running the following code:

public class Test {
    public static void main(final String[] args) throws Exception {
        System.out.println(new Object() {
            @Override
            public String toString() {
                for (Field f : getClass().getDeclaredFields()) {
                    System.out.println(f.getName());
                }
                System.out.println(getClass().getDeclaredConstructors()[0].toString());
                return "" + args.length;
            }
        });
    }
}

which prints:

val$args
Test$1(java.lang.String[])
0
Sign up to request clarification or add additional context in comments.

2 Comments

"under the hood, the values of these final variables will be copied into fields of the anonymous class". The anonymous class doesn't have any fields for those values to be copied into. Column and Row appear to be members of the enclosing instance and thus all of the ActionListener objects will see the same data - the data which exists when actionPerformed is called and not the data which existed when this individual ActionListener object was created. The solution is to have appropriate fields inside the anonymous class as noted in your alternative solution
@barrowc: Oh, but it does have the fields, see my edit for proof.
2

I think that you need to create your own class which implements ActionListener and has member variables to store the column and row values. Supply the column and row values in the constructor to that class.

As written, all of the individual ActionListener objects will be reading the values from the same Column and Row variables and thus all will end up with the last values assigned to those variables

Comments

1

I just took a closer look at your code; how are you able to compile this? You can't access non-final fields (Column, m, n)inside of the anonymous-inner class like that. How about this?

    for (int m = 0; m < width; m++)
    {
        for (int n = 0; n < width; n++)
        {
            final int innerM = m;
            final int innerN = n;
            but[m][n].addActionListener(new ActionListener()
            {
                public void actionPerformed(ActionEvent e)
                {
                    setColumnAndRow(innerM, innerN);
                    enablenumbers();
                    disablecolumns();
                    disablerows();
                    choose.setText("Now Choose The Nummber You Want To Put In This Square");
                }
            });
        }
    }

/*Sets the value of Column and Row when a button is clicked*/
private void setColumnAndRow(int m, int n)
{
    Column = m;
    Row = n; 
}

A couple of notes:

  • On your variable names - in Java it's convention to begin variable names with lower case letters and camel-case words so row instead of Row and column instead of Column, myVariable instead of My_variable or my_variable or variations.
  • On rows vs. columns - Java (and most other languages that derive syntax from C) have 0-based two-dimensional arrays where the first index selects the row and the second selects the column. I'm not sure what your purpose is but you might find that others get confused by your switching of the two indexes (i.e. I'd expect arr[row][col] not the other way around)

2 Comments

Thanks for the reply, but I suppose I should have mentioned that 'Column' and 'Row' are declared elsewhere in the program, basically the purpose of each button is to change their value depending on which button is pressed. But thank you for your reply. (I will add this to the original question)
I see now - I also realized my earlier code wouldn't have compiled and neither would your snippet from the question. See my updated answer for another suggestion I have.
1

It's difficult to say without seeing more code, but it looks like you're setting two variables Column and Row within the loop, then using them outside. If you're just doing something with those variables outside the loop, then by the time you exit they'll always be set to the maximum values (because it's only the last iteration of the loop that will count.)

You probably want to use the m and n values inside the actionPerformed method directly rather than waiting until after the loop has executed. Either that or define column and row variables within your ActionListener, not outside (otherwise you'll just end up always using the same values which will be the ones declared last in the loop.)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.