简体   繁体   中英

How to return values from an ActionListener which is not in inner class

I am new in Java and also in StackOverflow so, please excuse me if I do wrong something. I will correct it immediately!

My question is : How could I return a variable which is in a class that implements an ActionListener into another class's variable?

The class that implements the ActionListener is not an inner class.

I voluntary omitted imports.

Here an example:

File_A.java

public class Gui extends JFrame {
    private JButton myButton;
    private String path;
    some other properties...

    public Gui () {
        myButton = new JButton("Some Text");
        myButton.AddActionListener(new Pick_Something());
    }
}

File_B.java

public class Pick_Something implements ActionListener {
    @Override
    public void actionPerformed(ActionEvent e) {
        JFileChooser selectElement = new JFileChooser();
        String path;
        int status = selectElement.showOpenDialog(null);

        if (status == JFileChooser.APPROVE_OPTION)
            path = selectElement.getSelectedFile().getAbsolutePath();
        else
            path = null;
    }
}

How could I return File_B.java 's path variable in File_A.java 's path variable?

I tried to write a method who returns it but the method does not appear in the list of all methods so it is impossible to call. And I tried also to extends Pick_Something with Gui and make path protected but I had a StackOverflowError .

Anyone see what I do wrong or have an idea about how to do?

I would recommend using a call-back, something that Java-8's java.util.function supplies for you, and in fact a Consumer<String> would work perfectly here. Create your Consumer in the original class, and have the ActionListener class call its .accept(...) method, passing information directly from the listener class to the GUI with low coupling. For example if your Gui has a JTextField called filePathTxtField that you want filled with the user's file path of choice, one obtained by the ActionListener, then the consumer could look like so:

Consumer<String> consumer = (String text) -> {
    filePathTxtField.setText(text);
};

This would be created in the Gui class, and then passed into the ActionListener class via a constructor parameter:

// in the Gui class's constructor
button.addActionListener(new PickSomething(consumer));  

// the PickSomething class and its constructor
class PickSomething implements ActionListener {
    private Consumer<String> consumer;

    public PickSomething(Consumer<String> consumer) {
        this.consumer = consumer;
    }

Then the actionPerformed method could look like:

@Override
public void actionPerformed(ActionEvent e) {
    JFileChooser selectElement = new JFileChooser();
    String path;

    // get the path String and set it
    int status = selectElement.showOpenDialog(null);

    if (status == JFileChooser.APPROVE_OPTION) {
        path = selectElement.getSelectedFile().getAbsolutePath();
    } else {
        path = null;
    }

    // pass the path String into the Gui by calling the call-back method, passing it in
    consumer.accept(path);
}

The whole thing could look like:

import java.util.function.Consumer;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

@SuppressWarnings("serial")
public class Gui extends JPanel {
    private JTextField filePathTxtField = new JTextField(45);
    private int foo = 0;

    public Gui() {
        filePathTxtField.setFocusable(false);
        add(filePathTxtField);

        JButton button = new JButton("Get File Path");
        Consumer<String> consumer = (String text) -> {
            filePathTxtField.setText(text);
        };
        button.addActionListener(new PickSomething(consumer));
        add(button);
    }

    private static void createAndShowGui() {
        Gui mainPanel = new Gui();

        JFrame frame = new JFrame("Gui");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(mainPanel);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> createAndShowGui());
    }
}

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.function.Consumer;
import javax.swing.JFileChooser;

public class PickSomething implements ActionListener {
    private Consumer<String> consumer;

    public PickSomething(Consumer<String> consumer) {
        this.consumer = consumer;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        JFileChooser selectElement = new JFileChooser();
        String path;
        int status = selectElement.showOpenDialog(null);

        if (status == JFileChooser.APPROVE_OPTION) {
            path = selectElement.getSelectedFile().getAbsolutePath();
        } else {
            path = null;
        }
        consumer.accept(path);
    }
}   

My question is: How could I return a variable which is in a class that implements an ActionListener into another class's variable?

You can't.

A quick look at the JavaDocs for ActionListener will show you that you the method does not return a value. I'm pretty sure that even if it could it would be worthless, as the only time your code knows the method has been triggered is when it's actually called.

The solution? Pass a "model" to the ActionListener implementation...

Start by defining a simple interface or contract...

public PathPicker {
    public void setPath(File path);
}

Then update PickSomething to accept instances of this interface ...

public class PickSomething implements ActionListener {
    private PathPicker picker;

    public PickSomething(PathPicker picker) {
        this.picker = picker;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        JFileChooser selectElement = new JFileChooser();
        int status = selectElement.showOpenDialog(null);

        if (status == JFileChooser.APPROVE_OPTION) {
            picker.setPath(selectElement.getSelectedFile());
        } else {
            picker.setPath(null);
        }
    }
}

Now, all you need to do is implement the PathPicker interface, pass a reference of of it to PickSomething when you create and wait till it calls setPath .

This is commonly known as "delegation" (and ActionListener is also an example of it), where the actual responsibility to "delegated" to some other object. It's also an example of "observability", in a very simple sense, where the PickSomething can be observer by an instance of PathPicker for a change in its state (a path been selected).

It also decouples the code, as PathPicker doesn't care how the the path is set, only that it is notified when it is.

A note about paths...

File is an abstract representation of file system file or path. It has a lot of really cool functionality to make working with the filesystem easier and simpler.

Many APIs also take a reference of File to perform their operations. You should avoid converting a File to a String where ever possible, as you are going to rob yourself of this functionality and make your life more difficult in the long run

Here is a what I tried, and hope this helps. In this example, the listener class returns the selected path to the main window.

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class ActionListenerTester {

    private String path;
    private JLabel status;

    public static void main(String [] args) {
        new ActionListenerTester().gui();
    }
    private void gui() {
        JFrame frame = new JFrame();
        frame.setTitle("An External Listener");
        JLabel title = new JLabel("Get My Paths:");
        JButton button = new JButton("Get Path");
        button.addActionListener(new MyActionListener(this));
        status = new JLabel("Click the button to get path...");
        Container pane = frame.getContentPane();
        pane.setLayout(new GridLayout(3, 1));
        pane.add(title);
        pane.add(button);
        pane.add(status);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.setSize(500, 300);        
        frame.setVisible(true);
    }

    public void setPath(String path) {
        this.path = path;
        status.setText(path);
    }
}

class MyActionListener implements ActionListener {

    private ActionListenerTester gui;

    public MyActionListener(ActionListenerTester gui) {
        this.gui = gui;
    }

    public void actionPerformed(ActionEvent e) {
        JFileChooser selectElement = new JFileChooser();
        String path = "";
        int status = selectElement.showOpenDialog(null);
        if (status == JFileChooser.APPROVE_OPTION) {
            path = selectElement.getSelectedFile().getAbsolutePath();
        }

        path = path.isEmpty() ? "No path selected!" : path;
        gui.setPath(path);
    }
}



Alternative way:

This is another way to return the value from an action listener class when its not an inner class. to the main GUI class. This uses java.util.Observer and Observable objects. The main GUI class is the observer and the selected path in the action listener class is the observable. When the observable object (the path) is updated, the observer (the main GUI class) is notified with the path value.

 import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.util.Observer; import java.util.Observable; public class ActionListenerTester2 implements Observer { private String path; private JLabel status; public static void main(String [] args) { new ActionListenerTester2().gui(); } private void gui() { JFrame frame = new JFrame(); frame.setTitle("An External Listener 2"); JLabel title = new JLabel("Get My Paths:"); JButton button = new JButton("Get Path"); button.addActionListener(new MyActionListener(this)); status = new JLabel("Click the button to get path..."); Container pane = frame.getContentPane(); pane.setLayout(new GridLayout(3, 1)); pane.add(title); pane.add(button); pane.add(status); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setLocationRelativeTo(null); frame.setSize(500, 300); frame.setVisible(true); } /* * Observer interface's overridden method. * This method runs when the Observable object notifies * its observer objects (in this case, ActionListenerTester2) * about the update to the observable. */ @Override public void update(Observable o, Object arg) { path = (String) arg; status.setText(path); } } class MyActionListener implements ActionListener { private PathObservable observable; public MyActionListener(ActionListenerTester2 gui) { observable = new PathObservable(); observable.addObserver(gui); } public void actionPerformed(ActionEvent e) { JFileChooser selectElement = new JFileChooser(); String path = ""; int status = selectElement.showOpenDialog(null); if (status == JFileChooser.APPROVE_OPTION) { path = selectElement.getSelectedFile().getAbsolutePath(); } System.out.println("Path: " + path); path = path.isEmpty() ? "No path selected!" : path; observable.changeData(path); } /* * When the Observable object changes, the notifyObservers() * method informs all the Observer objects - in this example * the main gui class: ActionListenerTester2. */ class PathObservable extends Observable { PathObservable() { super(); } void changeData(Object data) { // the two methods of Observable class setChanged(); notifyObservers(data); } } } 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM