6

I've been developing java programs for 1½ year. I'm currently working on a summer project, that involves quite a big Graphical User Interface.

My GUI consists of several tabbed panes. Each pane has its own class. Each pane has SEVERAL jButtons.

Now, I've come to a point, where there's so many anonymous inner classes (for ActionListeners) in my tabbed-pane classes, that I am certain there must be a better way; if not for efficiency, then for maintainability - it's becoming quite a mess.

My question is this: Is there a better to organize listeners, when you have a lot of them in each class? I've thought about clustering the listeners in relevant classes - like the following sample code:

public class SomeListeners implements ActionListener{

    @Override
    public void actionPerformed(ActionEvent e){
        String command = e.getActionCommand();
        switch(command){
            case "This button":
                doThis();
                break;
            case "That button":
                doThat();
                break;                          
        }
    }     
}

Or might there be an even better way?

Thanks in advance :)

mKorbel
  • 109,525
  • 20
  • 134
  • 319
Daniel Mac
  • 400
  • 2
  • 14
  • I like @duffymo's recommendation about using dependency injection and am not sure why he deleted it. It doesn't have to be Spring as Guice would work just fine. And it would work well with treeno's suggestion as well as Tim Herold's suggestion (1+ to them both). It's all about loosening coupling and tightening cohesion as best possible. – Hovercraft Full Of Eels Aug 22 '13 at 21:06
  • I have copied duffymo's answer and posted as a community wiki answer as it deserves to be a full fledged answer. – Hovercraft Full Of Eels Aug 22 '13 at 21:12

6 Answers6

5

You could try the following approach:

Use real classes instead of anonymous classes. Every ListenerClass implements only one Use-Case / Functionality. The Classname should describe the UseCase. Then you can organize the classes in one or more packages to cluster them by categories that fit to the Use-Cases that the Listener in the package implements.

That means, you create an hierarchy of abstraction, that organizes the functionality like a tree-structure.

If some day later somebody has to maintain the Listeners he/she can find the Listener by first looking for a package that fits to the UseCase and then for the UseCase itself. Since you will have less packages then Classes it will be easier and faster to find the Listener.


Another way to think about that: If you have so much Events on one Tab, that you get problems organizing them in the code, how do you organize them visually on the Tab? Can you handle that in an ergonomic way for the user? Maybe the solution could be in splitting the functionality on more then one Tab? But since I don't know your UI I cannot say too much about that

treeno
  • 2,510
  • 1
  • 20
  • 36
4

I would recommand to use single class from type javax.swing.AbstractAction

Example:

Action leftAction = new LeftAction(); //LeftAction code is shown later
...
button = new JButton(leftAction)
...
menuItem = new JMenuItem(leftAction);

leftAction = new LeftAction("Go left", anIcon,
         "This is the left button.",
         new Integer(KeyEvent.VK_L));

...

class LeftAction extends AbstractAction {
    public LeftAction(String text, ImageIcon icon,
                      String desc, Integer mnemonic) {
        super(text, icon);
        putValue(SHORT_DESCRIPTION, desc);
        putValue(MNEMONIC_KEY, mnemonic);
    }
    public void actionPerformed(ActionEvent e) {
        displayResult("Action for first button/menu item", e);
    }
}
Khinsu
  • 1,487
  • 11
  • 27
3

Per duffymo: Use dependency injection and pass in the Listeners. This will give you the opportunity to reuse and change them at will.

Per me (hovercraft): It doesn't have to be Spring as Guice would work just fine. And it would work well with treeno's suggestion as well as Tim Herold's suggestion (1+ to them both). It's all about loosening coupling and tightening cohesion as best possible.

Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
  • I think that hooking up complex listener code by "programming" in XML is a very bad idea. But not going to downvote until I hear more responses. But if certain actions get enabled or changed based on the dynamic state of say, the document you are editing, it is hard to imagine a good outcome using Spring etc. – user949300 Aug 22 '13 at 22:00
  • Once upon time I was thinking about that idea too (using Spring framework for swing apps) but I gave up considering that to be a kind of, overkill. Or is it? I would like to see this question improved and I would like to make me wrong. ;) – Branislav Lazic Aug 23 '13 at 00:04
  • 1
    I still don't think it's overkill. Listeners are really part of the controller - they're actions triggered by view events. I'd prefer to construct the view as pure display and keep the smarts outside it. HFoE's comment about Guice is correct - any DI engine will do. Pick something else if you think Spring is too heavy, but recognize that Spring isn't "all or none". You can use just the features you're intereted in and leave the rest. – duffymo Aug 23 '13 at 12:05
  • -1 Since nobody has explained how these listeners can get dynamically disabled I have to downvote. – user949300 Aug 23 '13 at 17:02
  • Dynamically disabled? You would respond to some other event and removeListener by name. I'd downvote your downvote if I could. – duffymo Aug 23 '13 at 17:25
  • 1
    First, removing the listener will _not disable_ the button. (I'm pretty sure?). Second, you don't want to be constantly adding and removing listeners to dozens of widgets. How do you know which ones? Talk about complexity and memory leaks! Action has an enabled property, and fires the event for you - use it as it is intended. Listeners do not. Also, since the listeners were added using DI, e.g. Spring or Guice, and you propose to removed them by Java code, that's just terrible duplication DRY. – user949300 Aug 23 '13 at 17:43
  • @duffymo I like your idea to be able to downvote a downvote! There's been several times I wanted to do that. :-) – user949300 Aug 23 '13 at 17:52
2

At some point, anonymous inner classes go from "convenient" to an evil mess. You have passed that point.

Like Tim, I suggest you use an AbstractAction. However, to expand, create your own abstract subclass that can read its name, icon, description (for tooltips) etc. from a configuration file. That way you can internationalize the code, and others (marketing) can easily change the buttons from "Save" to "SuperSomethingTM" or whatever. And the graphic artist can easily modify the icon. Extend this class for the actual implementation.

One added advantage of AbstractAction is that they can be disabled. So, if there are no changes to be saved, you can easily disable the Save menu, the Save button and the Save icon in the toolbar.

There are various ways to store and read this config info. One is a ResourceBundle.

(Typed on my Nook so this is on the short side, others feel free to expand)

user949300
  • 15,364
  • 7
  • 35
  • 66
2

something like as this code

public class SomeListeners implements ActionListener{

    @Override
    public void actionPerformed(ActionEvent e){
        String command = e.getActionCommand();
        switch(command){
            case "This button":
                doThis();
                break;
            case "That button":
                doThat();
                break;                          
        }
    }     
}
Community
  • 1
  • 1
mKorbel
  • 109,525
  • 20
  • 134
  • 319
  • I don't understand your 2nd bullet. When `doThis()`, which is hooked up to a Button and a Menu somewhere, needs to be disabled because a File hasn't been loaded, or the user isn't signed in as an administrator, or the network connection is down, just how would you do that in this system? – user949300 Aug 23 '13 at 00:46
  • separate events from actionperformed to logical blocks, is more than 50pct that combinations of events is used for two or three JButtons or JMenuItems, one void can be reused by a few ActionPerformed, then there I see the place for setEnabled in Swing Action – mKorbel Aug 23 '13 at 05:59
  • 1
    I personally don't care for the switch/case. I'd keep the action to one to a customer. – duffymo Aug 23 '13 at 12:06
  • Still confused - what is a SwingAction and how is it hooked up? If you mean a javax.swing.Action, that's what I've been suggesting and, if so, why bother with a separate listener at all? I agree with @duffymo about the switch/case. – user949300 Aug 23 '13 at 17:06
  • I'm never suggest switch - case, logics for EventHandler is very different, hmmm there isn't strict logics, quite everything is possible, parent on top of all Listener (excl. Key & Mouse), btw did you tried my code example, Still confused - what is a SwingAction == hight possible abstraction, accesible, settable overloadable, forget about that, ignore in context of my answer, important is EventHandler only – mKorbel Aug 23 '13 at 18:31
  • @user949300 What mKorbel is getting at is that you can tell your objects that have the same ActionListener to have different ActionCommand Strings. For example thisButton.setActionCommand("This button"); and thatButton.setActionCommand("That button"); and then you give them both the same action listener thisButton.addActionListener(new SomeListener()); and thatButton.addActionListener(new SomeListener());. In this way if you have buttons that perform related tasks you can group them logically into the same ActionListener and yet still give them different actions to perform. – sage88 Oct 22 '14 at 21:23
1

JSR 296 was meant to provide a framework for what you are doing and many other things that have been mentioned here (i18n, Actions, etc). The most active implementation is the Better Swing Application Framework, or BSAF. A good article on how to use any JSR 296 implementation can be found here.

Jim S.
  • 231
  • 1
  • 6
  • Thank you! I appreciate your response! – Daniel Mac Aug 22 '13 at 18:06
  • 1
    BSAF does not appear to be actively maintained. I do use it in a project of my own, which started development back when JSR 296 looked like it would become a standard part of Java. I would probably not use BSAF if I started a new project. – Enwired Aug 22 '13 at 20:21