Question

I am currently working on some Java code that has a lot of ActionListeners defined in it (one for each JButton) and there are around 60 buttons. These are all defined as anonymous inner classes in the JButton.addActionListener method. I have been thinking of ways to refactor this to make the code look neater as this is making it looked very cluttered. I thought about possibly taking the listeners into a separate class that essentially has a load of static methods each returning a listener. This will mean that the code will look something like addActionListener(GetActionListener.addActionListener()). Whilst this will make it neater I feel that it is not really an elegant solution. I thought also a static final map holding KV pairs with listener name to the listener itself. Again however this still does not seem like a very elegant solution. I was wondering if anyone had any ideas? I should also say that all the actionListeners are pretty different.

Was it helpful?

Solution

I would suggest not to directly add actions using ActionListener. If you do this way it becomes non-reusable. Instead wrap your actions in javax.swing.Action class. So that you can reuse the action wherever you want. For e.g now you can use the same action for say a menu shortcut of a Copy action and the copy button in toolbar. Basically the idea is not to directly couple runnable actions with GUI elements.

Now coming to your question. I would make a repository of actions in a class called, say, ActionRepsoitory with a public method public Action getAction(String). Each of your action would be identified by a String constant which you use to retrieve the action from the repository. Typically that string would be the actionCommand for the element. How you manage the actions in the ActionRepository, via a HasMap or whatever, is completely dependent on you.

This is how its doen in most proffesional code, AFAIK.

OTHER TIPS

Not a duplication of this question (which was not a duplicate of the question it answered... wow) but the answer should apply.

If your inner classes are doing more than just calling a method inside of the outer class then you are doing it "wrong" (to my definition of "right"). In the posted code the calls to increment() and decrement() are the "right" way to do it. Refactoring the code have the listeners forward the method call to the outer class is a good place to start to make the code more manageable.

That being said... 60 buttons on a UI?! Really! Ouch! Are they all on one screen or is it done with tabs or something? (if it is tabs or something I have more to offer in the answer).

You could make a special Subclass of ActionListener that uses reflection to call a given method name, then you can implement all your 60 actions as normal methods.

package com.example;

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

public class MethodAsActionListener implements ActionListener {

    private Object receiver;
    private Method method;

    public MethodAsActionListener(Object receiver, String name) {
        this.receiver = receiver;
        try {
            this.method = receiver.getClass().getDeclaredMethod(name);
        } catch (SecurityException e) {
           throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void actionPerformed(ActionEvent event) {
        try {
            method.invoke(receiver);
        } catch (IllegalArgumentException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }

}

and then if you a method call to your class

private call(String name) {
    return new MethodAsActionListener(this, name);
}

then you can add your actions as follows

button1.addActionListener(call("methodName1"));
button2.addActionListener(call("methodName2"));
button3.addActionListener(call("methodName3"));
button4.addActionListener(call("methodName4"));
button5.addActionListener(call("methodName5"));

if one of these methods is missing, the program will fail when the UI is built (since we lookup the method when the action listener is created). Which is not as nice as at compile time, but still better than totally late-bound when the action is triggered.

I would recommend something like what you suggested--create a single listener class that you subscribe to all the events. You PROBABLY want to use a different instance of the class for each event though, telling the instance (in the constructor) in general what to do with this specific event.

The advantage of this is that you can then start factoring the code inside the listeners together into fewer methods because It's usually pretty similar. Sometimes you can get it into a single method.

One trick I've used for a "Pure dispatch" situation for menu creation was to specify the menu, the structure of the menu and the method each menu item links to in data. Needs a little reflection but it works.

In fact--let me look.

Yeah, I kept the classes in a google doc :) The data was specified like this:

final static String[] menus = { "File:*", "Save:save", "Load:load", "(", "Print:-", "Preview:preview", ")", "Quit:quit" };

It just parsed this. File becomes a top level item because of the start, save will call your "Save" method, load will call your "Load" method, Print is a sub-menu (hence the parens), with preview underneath it and print is not bound to anything.

This string can create and bind an entire menu with one call.

Here's my source code if you want to play with it.

The "TestMenu" class at the top is a testing class demonstrating how to use the buildMenus method.

This was done quite a few years ago, I might do it differently now, but it works. I'm not sure I like it actually generating the menu, and I think I'd make the string parser use a single string instead of breaking it down into strings for each item--should be easy to ensure each item is whitespace separated...

A better API might be a bind method like this:

bind(this, new JButton("Save"), "save", this);

where pressing the save button would cause the save method to be called on this (or whatever other object you passed in). You could even make the "save" parameter optional and just use the JButton.getText().toLower() as the method to call if no parameter exists (I guess that's convention before configuration)

I didn't do it this way with the menu because I also wanted to abstract out the menu creation and menu relationships into my data.

Note that coding this way is an awesome way to get your MVC separation in Java--all your controller code can be removed from your view.

package hEvil;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.border.EmptyBorder;

public class JDial extends JDialog {

private static final long serialVersionUID = -26565050431585019L;
private final JPanel contentPanel = new JPanel();

public static void main(String[] args) {
    try {

        JDial dialog = new JDial();
        dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
        dialog.setVisible(true);
        dialog.setTitle("Heavy Evil");
        dialog.setBackground(Color.WHITE);

    } catch (final Exception e) {
        e.printStackTrace();
    }
}

public JDial() {
    setBounds(0, 0, 1300, 800);
    getContentPane().setLayout(new BorderLayout());
    contentPanel.setLayout(new FlowLayout());
    contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
    getContentPane().add(contentPanel, BorderLayout.CENTER);

    JPanel windowPane = new JPanel();
    windowPane.setLayout(new FlowLayout(FlowLayout.RIGHT));
    getContentPane().add(windowPane, BorderLayout.SOUTH);

    {
        JButton cancelButton = new JButton("Exit");
        cancelButton.setActionCommand("Exit");
        windowPane.add(cancelButton);
        cancelButton.setBounds(0, 0, 1200, 700);

    }

    {
        JPanel textPane = new JPanel();
        textPane.setLayout(new FlowLayout(FlowLayout.LEFT));
        getContentPane().add(textPane, BorderLayout.NORTH);
        textPane.setVisible(true);

        {
            JTextArea textArea = new JTextArea("Username", 2, 15);
            textPane.add(textArea);
            textArea.setWrapStyleWord(true);
            textArea.setEditable(true);
            textArea.setFont(Font.getFont(Font.SANS_SERIF));
            textArea.setVisible(true);
            textArea.enableInputMethods(isEnabled());
            textArea.computeVisibleRect(getBounds());
            textArea.setBackground(Color.GRAY);

            JTextArea textArea2 = new JTextArea("Password", 2, 15);
            textPane.add(textArea2);
            textArea2.setWrapStyleWord(true);
            textArea2.setEditable(true);
            textArea2.setFont(Font.getFont(Font.SANS_SERIF));
            textArea2.setVisible(true);
            textArea2.enableInputMethods(isEnabled());
            textArea2.computeVisibleRect(getBounds());
            textArea2.setBackground(Color.GRAY);

        }
        {

            JButton registerButton = new JButton("Register");
            textPane.add(registerButton);

        }
        {
            JButton newButton = new JButton("Submit");
            textPane.add(newButton);
            newButton.setEnabled(true);
            getRootPane().setDefaultButton(newButton);
            newButton.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent evt) {

                    JFrame newFrame = new JFrame("Welcome");
                    newFrame.setVisible(true);
                    newFrame.setBackground(Color.BLACK);
                    newFrame.setBounds(0, 0, 580, 200);

                    JPanel newPanel = new JPanel();
                    newFrame.add(newPanel);
                    dispose();

                    JButton nuButton = new JButton("Mario");
                    newPanel.add(nuButton);

                    JButton nuButton2 = new JButton("Kirby");
                    newPanel.add(nuButton2);

                    JButton nuButton3 = new JButton("Mew Two");
                    newPanel.add(nuButton3);

                    JButton nuButton4 = new JButton("Vegeta");
                    newPanel.add(nuButton4);

                    JButton nuButton5 = new JButton("Tidus");
                    newPanel.add(nuButton5);

                    JButton nuButton6 = new JButton("Link");
                    newPanel.add(nuButton6);

                    JButton nuButton7 = new JButton("Master Chief");
                    newPanel.add(nuButton7);

                    JButton nuButton8 = new JButton("Snake");
                    newPanel.add(nuButton8);

                    JButton nuButton9 = new JButton("Cash");
                    newPanel.add(nuButton9);

                    JButton nuButton10 = new JButton("Lara");
                    newPanel.add(nuButton10);

                    JButton nuButton11 = new JButton("Max");
                    newPanel.add(nuButton11);

                    JButton nuButton12 = new JButton("Spyro");
                    newPanel.add(nuButton12);

                    JButton nuButton13 = new JButton("Sephiroth");
                    newPanel.add(nuButton13);

                    JButton nuButton14 = new JButton("Scorpion");
                    newPanel.add(nuButton14);

                }
            });

        }

    }

}

}
//AND I WANT TO BE ABLE TO IMPLEMENT EACH BUTTON FROM ANOTHER CLASS
//FROM ACTIONEVENT WITH SOMETHINGS SIMILAR TO nuButtonX.actionImplemented...
//CALLING THE SAME ACTIONLISTENER IF I CAN AND THEN CREATING A CLASS FOR
//MODIFICATIONS AT EACH INSTANCE. 
enter code here
package hEvil;

import java.awt.event.ActionEvent;

import javax.swing.JFrame;

public class ActoEve extends ActionEvent {

/**
 * 
 */
private static final long serialVersionUID = -2354901917888497068L;
public ActoEve(Object arg0, int arg1, String arg2) {
    super(ActionEvent.ACTION_PERFORMED, arg1, "flame_001.jpg");
    // TODO Auto-generated constructor stub
}
public void actionImplemented(ActionEvent evt1) {


    JFrame nuFrame = new JFrame();
    nuFrame.setVisible(true);
    nuFrame.setBounds(0, 0, 300, 200);



    // TODO Auto-generated constructor stub
}

}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top