Hi, I am trying to add an event handling feature to an old program I wrote.
Here is the relevant code:

public class CalculatorLayout extends JFrame{
    private BorderLayout layout;//layout object. Do I need a flowlayout at all?
    private JPanel numberKeysPanel;//contains the number keys panel
    //private JPanel calculationAreaPanel;//contains the calculation panel
    private GridLayout numberKeysGrid;//contains the number keys
    private JTextField calculationArea;//text field where numbers are displayed
    private JButton numberKey;//symbols
    private String[] labels;//hold the labels



public CalculatorLayout(){
        super( "Calculator" );
        layout = new BorderLayout( 0, 5 );//create FlowLayout. Do I need a flowlayout at all?
        setLayout( layout );    
        calculationArea = new JTextField( "", 30 );//created empty display

        numberKeysPanel = new JPanel();

        String[] labels = { "7", "8", "9", "/", "4", "5", "6", "*", "1", "2", "3", "-", "0", ".", "=", "+" };
        numberKeysGrid = new GridLayout( 4, 4, 5, 5 );//create grid layout

        for( int counter = 0; counter < labels.length; counter++ ){
            numberKey = new JButton( labels[ counter ] );//create button with apppropriate label
            numberKeysPanel.add( numberKey );//add buttons to panel
        }//end of loop
        numberKeysPanel.setLayout( numberKeysGrid );//attach grid layout to jpanel
        //set up GridLayout manager

        //numberKeysPanel.add( numberKeysGrid );//add grid to jpanel
        add( calculationArea, BorderLayout.PAGE_START );//add calc area txt field to jframe
        add( numberKeysPanel, BorderLayout.CENTER );//add jpanel to the jframe      
    }//end of constructor

Now, I was just adding the even handling code, but I was wondering, since the buttons are created in a loop there is no way that I can use .addActionListener to each button unless I do that in a for loop, correct? So something like this I presume:

//FOR EVENT HANDLING
        ButtonHandler handler = new ButtonHandler();
        for(int counter = 0; counter < labels.length; counter++){
            numberKey.addActionListener(handler);
        }

Thanks

Recommended Answers

All 22 Replies

I don't see where you have defined handler, but in general yes .. I just used this in something similiar

for (int i = 0; i<values.length; i++){
            final JToggleButton tg = new JToggleButton(values[i]);
            tg.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e){}

Then just define what you want to happen when action is performed

There's a good trick to use here, which is to create a handler that knows which button was pressed - it looks like this:

class MyListener implements ActionListener {
   int buttonNumber;
   public MyListener(int b) { // constructor takes button number
      buttonNumber = b;
   }
   public void ActionPerformed(....
       System.out.println("Handling event for button " + buttonNumber);
   }

then in your loop, add instances like:

for( int counter = 0; counter < labels.length; counter++ ) {
   numberKey = new JButton( labels[ counter ] );
   numberKeysPanel.add( numberKey );
   numberKey.addActionListener(new MyListener(counter));
}

@JamesCherrill (OT, can be taken as flamewar) with KeyBindings is our life again quite easier

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;

public class Calculator {

    private JTextField display = new JTextField(25);
    private JPanel panel = new JPanel();
    private JFrame frame = new JFrame("Calculator Panel");
    private Action numberAction = new AbstractAction() {
        @Override
        public void actionPerformed(ActionEvent e) {
            display.setCaretPosition(display.getDocument().getLength());
            display.replaceSelection(e.getActionCommand());
        }
    };

    public Calculator() {
        panel.setLayout(new BorderLayout());
        display.setEditable(false);
        display.setHorizontalAlignment(JTextField.RIGHT);
        panel.add(display, BorderLayout.NORTH);
        JPanel buttonPanel = new JPanel();
        buttonPanel.setLayout(new GridLayout(4, 3));
        for (int i = 0; i < 10; i++) {
            String text = String.valueOf(i);
            JButton button = new JButton(text);
            button.addActionListener(numberAction);
            button.setBorder(new LineBorder(Color.BLACK));
            button.setPreferredSize(new Dimension(50, 50));
            buttonPanel.add(button);    
            InputMap inputMap = button.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
            inputMap.put(KeyStroke.getKeyStroke(text), text);
            inputMap.put(KeyStroke.getKeyStroke("NUMPAD" + text), text);
            button.getActionMap().put(text, numberAction);
        }
        panel.add(buttonPanel, BorderLayout.CENTER);
        frame.add(panel);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                new Calculator();
            }
        });
    }
}

I agree. If you want keyboard support then key bindings are the best approach.

:-) relying on..., in most cases are Key and Mouse events implemented in APIs and correctly

  • for future readers and tourist I'm talking only about Swing APIs only

thanks, @mKorbel I don't think I need the key support for this, but thanks.
I have implemented the above but now I get an error

CalculatorLayout.java:35: error: constructor ButtonHandler in class CalculatorLayout.ButtonHandler cannot be applied to given types;
                ButtonHandler handler = new ButtonHandler();
                                        ^
  required: int
  found: no arguments
  reason: actual and formal argument lists differ in length
1 error

These are the amendments I have done:

numberKeysPanel = new JPanel();

        String[] labels = { "7", "8", "9", "/", "4", "5", "6", "*", "1", "2", "3", "-", "0", ".", "=", "+" };
        numberKeysGrid = new GridLayout( 4, 4, 5, 5 );//create grid layout

        ButtonHandler handler = new ButtonHandler();

        for( int counter = 0; counter < labels.length; counter++ ){
            numberKey = new JButton( labels[ counter ] );//create button with apppropriate label
            numberKeysPanel.add( numberKey );//add buttons to panel
            numberKey.addActionListener(handler);//create event handler
        }//end of loop
        numberKeysPanel.setLayout( numberKeysGrid );//attach grid layout to jpanel
        //set up GridLayout manager

so created the button handler and added the class:

}//end of constructor

    //class for event handling
    private class ButtonHandler implements ActionListener{
        int buttonNumber;
        public  ButtonHandler(int b){
            buttonNumber = b;
        }
        public void actionPerformed( ActionEvent event){
            System.out.println("Handling event for button " + buttonNumber);
        }
    }

}//end of CalculatorLayout class

Why is it asking for an int?!

The idea is that you have a class of ButtonHandlers, but you create a new instance for each button. The ButtonHandler has an instance variable for the number that its button represents. So, for example,
new ButtonHandler(9)
creates an instance that knows its button represents the number 9.
Have another look at my sample code - you;ll see that each button has its own instance of ButtonHandler.

The big advantage is that the actionPerformed becomes a lot easier. Instead of messing about trying to see which button was pressed and doing a big switch or nested ifs, you already have an instance var with the answer. Eg for number buttons you just want to build up the whole number entered, ie

public void actionPerformed( ActionEvent event){
   numberEntered = numberEntered*10 + buttonNumber;
}

Personally I would put the number buttons in a little JPanel and all the others in a seprate "functions" JPanel, whiuch would make the rest of the code easier, both for layout hanges and to separate the logic for number buttons from the very diffferent logic of the function buttons.

If you get the feeling I'm going too fast here, just ignore it all for now.

OK sorry, yes I made some changes now and it works, in that it returns the index of the position occupied by the key.

String[] labels = { "7", "8", "9", "/", "4", "5", "6", "*", "1", "2", "3", "-", "0", ".", "=", "+" };
        numberKeysGrid = new GridLayout( 4, 4, 5, 5 );//create grid layout

        //ButtonHandler handler = new ButtonHandler();

        for( int counter = 0; counter < labels.length; counter++ ){
            numberKey = new JButton( labels[ counter ] );//create button with apppropriate label
            numberKeysPanel.add( numberKey );//add buttons to panel
            numberKey.addActionListener(new ButtonHandler(counter));//create event handler
        }//end of loop




private class ButtonHandler implements ActionListener{
        int buttonNumber;
        public ButtonHandler(int b){
            buttonNumber = b;
        }
        public void actionPerformed( ActionEvent event){
            System.out.println("Handling event for button " + buttonNumber);
        }
    }

To return the actual number pressed, I am not sure I got that. I declare a new variable private int numberEntered; and change the actionPerformed:

public void actionPerformed( ActionEvent event){
            numberEntered = numberEntered*10 + buttonNumber;
            System.out.println("Handling event for button " + numberEntered);
        }

But this expression is interpreted as a string numberEntered = numberEntered*10 + buttonNumber; so the digits are added up as a string.

No, it just looks like string concatenation, butthat's being done arithmetically by multiplying the previous value by 10 before adding. So if numberEnterd starts at 0 and you press 1 then 2 then 3 the corresponding values of numberEntered are
0
0x10 + 1 = 1
1x10 + 2 = 12
12x10 + 3 = 123

Which is what calculators usually do when you press those buttons.

The problem you have is that the indexes of the buttons in the array do not correspond directly to the numeric values the buttons represent. You can easily fix that with an array of its that maps the actual numeric values to the indexes.

Just for all you folks out there who are still on an old version of Java, here's yet another reason to update to the current 1.8...

void addButton(String label, ActionListener action) {
    // simple method adds a button with an actionPerformed
    JButton b = new JButton(label);
    b.addActionListener(action);
    numberKeysPanel.add(b);
}

// then use that with lambdas for the actionPerformed...

addButton("7", (e) -> input = input*10 + 7);
addButton("8", (e) -> input = input*10 + 8);
...
addButton("+", (e) -> {result += input; input = 0;});
...
etc

OK, you could further shorten that by automating the numeric buttons a bit further (peronally not convinced for only 10 buttons, but hey...)

void addNumberButton(int value) {
    // simple method adds a button with an actionPerformed
    JButton b = new JButton("" + value);
    b.addActionListener( (e) -> input = input*10 + value);
    numberKeysPanel.add(b);
}

// then just

addNumberButton(7);
addNumberButton(8); etc

thanks for lambda version:-) code is simpler but confusing

I think it's only confusing because the syntax is new and unfamiliar. You soon get used to it and, yes, it certainly simplifies/shortens your code, eg

SwingUtilities.invokeLater(new Runnable() 
{
    public void run() 
    {
        runProgram();
    }
});

becomes

SwingUtilities.invokeLater(() -> runProgram());

or

SwingUtilities.invokeLater(this::runProgram);

I'm afraid that various interpretations/possibilities ->/:: can be misspeled by JVM, more that this.whatever (in Swing), prehistoric Object based on premature arrays versus Lambda with the engagement to modern arrays types, we'll see (newbee is the greatest sorcerer in this/the world)

The problem you have is that the indexes of the buttons in the array do not correspond directly to the numeric values the buttons represent. You can easily fix that with an array of its that maps the actual numeric values to the indexes.

I thought of a possible fix, but I want to check whether it is doable. Basically in the for loop I printed the values of each array element and the counter

for( int counter = 0; counter < labels.length; counter++ ){
    numberKey = new JButton( labels[ counter ] );//create button with apppropriate label
    numberKeysPanel.add( numberKey );//add buttons to panel
    numberKey.addActionListener(new ButtonHandler(counter));//create event handler
    System.out.println("labels[counter] " + labels[counter] + " print the counter " + counter  );
}//end of loop

That's so I get an idea of what is what. Then I am thinking that with a switch statement I could change the arrai index to match the label, meaning, in the loop I can test the button label and if it is = to 7 then change its index to 7 and so on.

then to use get/putClientProperty instead of looping for Fields inside current JVM

e.g pseudocode (ClientProperty can be multiplied, to nest any required information or value)

putClientProperty

buttons[i][j].putClientProperty("column", i);
buttons[i][j].putClientProperty("row", j);
buttons[i][j].addActionListener(new MyActionListener());

and getClientProperty

public class MyActionListener implements ActionListener {

@Override
public void actionPerformed(ActionEvent e) {
    JButton btn = (JButton) e.getSource();
    System.out.println("clicked column " + btn.getClientProperty("column")
            + ", row " + btn.getClientProperty("row"));
}

get/putClientProperty is another good approach to this problem.
Note that it's an alternative to the previous approach (ie removes the need for individual action handlers), so pick one or the other, don't try to combine them!

@Violet_82 see how is passed value to GridButton, can be added as paramater in class/void, plus by using get/putClientProperty is about the unbeatable solution

@JamesCherrill just forgot, I know one, most effective way, by using EventHandler, cant find there any side effects, sure can be discussed if Assembler/Cobol code construction is proper of ways in Java, and isn't two steps back, but most safer for hugest apps with many users levels, accesses and authorizations at runtime (value can came from LDAP, AD, Novell, etc)

e.g. everything there are only shortcuts, shorter as is possible in Java

import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.EventHandler;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;


public class GridButtonPanel extends JPanel {

    private static final int N = 2;
    private final List<GridButton> list = new ArrayList<GridButton>();

    public GridButtonPanel() {
        super(new GridLayout(N, N));
        for (int i = 0; i < N * N; i++) {
            int row = i / N;
            int col = i % N;
            GridButton gb = new GridButton(row, col);
            gb.addActionListener((ActionListener) EventHandler.create(ActionListener.class, this,
                    "actionName" + row + "A" + col));
            list.add(gb);
            this.add(gb);
        }
    }

    public void actionName0A0() {
        System.out.println(" Grid at Row 0, Column 0 ");
    }

    public void actionName0A1() {
        System.out.println(" Grid at Row 0, Column 1 ");
    }

    public void actionName1A0() {
        System.out.println(" Grid at Row 1, Column 0 ");
    }

    public void actionName1A1() {
        System.out.println(" Grid at Row 1, Column 1 ");
    }

    private GridButton getGridButton(int r, int c) {
        int index = r * N + c;
        return list.get(index);
    }

    private class GridButton extends JButton {

        private int row;
        private int col;

        public GridButton(int row, int col) {
            super("Row - " + row + ",  Col - " + col);
            this.row = row;
            this.col = col;
            this.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent e) {
                    int r = GridButton.this.row;
                    int c = GridButton.this.col;
                    GridButton gb = GridButtonPanel.this.getGridButton(r, c);
                    System.out.println("r" + r + ",c" + c
                            + " " + (GridButton.this == gb)
                            + " " + (GridButton.this.equals(gb)));
                }
            });
        }
    }

    private void display() {
        JFrame f = new JFrame("GridButton");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(this);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                new GridButtonPanel().display();
            }
        });
    }
}

OK!

We now have 3 Java 7 solutions to getting the value when a button is clicked, all sensible ideas, but all sharing the same issue that Violet spotted earlier - there's no simple relation ship between the loop index and the numeric value for these buttons, so you can't just add the buttons and their listeners in a simple loop.

Me, I've made the move to Java 8, and I'm going to forget the loop and stick with lambdas as an ideal solution to this one.

@Violet: Are you restricted to use some (old) version of Java for this?

Uhm, well I have version 7, so it would be ideal if I can keep working on that

@JamesCherrill

here's no simple relation ship between the loop index and the numeric value for these buttons, so you can't just add the buttons and their listeners in a simple loop.

sligtly to disagree

  • reason for my version where is used Swing Action, nothing simpler,

  • GridButton do that another way,

  • too lazy to

    a) create a real calculator

    b) add JTextArea/Pane with formula(s) history, btw JTextField can do that too, e.g. to block inputed number by using NavigationFilter after button with formula is pressed, again one codeline moveover (plus to override NavigationFilter)

  • version with JTextArea/Pane or JTextField with NavigationFilter required to usage of ClientPorperty (for desired spaces or remove un_wanted whitespaces)

Yes, you add the actions in the simple loop, but you need to define all the actions one at a time. I think the idea of the original question was to set up n buttons in a loop without having to write n similar lines/blocks as well. I think the nearest we will get may be to use another array that maps the values to the indexes, eg
int[] values = {7,8,9,4,5,6 etc
then use any of our solutions above to store those values with the right button or handler.

IMHO: In the end, the fact that there is a mixture of number and operator buttons makes the idea of processing them all in one simple loop a bit unrealistic.

OK thanks guys for all your help

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.