2

I'm working on a custom component and came up with a problem. Here is the component:

public class MyComponent extends JPanel {

    private final JButton jButton;
    private final JLabel jLabel;

    public MyComponent(){
        jButton = new JButton();
        //etc..
    }

    public void addActionListener(ActionListener l){
         //The problem with this is that ActionEvent has source attribute 
         //set to jButton which is not desirable. How can I set it to this?
         jButton.addActionListener(l);
    }

    //other component-specific methods
}

The thing is I'm trying to hide MyComponent's implementation details. But setting listener this way is not good since a caller may observe that source attrubte is jButton. How can I set to the enclosing MyComponent instance?

mKorbel
  • 109,525
  • 20
  • 134
  • 319
user3663882
  • 6,957
  • 10
  • 51
  • 92

2 Answers2

2

Instead of allowing the client to pass in an ActionListener, have the client pass in a different callback, create your own listener, then have your listener invoke the callback:

public class MyComponent extends JPanel {
    private final JButton jButton;

    public MyComponent(){
        jButton = new JButton();
    }

    public void addActionListener(SomeCallback callback){
        jButton.addActionListener(event -> { //create listener
            callback.execute(); //invoke callback
        });
    }
}

interface SomeCallback {
    void execute();
}

If you want to pass the client the ActionEvent without the ability to access ActionEvent#getSource(), create a wrapper:

class ActionEventWrapper {
    private ActionEvent event;

    public MyActionEvent(ActionEvent event) {
        this.event = event;
    }

    //expose methods that aren't getSource()
    public String getActionCommand() {
        return event.getActionCommand();
    }
}

Simply add this type to the callback's method parameter:

interface SomeCallback {
    void execute(ActionEventWrapper event);
}

You could then create a new ActionEventWrapper anytime an event is triggered:

    public void addActionListener(SomeCallback callback){
        jButton.addActionListener(event -> {
            callback.execute(new ActionEventWrapper(event));
        });
    }

If you really want to adjust the source of the component's listener's events, simply create a new ActionEvent, specifying whichever source you want via the constructor:

public void addActionListener(ActionListener listener) {
    jButton.addActionListener(event -> {
        listener.actionPerformed(new ActionEvent(..., event.getID(), event.getActionCommand()));
    });
}

The ... is where you specify which component you want to act as the source.

Vince
  • 14,470
  • 7
  • 39
  • 84
  • Actually this is a solution in my case. But what avout the orginal question... How to customize `ActionEvent`'s `source`? – user3663882 Apr 19 '16 at 16:03
  • @user3663882 You can't. The `ActionEvent` is created "behind the scenes", and the source is passed to the event as soon as the action has been triggered. Although I did add in an option for passing the callback an `ActionEvent` that doesn't contain `getSource()`. Let me know if this helps! – Vince Apr 19 '16 at 16:05
  • @user3663882 And let me know if there is anything else you are wondering about, or if this design doesn't fit your needs (please explain why aswell, so I can fix up my answer) – Vince Apr 19 '16 at 16:18
1

Following should work.

@SuppressWarnings("all")
public class MyComponent extends JPanel {
    private final JButton jButton = new JButton();
    private final JLabel jLabel = new JLabel();

    public void addActionListener(final ActionListener listener) {
        final MyComponent self = this;

        ActionListener newListener = new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                ActionEvent newEvent = new ActionEvent(e.getSource(), e.getID(), e.getActionCommand()) {
                    @Override
                    public Object getSource() {
                        return self;
                    }
                };
                listener.actionPerformed(newEvent);
            }
        };
        jButton.addActionListener(newListener);
    }
}

With Lambda expression (singular)

@SuppressWarnings("all")
public class MyComponent2 extends JPanel {
    private final JButton jButton = new JButton();
    private final JLabel jLabel = new JLabel();

    public void addActionListener(final ActionListener listener) {
        MyComponent2 self = this;

        jButton.addActionListener(e-> {
            ActionEvent newEvent = new ActionEvent(e.getSource(), e.getID(), e.getActionCommand()) {
                @Override
                public Object getSource() {
                    return self;
                }
            };
            listener.actionPerformed(newEvent);
        });
    }
}
11thdimension
  • 10,333
  • 4
  • 33
  • 71
  • I suggest avoiding the anonymous classes by switching to a lambda for the listener, and specifying the source through the constructor of `ActionEvent` instead of overriding the `getSource()` method – Vince Apr 19 '16 at 16:29
  • Lambda expressions are anonymous classes, I will try to convert the code to lambda equivalent. – 11thdimension Apr 19 '16 at 16:52
  • They aren't anonymous classes, nor do they generate any extra binary files after compiling. Lambdas are not simply syntactic sugar - check out the byte code. – Vince Apr 19 '16 at 16:53
  • Updated the code to use Lamda expression and fix the bug in the code. @Vince Emign as you can see, we are not saving much here by using Lambda expression. – 11thdimension Apr 19 '16 at 18:12
  • It gets rid of the boilerplate code, making it a lot easier to read, lowering the cognition required to understand the code (identifier is omitted, param type info is optional, no access modifiers). It hides the fact that an object is involved, since the purpose of this behavior is to pass a function to another function, which again frees up cognition. It also gets rid of the excess class files that get generated from anonymous classes, reducing the amount of work the class loader has to do. Trust me, a lot is saved, in both computational and mental processing. – Vince Apr 19 '16 at 18:20
  • Also thought I'd mention that instead of passing `e.getSource()` to the constructor of `ActionEvent`, just pass `self`. That way you won't have to override `getSource()` to return `self`, and avoids the anonymous class. – Vince Apr 19 '16 at 18:23
  • Class files are related to JVM, I don't care if JVM has to run more code if I can write less, Lamda expressions for me are way to reduce code and no it doesn't make it more understandable. As Java doesn't have inherent global definition of Callback. It has to have type safety and thus it needs types for callbacks. Now with Lamda expression we have to always hunt for the type of the arguments being passed when looking at them second time. – 11thdimension Apr 19 '16 at 18:28
  • Well lambdas allow you to both write less code and reduce the amount of work carried out by the JVM, so it's a win-win. As for "hunting for type of arguments", if the context of your code does not suggest what is being performed, then you should improve your design. Complaining about that is like complaining about how you'd have to "hunt" for return types: `BufferedReader#readLine()` - does it return a `Line` instance? Is it a `String`? We'd have to check the documentation to confirm. This problem don't seem to outweigh the benefits. – Vince Apr 19 '16 at 18:36