javajframejbutton

How does JButton call the actionPerformed method?


I was trying to understand exactly how a JButton object works.

I created the below SampleFrame object that extends JFrame.

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;

public class SampleFrame extends JFrame implements ActionListener{
    
    SampleFrame() {
        JButton button = new JButton("Hello");
        add(button);
        button.addActionListener(this);
    }
    @Override
    public void actionPerformed(ActionEvent arg0) {
        System.out.println("Hello");
    }

    public static void main(String[] args) {
        new SampleFrame().setVisible(true);
    }
}

I understand that add(button) is adding the JButton object to the JFrame and that button.AddActionLister(this) is adding the SampleFrame object to a set of listeners of that event so its actionPerformed method is called every time the button is pressed. What I don't understand is how is this method actionPerformed being called exactly? Is it some thread where this is getting called? Sorry if this sounds too naive, I'm not too familiar with Java (and threads).

Not sure if this has anything to do with it, but when I close the frame, the code still shows up as running in the console. This indicates to me that there is some sort of a 'while loop' running in parallel.


Solution

  • Let's rewrite your example code to comply with the usual Swing app approach as seen HelloWorldSwing class from the Swing tutorial provided by Oracle free-of-cost.

    public class OneButtonSwingApp
    {
        /**
         * Create & show GUI.  For thread safety, invoke from the event-dispatching thread.
         */
        private static void createAndShowGUI ( )
        {
            // Create and set up the window.
            JFrame frame = new JFrame( "One Button Example" );
            frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
    
            // Widgets
            JLabel timeLabel = new JLabel( "Now: " + Instant.now( ) );
            JButton tellTimeButton = new JButton( "Tell time" );
    
            // Behavior
            tellTimeButton.addActionListener( ( ActionEvent actionEvent ) -> { timeLabel.setText( "Now: " + Instant.now( ) ); } );
    
            // Arrange
            frame.setLayout( new FlowLayout( ) );
            frame.getContentPane( ).add( timeLabel );
            frame.getContentPane( ).add( tellTimeButton );
    
            // Display the window.
            frame.pack( );
            frame.setVisible( true );
        }
    
        public static void main ( String[] args )
        {
            // Schedule a job for the event-dispatching thread:
            // creating and showing this application's GUI.
            javax.swing.SwingUtilities.invokeLater(
                    new Runnable( )
                    {
                        public void run ( )
                        {
                            createAndShowGUI( );
                        }
                    } );
        }
    }
    

    Every Java app starts in a thread to execute the main method.

    Swing apps have an additional thread. That thread is dedicated to running the GUI. See Oracle tutorial: The Event Dispatch Thread.

    In our code here, on our initial app thread, we call SwingUtilities.invokeLater to schedule the later execution of some code on that event-dispatch thread. As part of that call we pass an object of an anonymous class that implements the Runnable interface. Implementing the run method is required to fulfill the contract of Runnable.

    In modern Java, we would simplify that main method with a method reference.

        public static void main ( String[] args )
        {
            // Schedule a job for the event-dispatching thread:
            // creating and showing this application's GUI.
            javax.swing.SwingUtilities.invokeLater( OneButtonSwingApp :: createAndShowGUI );
        }
    

    In Swing, like any other GUI framework I have ever seen, you never access or manipulate the GUI elements from another thread. Always use the event-dispatch thread to run any code to access/manipulate GUI elements.

    adding the SampleFrame object to a set of listeners of that event so its actionPerformed method is called every time the button is pressed

    Yes, our tellTimeButton.addActionListener call is passing a lambda (code to be executed later) to a collection of listeners. Calling this method is us telling the button "Here is some code to run whenever an action event occurs.".

    In your code, you passed the SampleFrame instance itself to be a listener. While making a listener of a JFrame object can work, I would recommend against it. Keep your objects clearly delineated, each with a single reason-for-being. Make a separate object with the behavior you want to run in response to the button click.

    Later, at runtime, the Swing framework is watching the user's moves. When the user clicks that button, the button loops through its collection of listeners to execute the code given earlier. The code executes on the event-dispatch thread. So it is crucial that you keep your listener code short and sweet, as the user-interface is frozen/unresponsive while that code executes. If you have a lengthy task to perform, spin off to another thread using SwingWorker.

    What I don't understand is how is this method actionPerformed being called exactly?

    Swing is an 👉🏽 event-driven framework. This means the framework is in charge, monitoring for events coming from the user or other sources. The flow-of-control in your app is not in charge. You set up your listeners ahead of time, then the listener code gets executed as their particular events occur.


    You really should work through the Oracle tutorial on Swing, to get a grasp on these key concepts.