开发者

How to add action listener that listens to multiple buttons

开发者 https://www.devze.com 2023-03-03 17:57 出处:网络
I\'m trying to figure out what i am doing wrong with action listeners. I\'m following multiple tutorials and yet netbeans and eclipse are giving me errors when im trying to use an action listener.

I'm trying to figure out what i am doing wrong with action listeners. I'm following multiple tutorials and yet netbeans and eclipse are giving me errors when im trying to use an action listener.

Below is a simple program that im trying to get a button working in.

What am i doing wrong?

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;


public class calc extends JFrame implements ActionListener {



    public static void main(String[] args) {

        JFrame calcFrame = new JFrame();

        calcFrame.setSize(100, 100);
        calcFrame.setVisible(true);

        JButton button1 = new JButton("1");
        button1.addActionListener(this);

     开发者_高级运维   calcFrame.add(button1);
    }

    public void actionPerformed(ActionEvent e) {
        if(e.getSource() == button1)
    }  

}

the action listener is never registered because with the if(e.getSource() == button1) it cant see button1, errors saying cannot find symbol.


There is no this pointer in a static method. (I don't believe this code will even compile.)

You shouldn't be doing these things in a static method like main(); set things up in a constructor. I didn't compile or run this to see if it actually works, but give it a try.

public class Calc extends JFrame implements ActionListener {

    private Button button1;

    public Calc()
    {
        super();
        this.setSize(100, 100);
        this.setVisible(true);

        this.button1 = new JButton("1");
        this.button1.addActionListener(this);
        this.add(button1);
    }


    public static void main(String[] args) {

        Calc calc = new Calc();
        calc.setVisible(true);
    }

    public void actionPerformed(ActionEvent e) {
        if(e.getSource() == button1)
    }  

}


I'm amazed that nobody has mentioned using an action command. This is a pretty standard way of associating sources and listeners. Its really useful if;

  • you have multiple event sources that need to do the same thing (eg if you want the use to be able to press the enter key on a text field as an alternative to clicking a button next to it)
  • you don't have a ref to the component generating the event

see;

import java.awt.FlowLayout;
import java.awt.event.ActionEvent;    
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;

public class DontExtendJFrame implements ActionListener {

  private enum Actions {
    HELLO,
    GOODBYE
  }

  public static void main(String[] args) {

    DontExtendJFrame instance = new DontExtendJFrame();

    JFrame frame = new JFrame("Test");
    frame.setLayout(new FlowLayout());
    frame.setSize(200, 100);

    JButton hello = new JButton("Hello");
    hello.setActionCommand(Actions.HELLO.name());
    hello.addActionListener(instance);
    frame.add(hello);

    JButton goodbye = new JButton("Goodbye");
    goodbye.setActionCommand(Actions.GOODBYE.name());
    goodbye.addActionListener(instance);
    frame.add(goodbye);

    frame.setVisible(true);
  }

  @Override
  public void actionPerformed(ActionEvent evt) {
    if (evt.getActionCommand() == Actions.HELLO.name()) {
      JOptionPane.showMessageDialog(null, "Hello");
    } else if (evt.getActionCommand() == Actions.GOODBYE.name()) {
      JOptionPane.showMessageDialog(null, "Goodbye");
    }
  }
}


Here is a modified form of the source based on my comment. Note that GUIs should be constructed & updated on the EDT, though I did not go that far.

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.JFrame;

public class Calc {

    public static void main(String[] args) {

        JFrame calcFrame = new JFrame();

        // usually a good idea.
        calcFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

        final JButton button1 = new JButton("1");
        button1.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                JOptionPane.showMessageDialog(
                    button1, "..is the loneliest number");
            }
        });

        calcFrame.add(button1);

        // don't do this..
        // calcFrame.setSize(100, 100);

        // important!
        calcFrame.pack();

        calcFrame.setVisible(true);
    }
}


The problem is that button1 is a local variable. You could do it by just change the way you add the actionListener.

button.addActionListener(new ActionListener() {  
            public void actionPerformed(ActionEvent e)
            {
                //button is pressed
                System.out.println("You clicked the button");
            }});

Or you make button1 a global variable.


You've been told how to sort your immediate problem, but I think there are more important problems here.

  • stick with conventions. Even for throw-away code. That means initial cases for class names.

  • Don't extend classes you don't need to. JFrame should rarely be extended. In fact, you don't create an instance of your derived class!!!

  • Don't bundle a bunch of things into one class. In particular, you should generally only subtype at most one main class or interface at a time (things like Comparable not included).

  • Always interact, including construct, Swing/AWT GUIs on the AWT Event Dispatch Thread (EDT). It's ugly and verbose, but that's Java for you.

  • Checking a source of an event is a bit of hack. Listeners are small, so you can't even claim the lame performance excuse.

So:

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;


public class Calc {
    public static void main(String[] args) {
        java.awt.EventQueue.invokeLater(new Runnable() { public void run() {
            runEDT();
        }});
    }
    private static void runEDT() {
        assert java.awt.EventQueue.isDispatchThread();

        JFrame frame = new JFrame();

        frame.setSize(100, 100);

        JButton button1 = new JButton("1");
        button1.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) {
                ...
            }
        });

        frame.add(button1);

        frame.setVisible(true);
    }
}

If you need to access any of the variables from the enclosing method within the listener, make them final.


There are good answers here but let me address the more global point of adding action listener that listens to multiple buttons.

There are two popular approaches.

Using a Common Action Listener

You can get the source of the action in your actionPerformed(ActionEvent e) implementation:

JButton button1, button2; //your button

@Override
public void actionPerformed(ActionEvent e) {

    JButton actionSource = (JButton) e.getSource();

    if(actionSource.equals(button1)){
        // YOU BUTTON 1 CODE HERE
    } else if (actionSource.equals(button2)) {
        // YOU BUTTON 2 CODE HERE
    }
}

Using ActionCommand

With this approach you setting the actionCommand field of your button which later will allow you to use switch:

button1.setActionCommand("actionName1");
button2.setActionCommand("actionName2");

And later:

@Override
public void actionPerformed(ActionEvent e) {
    String actionCommand = ((JButton) e.getSource()).getActionCommand();

    switch (actionCommand) {
        case "actionName1": 
            // YOU BUTTON 1 CODE HERE
        break;
        case "actionName2": 
            // YOU BUTTON 2 CODE HERE
        break;
    }
}

Check out to learn more about JFrame Buttons, Listeners and Fields.


The first problem is that button1 is a local variable of the main method, so the actionPerformed method doesn't have access to it.

The second problem is that the ActionListener interface is implemented by the class calc, but no instance of this class is created in the main method.

The usual way to do what you want is to create an instance of calc and make button1 a field of the calc class.


You are declaring button1 in main method so you can not access it in actionPerform. You should make it global in class.

 JButton button1;
 public static void main(String[] args) {

    JFrame calcFrame = new JFrame();

    calcFrame.setSize(100, 100);
    calcFrame.setVisible(true);

    button1 = new JButton("1");
    button1.addActionListener(this);

    calcFrame.add(button1);
}

public void actionPerformed(ActionEvent e) {
    if(e.getSource() == button1)
}


First, exend JFrame properly with a super() and a constructor then add actionlisteners to the frame and add the buttons.

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;


public class Calc extends JFrame implements ActionListener {
    JButton button1 = new JButton("1");
    JButton button2 = new JButton("2");

    public Calc()
    {
         setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
         setSize(100, 100);
         button1.addActionListener(this);
         button2.addActionListener(this);
         calcFrame.add(button1);
         calcFrame.add(button2);
    }
    public void actionPerformed(ActionEvent e)
    {
        Object source = e.getSource();
        if(source == button1)
        {
            \\button1 code here
        } else if(source == button2)
        {
            \\button2 code here
        }
    } 
    public static void main(String[] args)
    {

        JFrame calcFrame = new JFrame();
        calcFrame.setVisible(true);
    }
}


I use "e.getActionCommand().contains(CharSecuence s)", since I´m coming from an MVC context, and the Button is declared in the View class, but the actionPerformed call occurs in the controller.

public View() {
    ....
    buttonPlus = new Button("+");
    buttonMinus = new Button("-");
    ....
}

public void addController(ActionListener controller) {
    buttonPlus.addActionListener(controller);
    buttonMinus.addActionListener(controller);
}

My controller class implements ActionListener, and so, when overriding actionPerformed:

public void actionPerformed(ActionEvent e) {
    if(e.getActionCommand().contains("+")) {
        //do some action on the model
    } else if (e.getActionCommand().contains("-")) {
       //do some other action on the model
    }
}

I hope this other answer is also useful.


Using my approach, you can write the button click event handler in the 'classical way', just like how you did it in VB or MFC ;)

Suppose we have a class for a frame window which contains 2 buttons:

class MainWindow {
    Jbutton searchButton;
    Jbutton filterButton;
}

You can use my 'router' class to route the event back to your MainWindow class:

class MainWindow {
    JButton searchButton;
    Jbutton filterButton;
    ButtonClickRouter buttonRouter = new ButtonClickRouter(this);
    
    void initWindowContent() {
        // create your components here...
        
        // setup button listeners
        searchButton.addActionListener(buttonRouter);
        filterButton.addActionListener(buttonRouter);
    }

    void on_searchButton() {
        // TODO your handler goes here...
    }
    
    void on_filterButton() {
        // TODO your handler goes here...
    }
}

Do you like it? :)

If you like this way and hate the Java's anonymous subclass way, then you are as old as I am. The problem of 'addActionListener(new ActionListener {...})' is that it squeezes all button handlers into one outer method which makes the programme look wired. (in case you have a number of buttons in one window)

Finally, the router class is at below. You can copy it into your programme without the need for any update.

Just one thing to mention: the button fields and the event handler methods must be accessible to this router class! To simply put, if you copy this router class in the same package of your programme, your button fields and methods must be package-accessible. Otherwise, they must be public.

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ButtonClickRouter implements ActionListener {
    private Object target;

    ButtonClickRouter(Object target) {
        this.target = target;
    }

    @Override
    public void actionPerformed(ActionEvent actionEvent) {
        // get source button
        Object sourceButton = actionEvent.getSource();

        // find the corresponding field of the button in the host class
        Field fieldOfSourceButton = null;
        for (Field field : target.getClass().getDeclaredFields()) {
            try {
                if (field.get(target).equals(sourceButton)) {
                    fieldOfSourceButton = field;
                    break;
                }
            } catch (IllegalAccessException e) {
            }
        }

        if (fieldOfSourceButton == null)
            return;

        // make the expected method name for the source button
        // rule: suppose the button field is 'searchButton', then the method
        // is expected to be 'void on_searchButton()'
        String methodName = "on_" + fieldOfSourceButton.getName();

        // find such a method
        Method expectedHanderMethod = null;
        for (Method method : target.getClass().getDeclaredMethods()) {
            if (method.getName().equals(methodName)) {
                expectedHanderMethod = method;
                break;
            }
        }

        if (expectedHanderMethod == null)
            return;

        // fire
        try {
            expectedHanderMethod.invoke(target);
        } catch (IllegalAccessException | InvocationTargetException e) { }
    }
}

I'm a beginner in Java (not in programming), so maybe there are anything inappropriate in the above code. Review it before using it, please.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号