views:

2092

answers:

5

I was recently doing a programming assignment that required us to implement in code a program specified by a UML diagram. At one point, the diagram specified that I had to create an anonymous JButton that displayed a count (starting at one) and decremented each time it was clicked. The JButton and its ActionListener both had to be anonymous.

I came up with the following solution:

public static void main(String[] args) {
  JFrame f = new JFrame("frame");
  f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  f.setSize(400, 400);

  f.getContentPane().add(new JButton() {

    public int counter;

    {
      this.counter = 1;
      this.setBackground(Color.ORANGE);
      this.setText(this.counter + "");

      this.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent arg0) {
          counter --;
          setText(counter + "");
        }
      });

    }
  });

  f.setVisible(true);

}

This adds an anonymous JButton, then adds another (inner) anonymous ActionListener to handle events and update the button's text as necessary. Is there a better solution? I'm pretty sure I can't declare an anonymous JButton implements ActionListener (), but is there another more elegant way to achieve the same result?

+2  A: 

I usually go something like this:

JPanel panel = new JPanel();
panel.add(new JButton(new AbstractAction("name of button") {
    public void actionPerformed(ActionEvent e) {
        //do stuff here
    }
}));

AbstractAction implements ActionListener so this should satisfy the task.

It can be bad practice to squish so many lines of code together, but if you're used to reading it then it can be quite elegant.

Cogsy
This doesn't actually allow you to change the buttons text from within the action listener... That's quite an awkward requirement and I can't think of a way to improve what you already have.
Cogsy
So the public declaration of counter within the anonymous JButton and the unqualified access to it from within the ActionListener are OK?
Tim
Yup, it's the only way you'll be able to get a 'handle' on those fields. Just remember that the fields in the inner class will hide fields of the same name declared in the outer scope. Hopefully your compiler/IDE will warn you explicitly.
Cogsy
A: 

I would not do something like that in a real-world program, but given the requirements in your assignment, you can hardly do better.

ammoQ
A: 

Well there is a much more elegant way to do it.

Unfortunately, it's not a Core Java/Swing approach.

You can use SwingBuilder in Groovy to achieve the same result, using slightly more terse syntax, e.g. psuedo code:

button(text: '' + counter,
         actionPerformed: {counter--; text = '' + counter + ''},
         constraints:BL.SOUTH)

[http://groovy.codehaus.org/Swing+Builder][1]

I wouldn't use this in your assignment though, I've seen students really deviate from the norm and get marked down for it, but at least you can include it as a possible avenue to investigate further.

I think what you have at present is absolutely fine though.

Jon
A: 

Implementing multiple types is generally a bad idea.

It is rarely necessary to extend JComponent classes, although a lot of bad software and tutorials do it. An idiom/hack that has been gaining ground recently is Double Brace - a class is only subclasses in order to give it an instance initialiser which acts like a with statement from other languages.

In this case, the relevant code can be written as:

JButton button = new JButton();
button.addActionListener(new ActionListener() {
    int counter = 1;
    {
        updateText();
    }
    public void actionPerformed(ActionEvent arg0) {
        --counter;
        updateText();
    }
    private void updateText()
        setText(Integer.toString(counter));
    }
});
f.getContentPane(button);

If it gets more complex, then you'll probably want to make an outer class (that does not implement ActionListener or extend JButton) to handle the data.

Also note, you should be using the EventQueue.invokeLater boilerplate to ensure that Swing components are only ever used on the AWT EDT.

Tom Hawtin - tackline
updateText won't work with just setText - I think you need button.setText, or to nest the listener inside a new anonymous subclass of JButton.Also, f.getContentPane doesn't take any arguments, I think you mean f.getContentPane.add(button).
Mike Houston
+1  A: 

It's quite ugly, but you could do the following using the ActionListener method and an anonymous class:

  f.getContentPane().add(new JButton(new AbstractAction("name of button") {
   private int counter = 0;

   public void actionPerformed(ActionEvent e) {
    ((JButton) e.getSource()).setText(Integer.toString(counter--));
   }
  }) {
   {
    setText("1");
   }
  });

To make it easier to access the counter you could move it out to the top level of your class and access it from both places where setText is called.

Mike Houston
+1. It might be 'ugly' (your word, not mine), but I find it easier to understand than the code provided in the question. I guess the only question would be whether it meets the criteria that 'The JButton and its ActionListener both had to be anonymous.' AbstractAction implements ActionListener, but the instructor may feel that the criteria was not met.
Grant Wagner