A Java JList example: DefaultListModel, ListSelectionModel, ListSelectionListener, and ActionListener

This is a Java JList example from Sun Microsystems that shows how to work with a JList, including some of the most common and important features, including DefaultListModel, ListSelectionModel, ListSelectionListener, ActionListener, and more. I’ve included it here because I work with Java Swing applications quite a bit, and I want it to be easier to find on my website.

/**
 * Copyright (c) 1995 - 2008 Sun Microsystems, Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Sun Microsystems nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

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

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

// ListDemo.java requires no other files.
public class ListDemo extends JPanel implements ListSelectionListener {
  private JList list;
  private DefaultListModel listModel;

  private static final String hireString = "Hire";
  private static final String fireString = "Fire";
  private JButton fireButton;
  private JTextField employeeName;

  public ListDemo() {
    super(new BorderLayout());

    listModel = new DefaultListModel();
    listModel.addElement("Debbie Scott");
    listModel.addElement("Scott Hommel");
    listModel.addElement("Sharon Zakhour");

    // Create the list and put it in a scroll pane.
    list = new JList(listModel);
    list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    list.setSelectedIndex(0);
    list.addListSelectionListener(this);
    list.setVisibleRowCount(5);
    JScrollPane listScrollPane = new JScrollPane(list);

    JButton hireButton = new JButton(hireString);
    HireListener hireListener = new HireListener(hireButton);
    hireButton.setActionCommand(hireString);
    hireButton.addActionListener(hireListener);
    hireButton.setEnabled(false);

    fireButton = new JButton(fireString);
    fireButton.setActionCommand(fireString);
    fireButton.addActionListener(new FireListener());

    employeeName = new JTextField(10);
    employeeName.addActionListener(hireListener);
    employeeName.getDocument().addDocumentListener(hireListener);
    String name = listModel.getElementAt(list.getSelectedIndex()).toString();

    // Create a panel that uses BoxLayout.
    JPanel buttonPane = new JPanel();
    buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS));
    buttonPane.add(fireButton);
    buttonPane.add(Box.createHorizontalStrut(5));
    buttonPane.add(new JSeparator(SwingConstants.VERTICAL));
    buttonPane.add(Box.createHorizontalStrut(5));
    buttonPane.add(employeeName);
    buttonPane.add(hireButton);
    buttonPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));

    add(listScrollPane, BorderLayout.CENTER);
    add(buttonPane, BorderLayout.PAGE_END);
  }

  class FireListener implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      // This method can be called only if
      // there's a valid selection
      // so go ahead and remove whatever's selected.
      int index = list.getSelectedIndex();
      listModel.remove(index);

      int size = listModel.getSize();

      if (size == 0) { // Nobody's left, disable firing.
        fireButton.setEnabled(false);

      } else { // Select an index.
        if (index == listModel.getSize()) {
          // removed item in last position
          index--;
        }

        list.setSelectedIndex(index);
        list.ensureIndexIsVisible(index);
      }
    }
  }

  // This listener is shared by the text field and the hire button.
  class HireListener implements ActionListener, DocumentListener {
    private boolean alreadyEnabled = false;
    private JButton button;

    public HireListener(JButton button) {
      this.button = button;
    }

    // Required by ActionListener.
    public void actionPerformed(ActionEvent e) {
      String name = employeeName.getText();

      // User didn't type in a unique name...
      if (name.equals("") || alreadyInList(name)) {
        Toolkit.getDefaultToolkit().beep();
        employeeName.requestFocusInWindow();
        employeeName.selectAll();
        return;
      }

      int index = list.getSelectedIndex(); // get selected index
      if (index == -1) { // no selection, so insert at beginning
        index = 0;
      } else { // add after the selected item
        index++;
      }

      listModel.insertElementAt(employeeName.getText(), index);
      // If we just wanted to add to the end, we'd do this:
      // listModel.addElement(employeeName.getText());

      // Reset the text field.
      employeeName.requestFocusInWindow();
      employeeName.setText("");

      // Select the new item and make it visible.
      list.setSelectedIndex(index);
      list.ensureIndexIsVisible(index);
    }

    // This method tests for string equality. You could certainly
    // get more sophisticated about the algorithm. For example,
    // you might want to ignore white space and capitalization.
    protected boolean alreadyInList(String name) {
      return listModel.contains(name);
    }

    // Required by DocumentListener.
    public void insertUpdate(DocumentEvent e) {
      enableButton();
    }

    // Required by DocumentListener.
    public void removeUpdate(DocumentEvent e) {
      handleEmptyTextField(e);
    }

    // Required by DocumentListener.
    public void changedUpdate(DocumentEvent e) {
      if (!handleEmptyTextField(e)) {
        enableButton();
      }
    }

    private void enableButton() {
      if (!alreadyEnabled) {
        button.setEnabled(true);
      }
    }

    private boolean handleEmptyTextField(DocumentEvent e) {
      if (e.getDocument().getLength() <= 0) {
        button.setEnabled(false);
        alreadyEnabled = false;
        return true;
      }
      return false;
    }
  }

  // This method is required by ListSelectionListener.
  public void valueChanged(ListSelectionEvent e) {
    if (e.getValueIsAdjusting() == false) {

      if (list.getSelectedIndex() == -1) {
        // No selection, disable fire button.
        fireButton.setEnabled(false);

      } else {
        // Selection, enable the fire button.
        fireButton.setEnabled(true);
      }
    }
  }

  /**
   * Create the GUI and show it. For thread safety, this method should be
   * invoked from the event-dispatching thread.
   */
  private static void createAndShowGUI() {
    // Create and set up the window.
    JFrame frame = new JFrame("ListDemo");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    // Create and set up the content pane.
    JComponent newContentPane = new ListDemo();
    newContentPane.setOpaque(true); // content panes must be opaque
    frame.setContentPane(newContentPane);

    // 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();
      }
    });
  }
}