Moving code from ActionListener to main()

Tags: ,



PROBLEM:

I have following code for the java.awt.Button :

Button btn = new Button("abcde");
btn.addActionListener((ActionEvent e) ->
{
    String s = btn.getLabel().toUpperCase();    
    btn.setLabel(s);    
});

I need to move the code inside btn.addActionListener to the public static void main(String[] args), something like below (in pseudo code):

Button btn = new Button("abcde");
btn.addActionListener((ActionEvent e) ->
{
    notify_main()_that_button_had_been_clicked();  
});

public static void main(String[] args)
{
    block_until_button_clicked();

    String s = UI.getButton().getLabel();
    s = s.toUpperCase();
    UI.getButton().setLabel(s);
}

RELEVANT INFORMATION :

I am aware that there are better solutions for GUI development, but I am restricted to using AWT for UI.

I have no power to change the above, nor can I provide details about the real code due to legal restrictions.

To remedy above, I am submitting the below MVCE. Please base your answers on it:

import java.awt.Frame;
import java.awt.Button;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

public class ResponsiveUI extends Frame
{
    public final Button btn = new Button("abcde");

    public ResponsiveUI()
    {
        add(btn);

        btn.addActionListener((ActionEvent e) ->
        {
            String s = btn.getLabel().toUpperCase();    
            btn.setLabel(s);    
        });
    }

    public static void main(String[] args)
    {
        ResponsiveUI rui = new ResponsiveUI();
        rui.addWindowListener(new WindowAdapter()
        {
            @Override
            public void windowClosing(WindowEvent we)
            {
                System.exit(0);
            }
        });
        rui.setSize(250, 150);
        rui.setResizable(false);
        rui.setVisible(true);
    }
}

MY EFFORTS TO SOLVE THIS:

I have used Google extensively, and was able to find some useful links.

  1. UI will run in the separate thread, which will make it responsive (I do not know how to join() it properly, though).
  2. For signaling mechanism, wait() and notify() seem like the right way to go.
  3. To set Button’s text, I can use EventQueue.InvokeAndWait.
  4. To get Button’s text, I do not know what to do, but I have an ugly workaround.

Below is the modified MVCE :

import java.awt.Frame;
import java.awt.Button;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.lang.reflect.InvocationTargetException;

public class ResponsiveUI extends Frame
{

    public final Object lock = new Object();  // POINT #2 : signaling mechanism
    public final Button btn = new Button("abcde");

    public ResponsiveUI()
    {
        add(btn);

        btn.addActionListener((ActionEvent e) ->
        {
            // POINT #2 : signal the main() thread that button is clicked
            synchronized (lock)
            {
                lock.notify();
            } 
        });
    }

    public static void main(String[] args)
    {
        ResponsiveUI rui = new ResponsiveUI();

        // POINT #1: put UI into separate thread, so we can keep it responsive
        // POINT #1: I still do not know how to properly join() (it works OK though)
        Runnable r = () ->
        {
            rui.addWindowListener(new WindowAdapter()
            {
                @Override
                public void windowClosing(WindowEvent we)
                {
                    System.exit(0);
                }
            });
            rui.setSize(250, 150);
            rui.setResizable(false);
            rui.setVisible(true);
        };

        EventQueue.invokeLater(r);

        try
        {
            synchronized (rui.lock)    // POINT #2  
            {                          // POINT #2 
                rui.lock.wait();       // POINT #2 : wait for button press

                final Button b = new Button();  // POINT #4 : EventQueue uses final local variables
                                                // store text into temp button (ugly but works)
                EventQueue.invokeAndWait(() ->  // POINT #4
                {
                    b.setLabel(rui.btn.getLabel());  
                });
                // we could do all kind of things, but for illustrative purpose just transform text into upper case
                EventQueue.invokeAndWait(() ->    // POINT #3 :
                {
                    rui.btn.setLabel(b.getLabel().toUpperCase());

                });
            }
        }
        catch (InterruptedException | InvocationTargetException ex)
        {
            System.out.println("Error : " + ex);
        }
    }
}

Answer

As I understand your question, you want the main thread to be notified when the [AWT] button is clicked and upon receipt of that notification, you want to change the text of that button’s label.

I started with the code from your modified minimal-reproducible-example that you posted in your question.

Here is my code with explanatory notes after it.

import java.awt.Button;
import java.awt.EventQueue;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

public class ResponsiveUI extends Frame {
    private static String  btnTxt;
    public final Object lock = new Object();
    public final Button btn = new Button("abcde");

    public ResponsiveUI() {
        add(btn);
        btn.addActionListener((ActionEvent e) -> {
            synchronized (lock) {
                btnTxt = e.getActionCommand();
                lock.notifyAll();
            } 
        });
    }

    public static void main(String[] args) {
        ResponsiveUI rui = new ResponsiveUI();

        Runnable r = () -> {
            rui.addWindowListener(new WindowAdapter() {
                @Override
                public void windowClosing(WindowEvent we) {
                    System.exit(0);
                }
            });
            rui.setSize(250, 150);
            rui.setResizable(false);
            rui.setVisible(true);
        };
        EventQueue.invokeLater(r);
        synchronized (rui.lock) {
            try {
                rui.lock.wait();
                String newBtnTxt = btnTxt.toUpperCase();
                EventQueue.invokeLater(() -> rui.btn.setLabel(newBtnTxt));
            }
            catch (InterruptedException x) {
                x.printStackTrace();
            }
        }
    }
}

In order for the GUI thread to notify the main thread, I agree with you that wait() and notifyAll() is the mechanism to use. I use notifyAll() rather than notify() to make sure that all the waiting threads are notified.

I use a shared variable btnTxt to transfer the text of the button’s label between the GUI thread and the main thread.

By default, the action command of the ActionEvent is the text of the [AWT] button’s label. Hence the use of method getActionCommand() in my ActionListener implementation.

All changes to the GUI must be executed on the GUI thread, so after creating the new text for the button’s label in the main thread, I use method invokeLater() of class java.awt.EventQueue to actually change the button’s label text.

You should be able to copy the above code as is, compile it and run it.



Source: stackoverflow