Responsive Swing Application


In this section, we'll create an application that responds to a user action, namely the pressing of a button.

The Program edit

Last section, we wrote a program that displays a text label along with a button. In this section, we'll add functionality to the application so that the text changes when the user presses the button. In order to accomplish this, we'll write a subclass of JLabel that can change its text, and implements the ActionListener interface. Because a JButton generates action events when pushed, we can connect our specialized JLabel to the JButton by registering it as a listener. Here's the code:

  A new listener
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.SwingConstants;
import javax.swing.JLabel;

// ActionEvent and ActionListener
// live in the java.awt.event
// package

class ChangingLabel extends JLabel implements ActionListener {
    
    // String constants which define the text that's displayed.
    private static final String TEXT1 = "Hello, World!";
    private static final String TEXT2 = "Hello again!";
    
    // Instance field to keep track of the text currently
    // displayed
    private boolean usingText1;
    
    // Constructor.  Sets alignment and initial text, and
    // sets the usingText1 field appropriately.
    public ChangingLabel() {
        this.setHorizontalAlignment(SwingConstants.CENTER);        
        this.setText(TEXT1);    
        usingText1 = true;
    }
    
    // A method to change the label text.  If the
    // current text is TEXT1, changes it to TEXT2,
    // and vice-versa.  The method is private here,
    // because it is never called directly.
    private void changeLabel() {
        if(usingText1)
            this.setText(TEXT2);
        else
            this.setText(TEXT1);

                usingText1 = !usingText1;
    }
    
    // This method implements the ActionListener interface.
    // Any time that an action is performed by a component
    // that this object is registered with, this method
    // is called.  We can analyze the event object received
    // here for more information if we want to, but it's not
    // necessary in this application.
    public void actionPerformed(ActionEvent e) {
        this.changeLabel();
    }
}
  A Swing application with an active button
import java.awt.BorderLayout;

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

// Application with a changing text field
public class HelloWithButton2 {

        public static void main(String[] args) {
                
                // Constructs the ChangingLabel.  All the
            // intialization is done be the default
            // constructor, defined in the ChangingLabel
            // class below.
                ChangingLabel changingLabel = new ChangingLabel();
                
                // Create the button
                JButton button = new JButton("Button");
                
                // Register the ChangingLabel as an action
                // listener to the button.  Whenever the
                // button is pressed, its ActionEvent will
                // be sent the ChangingLabel's
                // actionPerformed() method, and the code
                // there will be executed.
                button.addActionListener(changingLabel);
                
                // Create the frame
                JFrame frame = new JFrame("Hello");
                
                // Add the label and the button to the
                // frame, using layout constants.
                frame.add(changingLabel, BorderLayout.CENTER);
                frame.add(button, BorderLayout.SOUTH);
                
                frame.setSize(300, 300);
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setVisible(true);
                frame.setLocationRelativeTo(null);
                frame.toFront();
        }
}

In this application, class ChangingLabel defines a special type of label that is an action listener (ie, that can receive ActionEvents) by implementing the ActionListener interface. It also has the ability to change its text through the private method changeLabel(). The actionPerformed() method just calls changeLabel() when it receives an ActionEvent.

The main program code in HelloWithButton2 is similar to the code in the last section, but uses a ChangeLabel instead of an ordinary JLabel object. It also registers the ChangeLabel as an ActionListener of the button with the button's addActionListener() method. We can do this because JButtons create ActionEvents, and ChangeLabel is an ActionListener (ie, implements the ActionListener interface).

There's one thing to notice in the above example. Two classes are defined, one to contain the program code in the static main() method, and another that defines a type used in the program. However, there's no reason that we can't put the main() method directly in our new type's class. In fact, putting the program's main() method in a customized subclass of a component is a common idiom in programming Swing applications. When reorganized this way, the program looks like this:

  The merged class
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingConstants;

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

// ActionEvent and ActionListener
// live in the java.awt.event
// package

// Application with a changing text field
public class ChangingLabel extends JLabel implements ActionListener {
        
        // String constants which define the text that's displayed.
        private static final String TEXT1 = "Hello, World!";
        private static final String TEXT2 = "Hello again!";
        
        // Instance field to keep track of the text currently
        // displayed
        private boolean usingText1;
        
        // Constructor.  Sets alignment and initial text, and
        // sets the usingText1 field appropriately.
        public ChangingLabel() {
                this.setHorizontalAlignment(SwingConstants.CENTER);             
                this.setText(TEXT1);    
                usingText1 = true;
        }
        
        // A method to change the label text.  If the
        // current text is TEXT1, changes it to TEXT2,
        // and vice-versa.  The method is private here,
        // because it is never called directly.
        private void changeLabel() {
                if(usingText1) {
                        this.setText(TEXT2);
                        usingText1 = false;
                } else {
                        this.setText(TEXT1);
                        usingText1 = true;
                }
        }
        
        // This method implements the ActionListener interface.
        // Any time that an action is performed by a component
        // that this object is registered with, this method
        // is called.  We can analyze the event object received
        // here for more information if we want to, but it's not
        // necessary in this application.
        public void actionPerformed(ActionEvent e) {
                this.changeLabel();
        }
        
        // Main method.  This is the code that is executed when the
        // program is run
        public static void main(String[] args) {
            
            // Constructs the ChangingLabel.  All the
            // intialization is done be the default
            // constructor, defined in the ChangingLabel
            // class below.
            ChangingLabel changingLabel = new ChangingLabel();
            
            // Create the button
            JButton button = new JButton("Button");
            
            // Register the ChangingLabel as an action
            // listener to the button.  Whenever the
            // button is pressed, its ActionEvent will
            // be sent the ChangingLabel's
            // actionPerformed() method, and the code
            // there will be executed.
            button.addActionListener(changingLabel);
            
            // Create the frame
            JFrame frame = new JFrame("Hello");
            
            // Add the label and the button to the
            // frame, using layout constants.
            frame.add(changingLabel, BorderLayout.CENTER);
            frame.add(button, BorderLayout.SOUTH);
            
            frame.setSize(300, 300);
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setVisible(true);
            frame.setLocationRelativeTo(null);
            frame.toFront();
    }
}

This program does exactly the same thing as the first, but organizes all the code into one class.

Multi-threading in Swing edit

The thread that performs actions inside the action, change, mouse and other listeners is called a Swing thread. These methods must execute and return quickly, as the Swing thread that must be available for the Swing framework to keep the application responsive. In other words, if you perform some time-consuming calculation or other activities, you must do this in a separate thread, not in the Swing thread. However then you access Swing components from the non-swing thread you initiated yourself, they may hang due race conditions if manipulated by the Swing thread at the same time. Because of that, Swing component from the non-Swing thread must be accessed through intermediate Runnable, using the SwingUtilities.invokeAndWait() method:

  Multi-threading
 String myMessage = "abc";
 final String message = myMessage; // Making final allows to access from inner class here.
 SwingUtilities.invokeAndWait(new Runnable {
   public void run() {
     myLabel.setText(message);
   }
 })

This call will fire setText(message) in the appropriate time, when the Swing thread will be ready to respond. This approach is also usual when implementing the moving progress bar that only works properly if the slow process itself is implemented outside the Swing thread.


 

To do:
Add screenshots.


 

To do:
Use separated examples.